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/Mime.php +0 -877 10.2.2 → 11.1.1 View file →
@@ -1,877 +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 - * | |
10 - * | Licensed under the GNU General Public License version 3 or |
11 - * | any later version with exceptions for skins & plugins. |
12 - * | See the README file for a full license statement. |
13 - * | |
14 - * | PURPOSE: |
15 - * | MIME message parsing utilities |
16 - * +-----------------------------------------------------------------------+
17 - * | Author: Thomas Bruederli <[email protected]> |
18 - * | Author: Aleksander Machniak <[email protected]> |
19 - * +-----------------------------------------------------------------------+
20 - */
21 -
22 -namespace AcyMailing\Libraries\Imap2\Roundcube;
23 -
24 -use AcyMailing\Libraries\Imap2\rcube;
25 -use AcyMailing\Libraries\Imap2\rcube_mime_decode;
26 -
27 -/**
28 - * Class for parsing MIME messages
29 - *
30 - * @package Framework
31 - * @subpackage Storage
32 - */
33 -class Mime
34 -{
35 - private static $default_charset;
36 -
37 -
38 - /**
39 - * Object constructor.
40 - */
41 - function __construct($default_charset = null)
42 - {
43 - self::$default_charset = $default_charset;
44 - }
45 -
46 - /**
47 - * Returns message/object character set name
48 - *
49 - * @return string Character set name
50 - */
51 - public static function get_charset()
52 - {
53 - if (self::$default_charset) {
54 - return self::$default_charset;
55 - }
56 -
57 - if ($charset = IMAP2_CHARSET) {
58 - return $charset;
59 - }
60 -
61 - return IMAP2_CHARSET;
62 - }
63 -
64 - /**
65 - * Parse the given raw message source and return a structure
66 - * of rcube_message_part objects.
67 - *
68 - * It makes use of the rcube_mime_decode library
69 - *
70 - * @param string $raw_body The message source
71 - *
72 - * @return object rcube_message_part The message structure
73 - */
74 - public static function parse_message($raw_body)
75 - {
76 - $conf = [
77 - 'include_bodies' => true,
78 - 'decode_bodies' => true,
79 - 'decode_headers' => false,
80 - 'default_charset' => self::get_charset(),
81 - ];
82 -
83 - $mime = new rcube_mime_decode($conf);
84 -
85 - return $mime->decode($raw_body);
86 - }
87 -
88 - /**
89 - * Split an address list into a structured array list
90 - *
91 - * @param string|array $input Input string (or list of strings)
92 - * @param int $max List only this number of addresses
93 - * @param boolean $decode Decode address strings
94 - * @param string $fallback Fallback charset if none specified
95 - * @param boolean $addronly Return flat array with e-mail addresses only
96 - *
97 - * @return array Indexed list of addresses
98 - */
99 - static function decode_address_list($input, $max = null, $decode = true, $fallback = null, $addronly = false)
100 - {
101 - // A common case when the same header is used many times in a mail message
102 - if (is_array($input)) {
103 - $input = implode(', ', $input);
104 - }
105 -
106 - $a = self::parse_address_list($input, $decode, $fallback);
107 - $out = [];
108 - $j = 0;
109 -
110 - // Special chars as defined by RFC 822 need to in quoted string (or escaped).
111 - $special_chars = '[\(\)\<\>\\\.\[\]@,;:"]';
112 -
113 - if (!is_array($a)) {
114 - return $out;
115 - }
116 -
117 - foreach ($a as $val) {
118 - $j++;
119 - $address = trim($val['address']);
120 -
121 - if ($addronly) {
122 - $out[$j] = $address;
123 - } else {
124 - $name = trim($val['name']);
125 - if ($name && $address && $name != $address) {
126 - $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
127 - } elseif ($address) {
128 - $string = $address;
129 - } elseif ($name) {
130 - $string = $name;
131 - }
132 -
133 - $out[$j] = ['name' => $name, 'mailto' => $address, 'string' => $string];
134 - }
135 -
136 - if ($max && $j == $max) {
137 - break;
138 - }
139 - }
140 -
141 - return $out;
142 - }
143 -
144 - /**
145 - * Decode a message header value
146 - *
147 - * @param string $input Header value
148 - * @param string $fallback Fallback charset if none specified
149 - *
150 - * @return string Decoded string
151 - */
152 - public static function decode_header($input, $fallback = null)
153 - {
154 - $str = self::decode_mime_string((string)$input, $fallback);
155 -
156 - return $str;
157 - }
158 -
159 - /**
160 - * Decode a mime-encoded string to internal charset
161 - *
162 - * @param string $input Header value
163 - * @param string $fallback Fallback charset if none specified
164 - *
165 - * @return string Decoded string
166 - */
167 - public static function decode_mime_string($input, $fallback = null)
168 - {
169 - $default_charset = $fallback ? : self::get_charset();
170 -
171 - // rfc: all line breaks or other characters not found
172 - // in the Base64 Alphabet must be ignored by decoding software
173 - // delete all blanks between MIME-lines, differently we can
174 - // receive unnecessary blanks and broken utf-8 symbols
175 - $input = preg_replace("/\?=\s+=\?/", '?==?', $input);
176 -
177 - // encoded-word regexp
178 - $re = '/=\?([^?]+)\?([BbQq])\?([^\n]*?)\?=/';
179 -
180 - // Find all RFC2047's encoded words
181 - if (preg_match_all($re, $input, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
182 - // Initialize variables
183 - $tmp = [];
184 - $out = '';
185 - $start = 0;
186 -
187 - foreach ($matches as $idx => $m) {
188 - $pos = $m[0][1];
189 - $charset = $m[1][0];
190 - $encoding = $m[2][0];
191 - $text = $m[3][0];
192 - $length = strlen($m[0][0]);
193 -
194 - // Append everything that is before the text to be decoded
195 - if ($start != $pos) {
196 - $substr = substr($input, $start, $pos - $start);
197 - $out .= Charset::convert($substr, $default_charset);
198 - $start = $pos;
199 - }
200 - $start += $length;
201 -
202 - // Per RFC2047, each string part "MUST represent an integral number
203 - // of characters . A multi-octet character may not be split across
204 - // adjacent encoded-words." However, some mailers break this, so we
205 - // try to handle characters spanned across parts anyway by iterating
206 - // through and aggregating sequential encoded parts with the same
207 - // character set and encoding, then perform the decoding on the
208 - // aggregation as a whole.
209 -
210 - $tmp[] = $text;
211 - if ($next_match = $matches[$idx + 1]) {
212 - if ($next_match[0][1] == $start
213 - && $next_match[1][0] == $charset
214 - && $next_match[2][0] == $encoding
215 - ) {
216 - continue;
217 - }
218 - }
219 -
220 - $count = count($tmp);
221 - $text = '';
222 -
223 - // Decode and join encoded-word's chunks
224 - if ($encoding == 'B' || $encoding == 'b') {
225 - $rest = '';
226 - // base64 must be decoded a segment at a time.
227 - // However, there are broken implementations that continue
228 - // in the following word, we'll handle that (#6048)
229 - for ($i = 0 ; $i < $count ; $i++) {
230 - $chunk = $rest.$tmp[$i];
231 - $length = strlen($chunk);
232 - if ($length % 4) {
233 - $length = floor($length / 4) * 4;
234 - $rest = substr($chunk, $length);
235 - $chunk = substr($chunk, 0, $length);
236 - }
237 -
238 - $text .= base64_decode($chunk);
239 - }
240 - } else { //if ($encoding == 'Q' || $encoding == 'q') {
241 - // quoted printable can be combined and processed at once
242 - for ($i = 0 ; $i < $count ; $i++) {
243 - $text .= $tmp[$i];
244 - }
245 -
246 - $text = str_replace('_', ' ', $text);
247 - $text = quoted_printable_decode($text);
248 - }
249 -
250 - $out .= Charset::convert($text, $charset);
251 - $tmp = [];
252 - }
253 -
254 - // add the last part of the input string
255 - if ($start != strlen($input)) {
256 - $out .= Charset::convert(substr($input, $start), $default_charset);
257 - }
258 -
259 - // return the results
260 - return $out;
261 - }
262 -
263 - // no encoding information, use fallback
264 - return Charset::convert($input, $default_charset);
265 - }
266 -
267 - /**
268 - * Decode a mime part
269 - *
270 - * @param string $input Input string
271 - * @param string $encoding Part encoding
272 - *
273 - * @return string Decoded string
274 - */
275 - public static function decode($input, $encoding = '7bit')
276 - {
277 - switch (strtolower($encoding)) {
278 - case 'quoted-printable':
279 - return quoted_printable_decode($input);
280 - case 'base64':
281 - return base64_decode($input);
282 - case 'x-uuencode':
283 - case 'x-uue':
284 - case 'uue':
285 - case 'uuencode':
286 - return convert_uudecode($input);
287 - case '7bit':
288 - default:
289 - return $input;
290 - }
291 - }
292 -
293 - /**
294 - * Split RFC822 header string into an associative array
295 - */
296 - public static function parse_headers($headers)
297 - {
298 - $a_headers = [];
299 - $headers = preg_replace('/\r?\n(\t| )+/', ' ', $headers);
300 - $lines = explode("\n", $headers);
301 - $count = count($lines);
302 -
303 - for ($i = 0 ; $i < $count ; $i++) {
304 - if ($p = strpos($lines[$i], ': ')) {
305 - $field = strtolower(substr($lines[$i], 0, $p));
306 - $value = trim(substr($lines[$i], $p + 1));
307 - if (!empty($value)) {
308 - $a_headers[$field] = $value;
309 - }
310 - }
311 - }
312 -
313 - return $a_headers;
314 - }
315 -
316 - /**
317 - * E-mail address list parser
318 - */
319 - private static function parse_address_list($str, $decode = true, $fallback = null)
320 - {
321 - // remove any newlines and carriage returns before
322 - $str = preg_replace('/\r?\n(\s|\t)?/', ' ', $str);
323 -
324 - // extract list items, remove comments
325 - $str = self::explode_header_string(',;', $str, true);
326 - $result = [];
327 -
328 - // simplified regexp, supporting quoted local part
329 - $email_rx = '(\S+|("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+"))@\S+';
330 -
331 - foreach ($str as $key => $val) {
332 - $name = '';
333 - $address = '';
334 - $val = trim($val);
335 -
336 - if (preg_match('/(.*)<('.$email_rx.')>$/', $val, $m)) {
337 - $address = $m[2];
338 - $name = trim($m[1]);
339 - } elseif (preg_match('/^('.$email_rx.')$/', $val, $m)) {
340 - $address = $m[1];
341 - $name = '';
342 - } // special case (#1489092)
343 - elseif (preg_match('/(\s*<MAILER-DAEMON>)$/', $val, $m)) {
344 - $address = 'MAILER-DAEMON';
345 - $name = substr($val, 0, -strlen($m[1]));
346 - } elseif (preg_match('/('.$email_rx.')/', $val, $m)) {
347 - $name = $m[1];
348 - } else {
349 - $name = $val;
350 - }
351 -
352 - // dequote and/or decode name
353 - if ($name) {
354 - if ($name[0] == '"' && $name[strlen($name) - 1] == '"') {
355 - $name = substr($name, 1, -1);
356 - $name = stripslashes($name);
357 - }
358 - if ($decode) {
359 - $name = self::decode_header($name, $fallback);
360 - // some clients encode addressee name with quotes around it
361 - if ($name[0] == '"' && $name[strlen($name) - 1] == '"') {
362 - $name = substr($name, 1, -1);
363 - }
364 - }
365 - }
366 -
367 - if (!$address && $name) {
368 - $address = $name;
369 - $name = '';
370 - }
371 -
372 - if ($address) {
373 - $address = self::fix_email($address);
374 - $result[$key] = ['name' => $name, 'address' => $address];
375 - }
376 - }
377 -
378 - return $result;
379 - }
380 -
381 - /**
382 - * Explodes header (e.g. address-list) string into array of strings
383 - * using specified separator characters with proper handling
384 - * of quoted-strings and comments (RFC2822)
385 - *
386 - * @param string $separator String containing separator characters
387 - * @param string $str Header string
388 - * @param bool $remove_comments Enable to remove comments
389 - *
390 - * @return array Header items
391 - */
392 - public static function explode_header_string($separator, $str, $remove_comments = false)
393 - {
394 - $length = strlen($str);
395 - $result = [];
396 - $quoted = false;
397 - $comment = 0;
398 - $out = '';
399 -
400 - for ($i = 0 ; $i < $length ; $i++) {
401 - // we're inside a quoted string
402 - if ($quoted) {
403 - if ($str[$i] == '"') {
404 - $quoted = false;
405 - } elseif ($str[$i] == "\\") {
406 - if ($comment <= 0) {
407 - $out .= "\\";
408 - }
409 - $i++;
410 - }
411 - } // we are inside a comment string
412 - elseif ($comment > 0) {
413 - if ($str[$i] == ')') {
414 - $comment--;
415 - } elseif ($str[$i] == '(') {
416 - $comment++;
417 - } elseif ($str[$i] == "\\") {
418 - $i++;
419 - }
420 - continue;
421 - } // separator, add to result array
422 - elseif (strpos($separator, $str[$i]) !== false) {
423 - if ($out) {
424 - $result[] = $out;
425 - }
426 - $out = '';
427 - continue;
428 - } // start of quoted string
429 - elseif ($str[$i] == '"') {
430 - $quoted = true;
431 - } // start of comment
432 - elseif ($remove_comments && $str[$i] == '(') {
433 - $comment++;
434 - }
435 -
436 - if ($comment <= 0) {
437 - $out .= $str[$i];
438 - }
439 - }
440 -
441 - if ($out && $comment <= 0) {
442 - $result[] = $out;
443 - }
444 -
445 - return $result;
446 - }
447 -
448 - /**
449 - * Interpret a format=flowed message body according to RFC 2646
450 - *
451 - * @param string $text Raw body formatted as flowed text
452 - * @param string $mark Mark each flowed line with specified character
453 - * @param boolean $delsp Remove the trailing space of each flowed line
454 - *
455 - * @return string Interpreted text with unwrapped lines and stuffed space removed
456 - */
457 - public static function unfold_flowed($text, $mark = null, $delsp = false)
458 - {
459 - $text = preg_split('/\r?\n/', $text);
460 - $last = -1;
461 - $q_level = 0;
462 - $marks = [];
463 -
464 - foreach ($text as $idx => $line) {
465 - if ($q = strspn($line, '>')) {
466 - // remove quote chars
467 - $line = substr($line, $q);
468 - // remove (optional) space-staffing
469 - if ($line[0] === ' ') $line = substr($line, 1);
470 -
471 - // The same paragraph (We join current line with the previous one) when:
472 - // - the same level of quoting
473 - // - previous line was flowed
474 - // - previous line contains more than only one single space (and quote char(s))
475 - if ($q == $q_level
476 - && isset($text[$last]) && $text[$last][strlen($text[$last]) - 1] == ' '
477 - && !preg_match('/^>+ {0,1}$/', $text[$last])
478 - ) {
479 - if ($delsp) {
480 - $text[$last] = substr($text[$last], 0, -1);
481 - }
482 - $text[$last] .= $line;
483 - unset($text[$idx]);
484 -
485 - if ($mark) {
486 - $marks[$last] = true;
487 - }
488 - } else {
489 - $last = $idx;
490 - }
491 - } else {
492 - if ($line == '-- ') {
493 - $last = $idx;
494 - } else {
495 - // remove space-stuffing
496 - if ($line[0] === ' ') $line = substr($line, 1);
497 -
498 - if (isset($text[$last]) && $line && !$q_level
499 - && $text[$last] != '-- '
500 - && $text[$last][strlen($text[$last]) - 1] == ' '
501 - ) {
502 - if ($delsp) {
503 - $text[$last] = substr($text[$last], 0, -1);
504 - }
505 - $text[$last] .= $line;
506 - unset($text[$idx]);
507 -
508 - if ($mark) {
509 - $marks[$last] = true;
510 - }
511 - } else {
512 - $text[$idx] = $line;
513 - $last = $idx;
514 - }
515 - }
516 - }
517 - $q_level = $q;
518 - }
519 -
520 - if (!empty($marks)) {
521 - foreach (array_keys($marks) as $mk) {
522 - $text[$mk] = $mark.$text[$mk];
523 - }
524 - }
525 -
526 - return implode("\r\n", $text);
527 - }
528 -
529 - /**
530 - * Wrap the given text to comply with RFC 2646
531 - *
532 - * @param string $text Text to wrap
533 - * @param int $length Length
534 - * @param string $charset Character encoding of $text
535 - *
536 - * @return string Wrapped text
537 - */
538 - public static function format_flowed($text, $length = 72, $charset = null)
539 - {
540 - $text = preg_split('/\r?\n/', $text);
541 -
542 - foreach ($text as $idx => $line) {
543 - if ($line != '-- ') {
544 - if ($level = strspn($line, '>')) {
545 - // remove quote chars
546 - $line = substr($line, $level);
547 - // remove (optional) space-staffing and spaces before the line end
548 - $line = rtrim($line, ' ');
549 - if ($line[0] === ' ') $line = substr($line, 1);
550 -
551 - $prefix = str_repeat('>', $level).' ';
552 - $line = $prefix.self::wordwrap($line, $length - $level - 2, " \r\n$prefix", false, $charset);
553 - } elseif ($line) {
554 - $line = self::wordwrap(rtrim($line), $length - 2, " \r\n", false, $charset);
555 - // space-stuffing
556 - $line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line);
557 - }
558 -
559 - $text[$idx] = $line;
560 - }
561 - }
562 -
563 - return implode("\r\n", $text);
564 - }
565 -
566 - /**
567 - * Improved wordwrap function with multibyte support.
568 - * The code is based on Zend_Text_MultiByte::wordWrap().
569 - *
570 - * @param string $string Text to wrap
571 - * @param int $width Line width
572 - * @param string $break Line separator
573 - * @param bool $cut Enable to cut word
574 - * @param string $charset Charset of $string
575 - * @param bool $wrap_quoted When enabled quoted lines will not be wrapped
576 - *
577 - * @return string Text
578 - */
579 - public static function wordwrap($string, $width = 75, $break = "\n", $cut = false, $charset = null, $wrap_quoted = true)
580 - {
581 - // Note: Never try to use iconv instead of mbstring functions here
582 - // Iconv's substr/strlen are 100x slower (#1489113)
583 -
584 - if ($charset && $charset != IMAP2_CHARSET) {
585 - mb_internal_encoding($charset);
586 - }
587 -
588 - // Convert \r\n to \n, this is our line-separator
589 - $string = str_replace("\r\n", "\n", $string);
590 - $separator = "\n"; // must be 1 character length
591 - $result = [];
592 -
593 - while (($stringLength = mb_strlen($string)) > 0) {
594 - $breakPos = mb_strpos($string, $separator, 0);
595 -
596 - // quoted line (do not wrap)
597 - if ($wrap_quoted && $string[0] == '>') {
598 - if ($breakPos === $stringLength - 1 || $breakPos === false) {
599 - $subString = $string;
600 - $cutLength = null;
601 - } else {
602 - $subString = mb_substr($string, 0, $breakPos);
603 - $cutLength = $breakPos + 1;
604 - }
605 - } // next line found and current line is shorter than the limit
606 - elseif ($breakPos !== false && $breakPos < $width) {
607 - if ($breakPos === $stringLength - 1) {
608 - $subString = $string;
609 - $cutLength = null;
610 - } else {
611 - $subString = mb_substr($string, 0, $breakPos);
612 - $cutLength = $breakPos + 1;
613 - }
614 - } else {
615 - $subString = mb_substr($string, 0, $width);
616 -
617 - // last line
618 - if ($breakPos === false && $subString === $string) {
619 - $cutLength = null;
620 - } else {
621 - $nextChar = mb_substr($string, $width, 1);
622 -
623 - if ($nextChar === ' ' || $nextChar === $separator) {
624 - $afterNextChar = mb_substr($string, $width + 1, 1);
625 -
626 - // Note: mb_substr() does never return False
627 - if ($afterNextChar === false || $afterNextChar === '') {
628 - $subString .= $nextChar;
629 - }
630 -
631 - $cutLength = mb_strlen($subString) + 1;
632 - } else {
633 - $spacePos = mb_strrpos($subString, ' ', 0);
634 -
635 - if ($spacePos !== false) {
636 - $subString = mb_substr($subString, 0, $spacePos);
637 - $cutLength = $spacePos + 1;
638 - } elseif ($cut === false) {
639 - $spacePos = mb_strpos($string, ' ', 0);
640 -
641 - if ($spacePos !== false && ($breakPos === false || $spacePos < $breakPos)) {
642 - $subString = mb_substr($string, 0, $spacePos);
643 - $cutLength = $spacePos + 1;
644 - } elseif ($breakPos === false) {
645 - $subString = $string;
646 - $cutLength = null;
647 - } else {
648 - $subString = mb_substr($string, 0, $breakPos);
649 - $cutLength = $breakPos + 1;
650 - }
651 - } else {
652 - $cutLength = $width;
653 - }
654 - }
655 - }
656 - }
657 -
658 - $result[] = $subString;
659 -
660 - if ($cutLength !== null) {
661 - $string = mb_substr($string, $cutLength, ($stringLength - $cutLength));
662 - } else {
663 - break;
664 - }
665 - }
666 -
667 - if ($charset && $charset != IMAP2_CHARSET) {
668 - mb_internal_encoding(IMAP2_CHARSET);
669 - }
670 -
671 - return implode($break, $result);
672 - }
673 -
674 - /**
675 - * A method to guess the mime_type of an attachment.
676 - *
677 - * @param string $path Path to the file or file contents
678 - * @param string $name File name (with suffix)
679 - * @param string $failover Mime type supplied for failover
680 - * @param boolean $is_stream Set to True if $path contains file contents
681 - * @param boolean $skip_suffix Set to True if the config/mimetypes.php mappig should be ignored
682 - *
683 - * @return string
684 - * @author Till Klampaeckel <[email protected]>
685 - * @see http://de2.php.net/manual/en/ref.fileinfo.php
686 - * @see http://de2.php.net/mime_content_type
687 - */
688 - public static function file_content_type($path, $name, $failover = 'application/octet-stream', $is_stream = false, $skip_suffix = false)
689 - {
690 - static $mime_ext = [];
691 -
692 - $mime_type = null;
693 - $config = rcube::get_instance()->config;
694 -
695 - if (!$skip_suffix && empty($mime_ext)) {
696 - foreach ($config->resolve_paths('mimetypes.php') as $fpath) {
697 - $mime_ext = array_merge($mime_ext, (array)@include($fpath));
698 - }
699 - }
700 -
701 - // use file name suffix with hard-coded mime-type map
702 - if (!$skip_suffix && is_array($mime_ext) && $name) {
703 - if ($suffix = substr($name, strrpos($name, '.') + 1)) {
704 - $mime_type = $mime_ext[strtolower($suffix)];
705 - }
706 - }
707 -
708 - // try fileinfo extension if available
709 - if (!$mime_type && function_exists('finfo_open')) {
710 - $mime_magic = $config->get('mime_magic');
711 - // null as a 2nd argument should be the same as no argument
712 - // this however is not true on all systems/versions
713 - if ($mime_magic) {
714 - $finfo = finfo_open(FILEINFO_MIME, $mime_magic);
715 - } else {
716 - $finfo = finfo_open(FILEINFO_MIME);
717 - }
718 -
719 - if ($finfo) {
720 - $func = $is_stream ? 'finfo_buffer' : 'finfo_file';
721 - $mime_type = $func($finfo, $path, FILEINFO_MIME_TYPE);
722 - finfo_close($finfo);
723 - }
724 - }
725 -
726 - // try PHP's mime_content_type
727 - if (!$mime_type && !$is_stream && function_exists('mime_content_type')) {
728 - $mime_type = @mime_content_type($path);
729 - }
730 -
731 - // fall back to user-submitted string
732 - if (!$mime_type) {
733 - $mime_type = $failover;
734 - }
735 -
736 - return $mime_type;
737 - }
738 -
739 - /**
740 - * Get mimetype => file extension mapping
741 - *
742 - * @param string Mime-Type to get extensions for
743 - *
744 - * @return array List of extensions matching the given mimetype or a hash array
745 - * with ext -> mimetype mappings if $mimetype is not given
746 - */
747 - public static function get_mime_extensions($mimetype = null)
748 - {
749 - static $mime_types, $mime_extensions;
750 -
751 - // return cached data
752 - if (is_array($mime_types)) {
753 - return $mimetype ? $mime_types[$mimetype] : $mime_extensions;
754 - }
755 -
756 - // load mapping file
757 - $file_paths = [];
758 -
759 - if ($mime_types = rcube::get_instance()->config->get('mime_types')) {
760 - $file_paths[] = $mime_types;
761 - }
762 -
763 - // try common locations
764 - if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
765 - $file_paths[] = 'C:/xampp/apache/conf/mime.types.';
766 - } else {
767 - $file_paths[] = '/etc/mime.types';
768 - $file_paths[] = '/etc/httpd/mime.types';
769 - $file_paths[] = '/etc/httpd2/mime.types';
770 - $file_paths[] = '/etc/apache/mime.types';
771 - $file_paths[] = '/etc/apache2/mime.types';
772 - $file_paths[] = '/etc/nginx/mime.types';
773 - $file_paths[] = '/usr/local/etc/httpd/conf/mime.types';
774 - $file_paths[] = '/usr/local/etc/apache/conf/mime.types';
775 - $file_paths[] = '/usr/local/etc/apache24/mime.types';
776 - }
777 -
778 - foreach ($file_paths as $fp) {
779 - if (@is_readable($fp)) {
780 - $lines = file($fp, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
781 - break;
782 - }
783 - }
784 -
785 - $mime_types = $mime_extensions = [];
786 - $regex = "/([\w\+\-\.\/]+)\s+([\w\s]+)/i";
787 - foreach ((array)$lines as $line) {
788 - // skip comments or mime types w/o any extensions
789 - if ($line[0] == '#' || !preg_match($regex, $line, $matches)) {
790 - continue;
791 - }
792 -
793 - $mime = $matches[1];
794 - foreach (explode(' ', $matches[2]) as $ext) {
795 - $ext = trim($ext);
796 - $mime_types[$mime][] = $ext;
797 - $mime_extensions[$ext] = $mime;
798 - }
799 - }
800 -
801 - // fallback to some well-known types most important for daily emails
802 - if (empty($mime_types)) {
803 - foreach (rcube::get_instance()->config->resolve_paths('mimetypes.php') as $fpath) {
804 - $mime_extensions = array_merge($mime_extensions, (array)@include($fpath));
805 - }
806 -
807 - foreach ($mime_extensions as $ext => $mime) {
808 - $mime_types[$mime][] = $ext;
809 - }
810 - }
811 -
812 - // Add some known aliases that aren't included by some mime.types (#1488891)
813 - // the order is important here so standard extensions have higher prio
814 - $aliases = [
815 - 'image/gif' => ['gif'],
816 - 'image/png' => ['png'],
817 - 'image/x-png' => ['png'],
818 - 'image/jpeg' => ['jpg', 'jpeg', 'jpe'],
819 - 'image/jpg' => ['jpg', 'jpeg', 'jpe'],
820 - 'image/pjpeg' => ['jpg', 'jpeg', 'jpe'],
821 - 'image/tiff' => ['tif'],
822 - 'image/bmp' => ['bmp'],
823 - 'image/x-ms-bmp' => ['bmp'],
824 - 'message/rfc822' => ['eml'],
825 - 'text/x-mail' => ['eml'],
826 - ];
827 -
828 - foreach ($aliases as $mime => $exts) {
829 - $mime_types[$mime] = array_unique(array_merge((array)$mime_types[$mime], $exts));
830 -
831 - foreach ($exts as $ext) {
832 - if (!isset($mime_extensions[$ext])) {
833 - $mime_extensions[$ext] = $mime;
834 - }
835 - }
836 - }
837 -
838 - return $mimetype ? $mime_types[$mimetype] : $mime_extensions;
839 - }
840 -
841 - /**
842 - * Detect image type of the given binary data by checking magic numbers.
843 - *
844 - * @param string $data Binary file content
845 - *
846 - * @return string Detected mime-type or jpeg as fallback
847 - */
848 - public static function image_content_type($data)
849 - {
850 - $type = 'jpeg';
851 - if (preg_match('/^\x89\x50\x4E\x47/', $data)) {
852 - $type = 'png';
853 - } elseif (preg_match('/^\x47\x49\x46\x38/', $data)) {
854 - $type = 'gif';
855 - } elseif (preg_match('/^\x00\x00\x01\x00/', $data)) $type = 'ico';
856 -
857 - // else if (preg_match('/^\xFF\xD8\xFF\xE0/', $data)) $type = 'jpeg';
858 -
859 - return 'image/'.$type;
860 - }
861 -
862 - /**
863 - * Try to fix invalid email addresses
864 - */
865 - public static function fix_email($email)
866 - {
867 - $parts = Utils::explode_quoted_string('@', $email);
868 - foreach ($parts as $idx => $part) {
869 - // remove redundant quoting (#1490040)
870 - if ($part[0] == '"' && preg_match('/^"([a-zA-Z0-9._+=-]+)"$/', $part, $m)) {
871 - $parts[$idx] = $m[1];
872 - }
873 - }
874 -
875 - return implode('@', $parts);
876 - }
877 -}