327 lines
11 KiB
PHP
327 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Originally based on code:
|
|
*
|
|
* Copyright (C) 2000 Edmund Grimley Evans <edmundo@rano.org>
|
|
* Released under the GPL (version 2)
|
|
*
|
|
* Translated from C to PHP by Thomas Bruederli <roundcube@gmail.com>
|
|
* Code extracted from the RoundCube Webmail (http://roundcube.net) project,
|
|
* SVN revision 1757
|
|
* The RoundCube project is released under the GPL (version 2)
|
|
*
|
|
* Copyright 2008-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (LGPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
|
|
*
|
|
* @category Horde
|
|
* @copyright 2000 Edmund Grimley Evans <edmundo@rano.org>
|
|
* @copyright 2008-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @package Imap_Client
|
|
*/
|
|
|
|
/**
|
|
* Allows conversions between UTF-8 and UTF7-IMAP (RFC 3501 [5.1.3]).
|
|
*
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
* @category Horde
|
|
* @copyright 2000 Edmund Grimley Evans <edmundo@rano.org>
|
|
* @copyright 2008-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @package Imap_Client
|
|
*/
|
|
class Horde_Imap_Client_Utf7imap
|
|
{
|
|
/**
|
|
* Lookup table for conversion.
|
|
*
|
|
* @var array
|
|
*/
|
|
private static $_index64 = array(
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
|
|
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
|
|
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
|
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
|
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
|
|
);
|
|
|
|
/**
|
|
* Lookup table for conversion.
|
|
*
|
|
* @var array
|
|
*/
|
|
private static $_base64 = array(
|
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
|
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
|
|
'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
|
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
|
'4', '5', '6', '7', '8', '9', '+', ','
|
|
);
|
|
|
|
/**
|
|
* Is mbstring extension available?
|
|
*
|
|
* @var array
|
|
*/
|
|
protected static $_mbstring = null;
|
|
|
|
/**
|
|
* Convert a string from UTF7-IMAP to UTF-8.
|
|
*
|
|
* @param string $str The UTF7-IMAP string.
|
|
*
|
|
* @return string The converted UTF-8 string.
|
|
* @throws Horde_Imap_Client_Exception
|
|
*/
|
|
public static function Utf7ImapToUtf8($str)
|
|
{
|
|
if ($str instanceof Horde_Imap_Client_Mailbox) {
|
|
return $str->utf8;
|
|
}
|
|
|
|
$str = strval($str);
|
|
|
|
/* Try mbstring, if available, which should be faster. Don't use the
|
|
* IMAP utf7_* functions because they are known to be buggy. */
|
|
if (is_null(self::$_mbstring)) {
|
|
self::$_mbstring = extension_loaded('mbstring');
|
|
}
|
|
if (self::$_mbstring) {
|
|
return @mb_convert_encoding($str, 'UTF-8', 'UTF7-IMAP');
|
|
}
|
|
|
|
$p = '';
|
|
$ptr = &self::$_index64;
|
|
|
|
for ($i = 0, $u7len = strlen($str); $u7len > 0; ++$i, --$u7len) {
|
|
$u7 = $str[$i];
|
|
if ($u7 === '&') {
|
|
$u7 = $str[++$i];
|
|
if (--$u7len && ($u7 === '-')) {
|
|
$p .= '&';
|
|
continue;
|
|
}
|
|
|
|
$ch = 0;
|
|
$k = 10;
|
|
for (; $u7len > 0; ++$i, --$u7len) {
|
|
$u7 = $str[$i];
|
|
|
|
if ((ord($u7) & 0x80) || ($b = $ptr[ord($u7)]) === -1) {
|
|
break;
|
|
}
|
|
|
|
if ($k > 0) {
|
|
$ch |= $b << $k;
|
|
$k -= 6;
|
|
} else {
|
|
$ch |= $b >> (-$k);
|
|
if ($ch < 0x80) {
|
|
/* Printable US-ASCII */
|
|
if ((0x20 <= $ch) && ($ch < 0x7f)) {
|
|
throw new Horde_Imap_Client_Exception(
|
|
Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
|
|
Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
|
|
);
|
|
}
|
|
$p .= chr($ch);
|
|
} else if ($ch < 0x800) {
|
|
$p .= chr(0xc0 | ($ch >> 6)) .
|
|
chr(0x80 | ($ch & 0x3f));
|
|
} else {
|
|
$p .= chr(0xe0 | ($ch >> 12)) .
|
|
chr(0x80 | (($ch >> 6) & 0x3f)) .
|
|
chr(0x80 | ($ch & 0x3f));
|
|
}
|
|
|
|
$ch = ($b << (16 + $k)) & 0xffff;
|
|
$k += 10;
|
|
}
|
|
}
|
|
|
|
/* Non-zero or too many extra bits -OR-
|
|
* Base64 not properly terminated -OR-
|
|
* Adjacent Base64 sections. */
|
|
if (($ch || ($k < 6)) ||
|
|
(!$u7len || $u7 !== '-') ||
|
|
(($u7len > 2) &&
|
|
($str[$i + 1] === '&') &&
|
|
($str[$i + 2] !== '-'))) {
|
|
throw new Horde_Imap_Client_Exception(
|
|
Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
|
|
Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
|
|
);
|
|
}
|
|
} elseif ((ord($u7) < 0x20) || (ord($u7) >= 0x7f)) {
|
|
/* Not printable US-ASCII */
|
|
throw new Horde_Imap_Client_Exception(
|
|
Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
|
|
Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
|
|
);
|
|
} else {
|
|
$p .= $u7;
|
|
}
|
|
}
|
|
|
|
return $p;
|
|
}
|
|
|
|
/**
|
|
* Convert a string from UTF-8 to UTF7-IMAP.
|
|
*
|
|
* @param string $str The UTF-8 string.
|
|
* @param boolean $force Assume $str is UTF-8 (no-autodetection)? If
|
|
* false, attempts to auto-detect if string is
|
|
* already in UTF7-IMAP.
|
|
*
|
|
* @return string The converted UTF7-IMAP string.
|
|
* @throws Horde_Imap_Client_Exception
|
|
*/
|
|
public static function Utf8ToUtf7Imap($str, $force = true)
|
|
{
|
|
if ($str instanceof Horde_Imap_Client_Mailbox) {
|
|
return $str->utf7imap;
|
|
}
|
|
|
|
$str = strval($str);
|
|
|
|
/* No need to do conversion if all chars are in US-ASCII range or if
|
|
* no ampersand is present. But will assume that an already encoded
|
|
* ampersand means string is in UTF7-IMAP already. */
|
|
if (!$force &&
|
|
!preg_match('/[\x80-\xff]|&$|&(?![,+A-Za-z0-9]*-)/', $str)) {
|
|
return $str;
|
|
}
|
|
|
|
/* Try mbstring, if available, which should be faster. Don't use the
|
|
* IMAP utf7_* functions because they are known to be buggy. */
|
|
if (is_null(self::$_mbstring)) {
|
|
self::$_mbstring = extension_loaded('mbstring');
|
|
}
|
|
if (self::$_mbstring) {
|
|
return @mb_convert_encoding($str, 'UTF7-IMAP', 'UTF-8');
|
|
}
|
|
|
|
$u8len = strlen($str);
|
|
$i = 0;
|
|
$base64 = false;
|
|
$p = '';
|
|
$ptr = &self::$_base64;
|
|
|
|
while ($u8len) {
|
|
$u8 = $str[$i];
|
|
$c = ord($u8);
|
|
|
|
if ($c < 0x80) {
|
|
$ch = $c;
|
|
$n = 0;
|
|
} elseif ($c < 0xc2) {
|
|
throw new Horde_Imap_Client_Exception(
|
|
Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
|
|
Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
|
|
);
|
|
} elseif ($c < 0xe0) {
|
|
$ch = $c & 0x1f;
|
|
$n = 1;
|
|
} elseif ($c < 0xf0) {
|
|
$ch = $c & 0x0f;
|
|
$n = 2;
|
|
} elseif ($c < 0xf8) {
|
|
$ch = $c & 0x07;
|
|
$n = 3;
|
|
} elseif ($c < 0xfc) {
|
|
$ch = $c & 0x03;
|
|
$n = 4;
|
|
} elseif ($c < 0xfe) {
|
|
$ch = $c & 0x01;
|
|
$n = 5;
|
|
} else {
|
|
throw new Horde_Imap_Client_Exception(
|
|
Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
|
|
Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
|
|
);
|
|
}
|
|
|
|
if ($n > --$u8len) {
|
|
throw new Horde_Imap_Client_Exception(
|
|
Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
|
|
Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
|
|
);
|
|
}
|
|
|
|
++$i;
|
|
|
|
for ($j = 0; $j < $n; ++$j) {
|
|
$o = ord($str[$i + $j]);
|
|
if (($o & 0xc0) !== 0x80) {
|
|
throw new Horde_Imap_Client_Exception(
|
|
Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
|
|
Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
|
|
);
|
|
}
|
|
$ch = ($ch << 6) | ($o & 0x3f);
|
|
}
|
|
|
|
if (($n > 1) && !($ch >> ($n * 5 + 1))) {
|
|
throw new Horde_Imap_Client_Exception(
|
|
Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
|
|
Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
|
|
);
|
|
}
|
|
|
|
$i += $n;
|
|
$u8len -= $n;
|
|
|
|
if (($ch < 0x20) || ($ch >= 0x7f)) {
|
|
if (!$base64) {
|
|
$p .= '&';
|
|
$base64 = true;
|
|
$b = 0;
|
|
$k = 10;
|
|
}
|
|
|
|
if ($ch & ~0xffff) {
|
|
$ch = 0xfffe;
|
|
}
|
|
|
|
$p .= $ptr[($b | $ch >> $k)];
|
|
$k -= 6;
|
|
for (; $k >= 0; $k -= 6) {
|
|
$p .= $ptr[(($ch >> $k) & 0x3f)];
|
|
}
|
|
|
|
$b = ($ch << (-$k)) & 0x3f;
|
|
$k += 16;
|
|
} else {
|
|
if ($base64) {
|
|
if ($k > 10) {
|
|
$p .= $ptr[$b];
|
|
}
|
|
$p .= '-';
|
|
$base64 = false;
|
|
}
|
|
|
|
$p .= chr($ch);
|
|
if (chr($ch) === '&') {
|
|
$p .= '-';
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($base64) {
|
|
if ($k > 10) {
|
|
$p .= $ptr[$b];
|
|
}
|
|
$p .= '-';
|
|
}
|
|
|
|
return $p;
|
|
}
|
|
|
|
}
|