PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.1
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
← All changes | back/Libraries/Imap2/Roundcube/Charset.php +0 -1218 trunk → 11.1.1 View file →
@@ -1,1218 +1,0 @@
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 -}