PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.0.3
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.0.3
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / back / Libraries / Imap2 / Roundcube / Charset.php

Charset.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 11.0.3, at back/Libraries/Imap2/Roundcube/Charset.php

1,219 lines 36.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * +-----------------------------------------------------------------------+
5 * | This file is part of the Roundcube Webmail client |
6 * | |
7 * | Copyright (C) The Roundcube Dev Team |
8 * | Copyright (C) Kolab Systems AG |
9 * | Copyright (C) 2000 Edmund Grimley Evans <[email protected]> |
10 * | |
11 * | Licensed under the GNU General Public License version 3 or |
12 * | any later version with exceptions for skins & plugins. |
13 * | See the README file for a full license statement. |
14 * | |
15 * | PURPOSE: |
16 * | Provide charset conversion functionality |
17 * +-----------------------------------------------------------------------+
18 * | Author: Thomas Bruederli <[email protected]> |
19 * | Author: Aleksander Machniak <[email protected]> |
20 * | Author: Edmund Grimley Evans <[email protected]> |
21 * +-----------------------------------------------------------------------+
22 */
23
24 namespace AcyMailing\Libraries\Imap2\Roundcube;
25
26 /**
27 * Character sets conversion functionality
28 *
29 * @package Framework
30 * @subpackage Core
31 */
32 class Charset
33 {
34 // Aliases: some of them from HTML5 spec.
35 static public $aliases = [
36 'USASCII' => 'WINDOWS-1252',
37 'ANSIX31101983' => 'WINDOWS-1252',
38 'ANSIX341968' => 'WINDOWS-1252',
39 'UNKNOWN8BIT' => 'ISO-8859-15',
40 'UNKNOWN' => 'ISO-8859-15',
41 'USERDEFINED' => 'ISO-8859-15',
42 'KSC56011987' => 'EUC-KR',
43 'GB2312' => 'GBK',
44 'GB231280' => 'GBK',
45 'UNICODE' => 'UTF-8',
46 'UTF7IMAP' => 'UTF7-IMAP',
47 'TIS620' => 'WINDOWS-874',
48 'ISO88599' => 'WINDOWS-1254',
49 'ISO885911' => 'WINDOWS-874',
50 'MACROMAN' => 'MACINTOSH',
51 '77' => 'MAC',
52 '128' => 'SHIFT-JIS',
53 '129' => 'CP949',
54 '130' => 'CP1361',
55 '134' => 'GBK',
56 '136' => 'BIG5',
57 '161' => 'WINDOWS-1253',
58 '162' => 'WINDOWS-1254',
59 '163' => 'WINDOWS-1258',
60 '177' => 'WINDOWS-1255',
61 '178' => 'WINDOWS-1256',
62 '186' => 'WINDOWS-1257',
63 '204' => 'WINDOWS-1251',
64 '222' => 'WINDOWS-874',
65 '238' => 'WINDOWS-1250',
66 'MS950' => 'CP950',
67 'WINDOWS949' => 'UHC',
68 ];
69
70 /**
71 * Windows codepages
72 *
73 * @var array
74 */
75 static public $windows_codepages = [
76 37 => 'IBM037', // IBM EBCDIC US-Canada
77 437 => 'IBM437', // OEM United States
78 500 => 'IBM500', // IBM EBCDIC International
79 708 => 'ASMO-708', // Arabic (ASMO 708)
80 720 => 'DOS-720', // Arabic (Transparent ASMO); Arabic (DOS)
81 737 => 'IBM737', // OEM Greek (formerly 437G); Greek (DOS)
82 775 => 'IBM775', // OEM Baltic; Baltic (DOS)
83 850 => 'IBM850', // OEM Multilingual Latin 1; Western European (DOS)
84 852 => 'IBM852', // OEM Latin 2; Central European (DOS)
85 855 => 'IBM855', // OEM Cyrillic (primarily Russian)
86 857 => 'IBM857', // OEM Turkish; Turkish (DOS)
87 858 => 'IBM00858', // OEM Multilingual Latin 1 + Euro symbol
88 860 => 'IBM860', // OEM Portuguese; Portuguese (DOS)
89 861 => 'IBM861', // OEM Icelandic; Icelandic (DOS)
90 862 => 'DOS-862', // OEM Hebrew; Hebrew (DOS)
91 863 => 'IBM863', // OEM French Canadian; French Canadian (DOS)
92 864 => 'IBM864', // OEM Arabic; Arabic (864)
93 865 => 'IBM865', // OEM Nordic; Nordic (DOS)
94 866 => 'cp866', // OEM Russian; Cyrillic (DOS)
95 869 => 'IBM869', // OEM Modern Greek; Greek, Modern (DOS)
96 870 => 'IBM870', // IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2
97 874 => 'windows-874', // ANSI/OEM Thai (ISO 8859-11); Thai (Windows)
98 875 => 'cp875', // IBM EBCDIC Greek Modern
99 932 => 'shift_jis', // ANSI/OEM Japanese; Japanese (Shift-JIS)
100 936 => 'gb2312', // ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)
101 950 => 'big5', // ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)
102 1026 => 'IBM1026', // IBM EBCDIC Turkish (Latin 5)
103 1047 => 'IBM01047', // IBM EBCDIC Latin 1/Open System
104 1140 => 'IBM01140', // IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)
105 1141 => 'IBM01141', // IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)
106 1142 => 'IBM01142', // IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)
107 1143 => 'IBM01143', // IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)
108 1144 => 'IBM01144', // IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)
109 1145 => 'IBM01145', // IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)
110 1146 => 'IBM01146', // IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)
111 1147 => 'IBM01147', // IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)
112 1148 => 'IBM01148', // IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)
113 1149 => 'IBM01149', // IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)
114 1200 => 'UTF-16', // Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications
115 1201 => 'UTF-16BE', // Unicode UTF-16, big endian byte order; available only to managed applications
116 1250 => 'windows-1250', // ANSI Central European; Central European (Windows)
117 1251 => 'windows-1251', // ANSI Cyrillic; Cyrillic (Windows)
118 1252 => 'windows-1252', // ANSI Latin 1; Western European (Windows)
119 1253 => 'windows-1253', // ANSI Greek; Greek (Windows)
120 1254 => 'windows-1254', // ANSI Turkish; Turkish (Windows)
121 1255 => 'windows-1255', // ANSI Hebrew; Hebrew (Windows)
122 1256 => 'windows-1256', // ANSI Arabic; Arabic (Windows)
123 1257 => 'windows-1257', // ANSI Baltic; Baltic (Windows)
124 1258 => 'windows-1258', // ANSI/OEM Vietnamese; Vietnamese (Windows)
125 10000 => 'macintosh', // MAC Roman; Western European (Mac)
126 12000 => 'UTF-32', // Unicode UTF-32, little endian byte order; available only to managed applications
127 12001 => 'UTF-32BE', // Unicode UTF-32, big endian byte order; available only to managed applications
128 20127 => 'US-ASCII', // US-ASCII (7-bit)
129 20273 => 'IBM273', // IBM EBCDIC Germany
130 20277 => 'IBM277', // IBM EBCDIC Denmark-Norway
131 20278 => 'IBM278', // IBM EBCDIC Finland-Sweden
132 20280 => 'IBM280', // IBM EBCDIC Italy
133 20284 => 'IBM284', // IBM EBCDIC Latin America-Spain
134 20285 => 'IBM285', // IBM EBCDIC United Kingdom
135 20290 => 'IBM290', // IBM EBCDIC Japanese Katakana Extended
136 20297 => 'IBM297', // IBM EBCDIC France
137 20420 => 'IBM420', // IBM EBCDIC Arabic
138 20423 => 'IBM423', // IBM EBCDIC Greek
139 20424 => 'IBM424', // IBM EBCDIC Hebrew
140 20838 => 'IBM-Thai', // IBM EBCDIC Thai
141 20866 => 'koi8-r', // Russian (KOI8-R); Cyrillic (KOI8-R)
142 20871 => 'IBM871', // IBM EBCDIC Icelandic
143 20880 => 'IBM880', // IBM EBCDIC Cyrillic Russian
144 20905 => 'IBM905', // IBM EBCDIC Turkish
145 20924 => 'IBM00924', // IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
146 20932 => 'EUC-JP', // Japanese (JIS 0208-1990 and 0212-1990)
147 20936 => 'cp20936', // Simplified Chinese (GB2312); Chinese Simplified (GB2312-80)
148 20949 => 'cp20949', // Korean Wansung
149 21025 => 'cp1025', // IBM EBCDIC Cyrillic Serbian-Bulgarian
150 21866 => 'koi8-u', // Ukrainian (KOI8-U); Cyrillic (KOI8-U)
151 28591 => 'iso-8859-1', // ISO 8859-1 Latin 1; Western European (ISO)
152 28592 => 'iso-8859-2', // ISO 8859-2 Central European; Central European (ISO)
153 28593 => 'iso-8859-3', // ISO 8859-3 Latin 3
154 28594 => 'iso-8859-4', // ISO 8859-4 Baltic
155 28595 => 'iso-8859-5', // ISO 8859-5 Cyrillic
156 28596 => 'iso-8859-6', // ISO 8859-6 Arabic
157 28597 => 'iso-8859-7', // ISO 8859-7 Greek
158 28598 => 'iso-8859-8', // ISO 8859-8 Hebrew; Hebrew (ISO-Visual)
159 28599 => 'iso-8859-9', // ISO 8859-9 Turkish
160 28603 => 'iso-8859-13', // ISO 8859-13 Estonian
161 28605 => 'iso-8859-15', // ISO 8859-15 Latin 9
162 38598 => 'iso-8859-8-i', // ISO 8859-8 Hebrew; Hebrew (ISO-Logical)
163 50220 => 'iso-2022-jp', // ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)
164 50221 => 'csISO2022JP', // ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)
165 50222 => 'iso-2022-jp', // ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)
166 50225 => 'iso-2022-kr', // ISO 2022 Korean
167 51932 => 'EUC-JP', // EUC Japanese
168 51936 => 'EUC-CN', // EUC Simplified Chinese; Chinese Simplified (EUC)
169 51949 => 'EUC-KR', // EUC Korean
170 52936 => 'hz-gb-2312', // HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)
171 54936 => 'GB18030', // Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)
172 65000 => 'UTF-7',
173 65001 => 'UTF-8',
174 ];
175
176 /**
177 * Catch an error and throw an exception.
178 *
179 * @param int $errno Level of the error
180 * @param string $errstr Error message
181 */
182 public static function error_handler($errno, $errstr)
183 {
184 throw new \Exception($errstr, 0, $errno);
185 }
186
187 /**
188 * Parse and validate charset name string.
189 * Sometimes charset string is malformed, there are also charset aliases,
190 * but we need strict names for charset conversion (specially utf8 class)
191 *
192 * @param string $input Input charset name
193 *
194 * @return string The validated charset name
195 */
196 public static function parse_charset($input)
197 {
198 static $charsets = [];
199 $charset = strtoupper($input);
200
201 if (isset($charsets[$input])) {
202 return $charsets[$input];
203 }
204
205 $charset = preg_replace([
206 '/^[^0-9A-Z]+/', // e.g. _ISO-8859-JP$SIO
207 '/\$.*$/', // e.g. _ISO-8859-JP$SIO
208 '/UNICODE-1-1-*/', // RFC1641/1642
209 '/^X-/', // X- prefix (e.g. X-ROMAN8 => ROMAN8)
210 '/\*.*$/', // lang code according to RFC 2231.5
211 ], '', $charset);
212
213 if ($charset == 'BINARY') {
214 return $charsets[$input] = null;
215 }
216
217 // allow A-Z and 0-9 only
218 $str = preg_replace('/[^A-Z0-9]/', '', $charset);
219
220 if (isset(self::$aliases[$str])) {
221 $result = self::$aliases[$str];
222 } // UTF
223 elseif (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m)) {
224 $result = 'UTF-'.$m[1].@$m[2];
225 } // ISO-8859
226 elseif (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
227 $iso = 'ISO-8859-'.($m[1] ? : 1);
228 // some clients sends windows-1252 text as latin1,
229 // it is safe to use windows-1252 for all latin1
230 $result = $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
231 } // handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
232 elseif (preg_match('/(WIN|WINDOWS)([0-9]+)/', $str, $m)) {
233 $result = 'WINDOWS-'.$m[2];
234 } // LATIN
235 elseif (preg_match('/LATIN(.*)/', $str, $m)) {
236 $aliases = [
237 '2' => 2,
238 '3' => 3,
239 '4' => 4,
240 '5' => 9,
241 '6' => 10,
242 '7' => 13,
243 '8' => 14,
244 '9' => 15,
245 '10' => 16,
246 'ARABIC' => 6,
247 'CYRILLIC' => 5,
248 'GREEK' => 7,
249 'GREEK1' => 7,
250 'HEBREW' => 8,
251 ];
252
253 // some clients sends windows-1252 text as latin1,
254 // it is safe to use windows-1252 for all latin1
255 if ($m[1] == 1) {
256 $result = 'WINDOWS-1252';
257 } // if iconv is not supported we need ISO labels, it's also safe for iconv
258 elseif (!empty($aliases[$m[1]])) {
259 $result = 'ISO-8859-'.$aliases[$m[1]];
260 } // iconv requires conversion of e.g. LATIN-1 to LATIN1
261 else {
262 $result = $str;
263 }
264 } else {
265 $result = $charset;
266 }
267
268 $charsets[$input] = $result;
269
270 return $result;
271 }
272
273 /**
274 * Convert a string from one charset to another.
275 * Uses mbstring and iconv functions if possible
276 *
277 * @param string $str Input string
278 * @param string $from Suspected charset of the input string
279 * @param string $to Target charset to convert to; defaults to RCUBE_CHARSET
280 *
281 * @return string Converted string
282 */
283 public static function convert($str, $from, $to = null)
284 {
285 static $iconv_options = null;
286 static $mbstring_sc = null;
287
288 $to = empty($to) ? IMAP2_CHARSET : strtoupper($to);
289 $from = self::parse_charset($from);
290
291 // It is a common case when UTF-16 charset is used with US-ASCII content (#1488654)
292 // In that case we can just skip the conversion (use UTF-8)
293 if ($from == 'UTF-16' && !preg_match('/[^\x00-\x7F]/', $str)) {
294 $from = 'UTF-8';
295 }
296
297 if ($from == $to || empty($str) || empty($from)) {
298 return $str;
299 }
300
301 if ($iconv_options === null) {
302 if (function_exists('iconv')) {
303 // ignore characters not available in output charset
304 $iconv_options = '//IGNORE';
305 if (iconv('', $iconv_options, '') === false) {
306 // iconv implementation does not support options
307 $iconv_options = '';
308 }
309 } else {
310 $iconv_options = false;
311 }
312 }
313
314 // convert charset using iconv module
315 if ($iconv_options !== false && $from != 'UTF7-IMAP' && $to != 'UTF7-IMAP'
316 && $from !== 'ISO-2022-JP'
317 ) {
318 // throw an exception if iconv reports an illegal character in input
319 // it means that input string has been truncated
320 #set_error_handler(array('rcube_charset', 'error_handler'), E_NOTICE);
321 try {
322 $out = iconv($from, $to.$iconv_options, $str);
323 } catch (\Exception $e) {
324 $out = false;
325 }
326 #restore_error_handler();
327
328 if ($out !== false) {
329 return $out;
330 }
331 }
332
333 if ($mbstring_sc === null) {
334 $mbstring_sc = extension_loaded('mbstring') ? mb_substitute_character() : false;
335 }
336
337 // convert charset using mbstring module
338 if ($mbstring_sc !== false) {
339 $aliases = [
340 'WINDOWS-1257' => 'ISO-8859-13',
341 'US-ASCII' => 'ASCII',
342 'ISO-2022-JP' => 'ISO-2022-JP-MS',
343 ];
344
345 $mb_from = $aliases[$from] ? : $from;
346 $mb_to = $aliases[$to] ? : $to;
347
348 // Do the same as //IGNORE with iconv
349 mb_substitute_character('none');
350
351 // throw an exception if mbstring reports an illegal character in input
352 // using mb_check_encoding() is much slower
353 #set_error_handler(array('rcube_charset', 'error_handler'), E_WARNING);
354 try {
355 $out = mb_convert_encoding($str, $mb_to, $mb_from);
356 } catch (\Exception $e) {
357 $out = false;
358 }
359 #restore_error_handler();
360
361 mb_substitute_character($mbstring_sc);
362
363 if ($out !== false) {
364 return $out;
365 }
366 }
367
368 // convert charset using bundled classes/functions
369 if ($to == 'UTF-8') {
370 if ($from == 'UTF7-IMAP') {
371 if ($out = self::utf7imap_to_utf8($str)) {
372 return $out;
373 }
374 } elseif ($from == 'UTF-7') {
375 if ($out = self::utf7_to_utf8($str)) {
376 return $out;
377 }
378 }
379 }
380
381 // encode string for output
382 if ($from == 'UTF-8') {
383 // @TODO: we need a function for UTF-7 (RFC2152) conversion
384 if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
385 if ($out = self::utf8_to_utf7imap($str)) {
386 return $out;
387 }
388 }
389 }
390
391 if (!isset($out)) {
392 trigger_error("No suitable function found for '$from' to '$to' conversion");
393 }
394
395 // return original string
396 return $str;
397 }
398
399 /**
400 * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
401 *
402 * @param string $str Input string (UTF-7)
403 *
404 * @return string Converted string (UTF-8)
405 */
406 public static function utf7_to_utf8($str)
407 {
408 $Index_64 = [
409 0,
410 0,
411 0,
412 0,
413 0,
414 0,
415 0,
416 0,
417 0,
418 0,
419 0,
420 0,
421 0,
422 0,
423 0,
424 0,
425 0,
426 0,
427 0,
428 0,
429 0,
430 0,
431 0,
432 0,
433 0,
434 0,
435 0,
436 0,
437 0,
438 0,
439 0,
440 0,
441 0,
442 0,
443 0,
444 0,
445 0,
446 0,
447 0,
448 0,
449 0,
450 0,
451 0,
452 1,
453 0,
454 0,
455 0,
456 0,
457 1,
458 1,
459 1,
460 1,
461 1,
462 1,
463 1,
464 1,
465 1,
466 1,
467 0,
468 0,
469 0,
470 0,
471 0,
472 0,
473 0,
474 1,
475 1,
476 1,
477 1,
478 1,
479 1,
480 1,
481 1,
482 1,
483 1,
484 1,
485 1,
486 1,
487 1,
488 1,
489 1,
490 1,
491 1,
492 1,
493 1,
494 1,
495 1,
496 1,
497 1,
498 1,
499 1,
500 0,
501 0,
502 0,
503 0,
504 0,
505 0,
506 1,
507 1,
508 1,
509 1,
510 1,
511 1,
512 1,
513 1,
514 1,
515 1,
516 1,
517 1,
518 1,
519 1,
520 1,
521 1,
522 1,
523 1,
524 1,
525 1,
526 1,
527 1,
528 1,
529 1,
530 1,
531 1,
532 0,
533 0,
534 0,
535 0,
536 0,
537 ];
538
539 $u7len = strlen($str);
540 $str = strval($str);
541 $res = '';
542
543 for ($i = 0 ; $u7len > 0 ; $i++, $u7len--) {
544 $u7 = $str[$i];
545 if ($u7 == '+') {
546 $i++;
547 $u7len--;
548 $ch = '';
549
550 for (; $u7len > 0 ; $i++, $u7len--) {
551 $u7 = $str[$i];
552
553 if (!$Index_64[ord($u7)]) {
554 break;
555 }
556
557 $ch .= $u7;
558 }
559
560 if ($ch == '') {
561 if ($u7 == '-') {
562 $res .= '+';
563 }
564
565 continue;
566 }
567
568 $res .= self::utf16_to_utf8(base64_decode($ch));
569 } else {
570 $res .= $u7;
571 }
572 }
573
574 return $res;
575 }
576
577 /**
578 * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
579 *
580 * @param string $str Input string
581 *
582 * @return string The converted string
583 */
584 public static function utf16_to_utf8($str)
585 {
586 $len = strlen($str);
587 $dec = '';
588
589 for ($i = 0 ; $i < $len ; $i += 2) {
590 $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
591 if ($c >= 0x0001 && $c <= 0x007F) {
592 $dec .= chr($c);
593 } elseif ($c > 0x07FF) {
594 $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
595 $dec .= chr(0x80 | (($c >> 6) & 0x3F));
596 $dec .= chr(0x80 | (($c >> 0) & 0x3F));
597 } else {
598 $dec .= chr(0xC0 | (($c >> 6) & 0x1F));
599 $dec .= chr(0x80 | (($c >> 0) & 0x3F));
600 }
601 }
602
603 return $dec;
604 }
605
606 /**
607 * Convert the data ($str) from RFC 2060's UTF-7 to UTF-8.
608 * If input data is invalid, return the original input string.
609 * RFC 2060 obviously intends the encoding to be unique (see
610 * point 5 in section 5.1.3), so we reject any non-canonical
611 * form, such as &ACY- (instead of &-) or &AMA-&AMA- (instead
612 * of &AMAAwA-).
613 *
614 * Translated from C to PHP by Thomas Bruederli <[email protected]>
615 *
616 * @param string $str Input string (UTF7-IMAP)
617 *
618 * @return string Output string (UTF-8)
619 */
620 public static function utf7imap_to_utf8($str)
621 {
622 $Index_64 = [
623 -1,
624 -1,
625 -1,
626 -1,
627 -1,
628 -1,
629 -1,
630 -1,
631 -1,
632 -1,
633 -1,
634 -1,
635 -1,
636 -1,
637 -1,
638 -1,
639 -1,
640 -1,
641 -1,
642 -1,
643 -1,
644 -1,
645 -1,
646 -1,
647 -1,
648 -1,
649 -1,
650 -1,
651 -1,
652 -1,
653 -1,
654 -1,
655 -1,
656 -1,
657 -1,
658 -1,
659 -1,
660 -1,
661 -1,
662 -1,
663 -1,
664 -1,
665 -1,
666 62,
667 63,
668 -1,
669 -1,
670 -1,
671 52,
672 53,
673 54,
674 55,
675 56,
676 57,
677 58,
678 59,
679 60,
680 61,
681 -1,
682 -1,
683 -1,
684 -1,
685 -1,
686 -1,
687 -1,
688 0,
689 1,
690 2,
691 3,
692 4,
693 5,
694 6,
695 7,
696 8,
697 9,
698 10,
699 11,
700 12,
701 13,
702 14,
703 15,
704 16,
705 17,
706 18,
707 19,
708 20,
709 21,
710 22,
711 23,
712 24,
713 25,
714 -1,
715 -1,
716 -1,
717 -1,
718 -1,
719 -1,
720 26,
721 27,
722 28,
723 29,
724 30,
725 31,
726 32,
727 33,
728 34,
729 35,
730 36,
731 37,
732 38,
733 39,
734 40,
735 41,
736 42,
737 43,
738 44,
739 45,
740 46,
741 47,
742 48,
743 49,
744 50,
745 51,
746 -1,
747 -1,
748 -1,
749 -1,
750 -1,
751 ];
752
753 $u7len = strlen($str);
754 $str = strval($str);
755 $p = '';
756 $err = '';
757
758 for ($i = 0 ; $u7len > 0 ; $i++, $u7len--) {
759 $u7 = $str[$i];
760 if ($u7 == '&') {
761 $i++;
762 $u7len--;
763 $u7 = $str[$i];
764
765 if ($u7len && $u7 == '-') {
766 $p .= '&';
767 continue;
768 }
769
770 $ch = 0;
771 $k = 10;
772 for (; $u7len > 0 ; $i++, $u7len--) {
773 $u7 = $str[$i];
774
775 if ((ord($u7) & 0x80) || ($b = $Index_64[ord($u7)]) == -1) {
776 break;
777 }
778
779 if ($k > 0) {
780 $ch |= $b << $k;
781 $k -= 6;
782 } else {
783 $ch |= $b >> (-$k);
784 if ($ch < 0x80) {
785 // Printable US-ASCII
786 if (0x20 <= $ch && $ch < 0x7f) {
787 return $err;
788 }
789 $p .= chr($ch);
790 } elseif ($ch < 0x800) {
791 $p .= chr(0xc0 | ($ch >> 6));
792 $p .= chr(0x80 | ($ch & 0x3f));
793 } else {
794 $p .= chr(0xe0 | ($ch >> 12));
795 $p .= chr(0x80 | (($ch >> 6) & 0x3f));
796 $p .= chr(0x80 | ($ch & 0x3f));
797 }
798
799 $ch = ($b << (16 + $k)) & 0xffff;
800 $k += 10;
801 }
802 }
803
804 // Non-zero or too many extra bits
805 if ($ch || $k < 6) {
806 return $err;
807 }
808
809 // BASE64 not properly terminated
810 if (!$u7len || $u7 != '-') {
811 return $err;
812 }
813
814 // Adjacent BASE64 sections
815 if ($u7len > 2 && $str[$i + 1] == '&' && $str[$i + 2] != '-') {
816 return $err;
817 }
818 } // Not printable US-ASCII
819 elseif (ord($u7) < 0x20 || ord($u7) >= 0x7f) {
820 return $err;
821 } else {
822 $p .= $u7;
823 }
824 }
825
826 return $p;
827 }
828
829 /**
830 * Convert the data ($str) from UTF-8 to RFC 2060's UTF-7.
831 * Unicode characters above U+FFFF are replaced by U+FFFE.
832 * If input data is invalid, return an empty string.
833 *
834 * Translated from C to PHP by Thomas Bruederli <[email protected]>
835 *
836 * @param string $str Input string (UTF-8)
837 *
838 * @return string Output string (UTF7-IMAP)
839 */
840 public static function utf8_to_utf7imap($str)
841 {
842 $B64Chars = [
843 'A',
844 'B',
845 'C',
846 'D',
847 'E',
848 'F',
849 'G',
850 'H',
851 'I',
852 'J',
853 'K',
854 'L',
855 'M',
856 'N',
857 'O',
858 'P',
859 'Q',
860 'R',
861 'S',
862 'T',
863 'U',
864 'V',
865 'W',
866 'X',
867 'Y',
868 'Z',
869 'a',
870 'b',
871 'c',
872 'd',
873 'e',
874 'f',
875 'g',
876 'h',
877 'i',
878 'j',
879 'k',
880 'l',
881 'm',
882 'n',
883 'o',
884 'p',
885 'q',
886 'r',
887 's',
888 't',
889 'u',
890 'v',
891 'w',
892 'x',
893 'y',
894 'z',
895 '0',
896 '1',
897 '2',
898 '3',
899 '4',
900 '5',
901 '6',
902 '7',
903 '8',
904 '9',
905 '+',
906 ',',
907 ];
908
909 $u8len = strlen($str);
910 $base64 = 0;
911 $i = 0;
912 $p = '';
913 $err = '';
914
915 while ($u8len) {
916 $u8 = $str[$i];
917 $c = ord($u8);
918
919 if ($c < 0x80) {
920 $ch = $c;
921 $n = 0;
922 } elseif ($c < 0xc2) {
923 return $err;
924 } elseif ($c < 0xe0) {
925 $ch = $c & 0x1f;
926 $n = 1;
927 } elseif ($c < 0xf0) {
928 $ch = $c & 0x0f;
929 $n = 2;
930 } elseif ($c < 0xf8) {
931 $ch = $c & 0x07;
932 $n = 3;
933 } elseif ($c < 0xfc) {
934 $ch = $c & 0x03;
935 $n = 4;
936 } elseif ($c < 0xfe) {
937 $ch = $c & 0x01;
938 $n = 5;
939 } else {
940 return $err;
941 }
942
943 $i++;
944 $u8len--;
945
946 if ($n > $u8len) {
947 return $err;
948 }
949
950 for ($j = 0 ; $j < $n ; $j++) {
951 $o = ord($str[$i + $j]);
952 if (($o & 0xc0) != 0x80) {
953 return $err;
954 }
955 $ch = ($ch << 6) | ($o & 0x3f);
956 }
957
958 if ($n > 1 && !($ch >> ($n * 5 + 1))) {
959 return $err;
960 }
961
962 $i += $n;
963 $u8len -= $n;
964
965 if ($ch < 0x20 || $ch >= 0x7f) {
966 if (!$base64) {
967 $p .= '&';
968 $base64 = 1;
969 $b = 0;
970 $k = 10;
971 }
972 if ($ch & ~0xffff) {
973 $ch = 0xfffe;
974 }
975
976 $p .= $B64Chars[($b | $ch >> $k)];
977 $k -= 6;
978 for (; $k >= 0 ; $k -= 6) {
979 $p .= $B64Chars[(($ch >> $k) & 0x3f)];
980 }
981
982 $b = ($ch << (-$k)) & 0x3f;
983 $k += 16;
984 } else {
985 if ($base64) {
986 if ($k > 10) {
987 $p .= $B64Chars[$b];
988 }
989 $p .= '-';
990 $base64 = 0;
991 }
992
993 $p .= chr($ch);
994 if (chr($ch) == '&') {
995 $p .= '-';
996 }
997 }
998 }
999
1000 if ($base64) {
1001 if ($k > 10) {
1002 $p .= $B64Chars[$b];
1003 }
1004 $p .= '-';
1005 }
1006
1007 return $p;
1008 }
1009
1010 /**
1011 * A method to guess character set of a string.
1012 *
1013 * @param string $string String
1014 * @param string $failover Default result for failover
1015 * @param string $language User language
1016 *
1017 * @return string Charset name
1018 */
1019 public static function detect($string, $failover = null, $language = null)
1020 {
1021 if (substr($string, 0, 4) == "\0\0\xFE\xFF") return 'UTF-32BE'; // Big Endian
1022 if (substr($string, 0, 4) == "\xFF\xFE\0\0") return 'UTF-32LE'; // Little Endian
1023 if (substr($string, 0, 2) == "\xFE\xFF") return 'UTF-16BE'; // Big Endian
1024 if (substr($string, 0, 2) == "\xFF\xFE") return 'UTF-16LE'; // Little Endian
1025 if (substr($string, 0, 3) == "\xEF\xBB\xBF") return 'UTF-8';
1026
1027 // heuristics
1028 if ($string[0] == "\0" && $string[1] == "\0" && $string[2] == "\0" && $string[3] != "\0") return 'UTF-32BE';
1029 if ($string[0] != "\0" && $string[1] == "\0" && $string[2] == "\0" && $string[3] == "\0") return 'UTF-32LE';
1030 if ($string[0] == "\0" && $string[1] != "\0" && $string[2] == "\0" && $string[3] != "\0") return 'UTF-16BE';
1031 if ($string[0] != "\0" && $string[1] == "\0" && $string[2] != "\0" && $string[3] == "\0") return 'UTF-16LE';
1032
1033 if (empty($language)) {
1034 $rcube = rcube::get_instance();
1035 $language = $rcube->get_user_language();
1036 }
1037
1038 // Prioritize charsets according to current language (#1485669)
1039 switch ($language) {
1040 case 'ja_JP':
1041 $prio = ['ISO-2022-JP', 'JIS', 'UTF-8', 'EUC-JP', 'eucJP-win', 'SJIS', 'SJIS-win'];
1042 break;
1043
1044 case 'zh_CN':
1045 case 'zh_TW':
1046 $prio = ['UTF-8', 'BIG-5', 'GB2312', 'EUC-TW'];
1047 break;
1048
1049 case 'ko_KR':
1050 $prio = ['UTF-8', 'EUC-KR', 'ISO-2022-KR'];
1051 break;
1052
1053 case 'ru_RU':
1054 $prio = ['UTF-8', 'WINDOWS-1251', 'KOI8-R'];
1055 break;
1056
1057 case 'tr_TR':
1058 $prio = ['UTF-8', 'ISO-8859-9', 'WINDOWS-1254'];
1059 break;
1060 }
1061
1062 // mb_detect_encoding() is not reliable for some charsets (#1490135)
1063 // use mb_check_encoding() to make charset priority lists really working
1064 if ($prio && function_exists('mb_check_encoding')) {
1065 foreach ($prio as $encoding) {
1066 if (mb_check_encoding($string, $encoding)) {
1067 return $encoding;
1068 }
1069 }
1070 }
1071
1072 if (function_exists('mb_detect_encoding')) {
1073 if (!$prio) {
1074 $prio = [
1075 'UTF-8',
1076 'SJIS',
1077 'GB2312',
1078 'ISO-8859-1',
1079 'ISO-8859-2',
1080 'ISO-8859-3',
1081 'ISO-8859-4',
1082 'ISO-8859-5',
1083 'ISO-8859-6',
1084 'ISO-8859-7',
1085 'ISO-8859-8',
1086 'ISO-8859-9',
1087 'ISO-8859-10',
1088 'ISO-8859-13',
1089 'ISO-8859-14',
1090 'ISO-8859-15',
1091 'ISO-8859-16',
1092 'WINDOWS-1252',
1093 'WINDOWS-1251',
1094 'EUC-JP',
1095 'EUC-TW',
1096 'KOI8-R',
1097 'BIG-5',
1098 'ISO-2022-KR',
1099 'ISO-2022-JP',
1100 ];
1101 }
1102
1103 $encodings = array_unique(array_merge($prio, mb_list_encodings()));
1104
1105 if ($encoding = mb_detect_encoding($string, $encodings)) {
1106 return $encoding;
1107 }
1108 }
1109
1110 // No match, check for UTF-8
1111 // from http://w3.org/International/questions/qa-forms-utf-8.html
1112 if (preg_match(
1113 '/\A(
1114 [\x09\x0A\x0D\x20-\x7E]
1115 | [\xC2-\xDF][\x80-\xBF]
1116 | \xE0[\xA0-\xBF][\x80-\xBF]
1117 | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
1118 | \xED[\x80-\x9F][\x80-\xBF]
1119 | \xF0[\x90-\xBF][\x80-\xBF]{2}
1120 | [\xF1-\xF3][\x80-\xBF]{3}
1121 | \xF4[\x80-\x8F][\x80-\xBF]{2}
1122 )*\z/xs',
1123 substr($string, 0, 2048)
1124 )
1125 ) {
1126 return 'UTF-8';
1127 }
1128
1129 return $failover;
1130 }
1131
1132 /**
1133 * Removes non-unicode characters from input.
1134 *
1135 * @param mixed $input String or array.
1136 *
1137 * @return mixed String or array
1138 */
1139 public static function clean($input)
1140 {
1141 // handle input of type array
1142 if (is_array($input)) {
1143 foreach ($input as $idx => $val) {
1144 $input[$idx] = self::clean($val);
1145 }
1146
1147 return $input;
1148 }
1149
1150 if (!is_string($input) || $input == '') {
1151 return $input;
1152 }
1153
1154 // iconv/mbstring are much faster (especially with long strings)
1155 if (function_exists('mb_convert_encoding')) {
1156 $msch = mb_substitute_character();
1157 mb_substitute_character('none');
1158 $res = mb_convert_encoding($input, 'UTF-8', 'UTF-8');
1159 mb_substitute_character($msch);
1160
1161 if ($res !== false) {
1162 return $res;
1163 }
1164 }
1165
1166 if (function_exists('iconv')) {
1167 if (($res = @iconv('UTF-8', 'UTF-8//IGNORE', $input)) !== false) {
1168 return $res;
1169 }
1170 }
1171
1172 $seq = '';
1173 $out = '';
1174 $regexp = '/^('.
1175 // '[\x00-\x7F]'. // UTF8-1
1176 '|[\xC2-\xDF][\x80-\xBF]'. // UTF8-2
1177 '|\xE0[\xA0-\xBF][\x80-\xBF]'. // UTF8-3
1178 '|[\xE1-\xEC][\x80-\xBF][\x80-\xBF]'. // UTF8-3
1179 '|\xED[\x80-\x9F][\x80-\xBF]'. // UTF8-3
1180 '|[\xEE-\xEF][\x80-\xBF][\x80-\xBF]'. // UTF8-3
1181 '|\xF0[\x90-\xBF][\x80-\xBF][\x80-\xBF]'. // UTF8-4
1182 '|[\xF1-\xF3][\x80-\xBF][\x80-\xBF][\x80-\xBF]'.// UTF8-4
1183 '|\xF4[\x80-\x8F][\x80-\xBF][\x80-\xBF]'. // UTF8-4
1184 ')$/';
1185
1186 for ($i = 0, $len = strlen($input) ; $i < $len ; $i++) {
1187 $chr = $input[$i];
1188 $ord = ord($chr);
1189
1190 // 1-byte character
1191 if ($ord <= 0x7F) {
1192 if ($seq !== '') {
1193 $out .= preg_match($regexp, $seq) ? $seq : '';
1194 $seq = '';
1195 }
1196
1197 $out .= $chr;
1198 } // first byte of multibyte sequence
1199 elseif ($ord >= 0xC0) {
1200 if ($seq !== '') {
1201 $out .= preg_match($regexp, $seq) ? $seq : '';
1202 $seq = '';
1203 }
1204
1205 $seq = $chr;
1206 } // next byte of multibyte sequence
1207 elseif ($seq !== '') {
1208 $seq .= $chr;
1209 }
1210 }
1211
1212 if ($seq !== '') {
1213 $out .= preg_match($regexp, $seq) ? $seq : '';
1214 }
1215
1216 return $out;
1217 }
1218 }
1219