Elgg  Version master
Mime.php
Go to the documentation of this file.
1 <?php
10 namespace Laminas\Mime;
11 
12 use function base64_encode;
13 use function chunk_split;
14 use function count;
15 use function implode;
16 use function max;
17 use function md5;
18 use function microtime;
19 use function ord;
20 use function preg_match;
21 use function rtrim;
22 use function sprintf;
23 use function str_replace;
24 use function strcspn;
25 use function strlen;
26 use function strpos;
27 use function strrpos;
28 use function strtoupper;
29 use function substr;
30 use function substr_replace;
31 use function trim;
32 
36 class Mime
37 {
38  // phpcs:disable Generic.Files.LineLength.TooLong
39  public const TYPE_OCTETSTREAM = 'application/octet-stream';
40  public const TYPE_TEXT = 'text/plain';
41  public const TYPE_HTML = 'text/html';
42  public const TYPE_ENRICHED = 'text/enriched';
43  public const TYPE_XML = 'text/xml';
44  public const ENCODING_7BIT = '7bit';
45  public const ENCODING_8BIT = '8bit';
46  public const ENCODING_QUOTEDPRINTABLE = 'quoted-printable';
47  public const ENCODING_BASE64 = 'base64';
48  public const DISPOSITION_ATTACHMENT = 'attachment';
49  public const DISPOSITION_INLINE = 'inline';
50  public const LINELENGTH = 72;
51  public const LINEEND = "\n";
52  public const MULTIPART_ALTERNATIVE = 'multipart/alternative';
53  public const MULTIPART_MIXED = 'multipart/mixed';
54  public const MULTIPART_RELATED = 'multipart/related';
55  public const MULTIPART_RELATIVE = 'multipart/relative';
56  public const MULTIPART_REPORT = 'multipart/report';
57  public const MESSAGE_RFC822 = 'message/rfc822';
58  public const MESSAGE_DELIVERY_STATUS = 'message/delivery-status';
59  public const CHARSET_REGEX = '#=\?(?P<charset>[\x21\x23-\x26\x2a\x2b\x2d\x5e\5f\60\x7b-\x7ea-zA-Z0-9]+)\?(?P<encoding>[\x21\x23-\x26\x2a\x2b\x2d\x5e\5f\60\x7b-\x7ea-zA-Z0-9]+)\?(?P<text>[\x21-\x3e\x40-\x7e]+)#';
60  // phpcs:enable
61 
63  protected $boundary;
64 
66  protected static $makeUnique = 0;
67 
73  public static $qpKeys = [
74  "\x00",
75  "\x01",
76  "\x02",
77  "\x03",
78  "\x04",
79  "\x05",
80  "\x06",
81  "\x07",
82  "\x08",
83  "\x09",
84  "\x0A",
85  "\x0B",
86  "\x0C",
87  "\x0D",
88  "\x0E",
89  "\x0F",
90  "\x10",
91  "\x11",
92  "\x12",
93  "\x13",
94  "\x14",
95  "\x15",
96  "\x16",
97  "\x17",
98  "\x18",
99  "\x19",
100  "\x1A",
101  "\x1B",
102  "\x1C",
103  "\x1D",
104  "\x1E",
105  "\x1F",
106  "\x7F",
107  "\x80",
108  "\x81",
109  "\x82",
110  "\x83",
111  "\x84",
112  "\x85",
113  "\x86",
114  "\x87",
115  "\x88",
116  "\x89",
117  "\x8A",
118  "\x8B",
119  "\x8C",
120  "\x8D",
121  "\x8E",
122  "\x8F",
123  "\x90",
124  "\x91",
125  "\x92",
126  "\x93",
127  "\x94",
128  "\x95",
129  "\x96",
130  "\x97",
131  "\x98",
132  "\x99",
133  "\x9A",
134  "\x9B",
135  "\x9C",
136  "\x9D",
137  "\x9E",
138  "\x9F",
139  "\xA0",
140  "\xA1",
141  "\xA2",
142  "\xA3",
143  "\xA4",
144  "\xA5",
145  "\xA6",
146  "\xA7",
147  "\xA8",
148  "\xA9",
149  "\xAA",
150  "\xAB",
151  "\xAC",
152  "\xAD",
153  "\xAE",
154  "\xAF",
155  "\xB0",
156  "\xB1",
157  "\xB2",
158  "\xB3",
159  "\xB4",
160  "\xB5",
161  "\xB6",
162  "\xB7",
163  "\xB8",
164  "\xB9",
165  "\xBA",
166  "\xBB",
167  "\xBC",
168  "\xBD",
169  "\xBE",
170  "\xBF",
171  "\xC0",
172  "\xC1",
173  "\xC2",
174  "\xC3",
175  "\xC4",
176  "\xC5",
177  "\xC6",
178  "\xC7",
179  "\xC8",
180  "\xC9",
181  "\xCA",
182  "\xCB",
183  "\xCC",
184  "\xCD",
185  "\xCE",
186  "\xCF",
187  "\xD0",
188  "\xD1",
189  "\xD2",
190  "\xD3",
191  "\xD4",
192  "\xD5",
193  "\xD6",
194  "\xD7",
195  "\xD8",
196  "\xD9",
197  "\xDA",
198  "\xDB",
199  "\xDC",
200  "\xDD",
201  "\xDE",
202  "\xDF",
203  "\xE0",
204  "\xE1",
205  "\xE2",
206  "\xE3",
207  "\xE4",
208  "\xE5",
209  "\xE6",
210  "\xE7",
211  "\xE8",
212  "\xE9",
213  "\xEA",
214  "\xEB",
215  "\xEC",
216  "\xED",
217  "\xEE",
218  "\xEF",
219  "\xF0",
220  "\xF1",
221  "\xF2",
222  "\xF3",
223  "\xF4",
224  "\xF5",
225  "\xF6",
226  "\xF7",
227  "\xF8",
228  "\xF9",
229  "\xFA",
230  "\xFB",
231  "\xFC",
232  "\xFD",
233  "\xFE",
234  "\xFF",
235  ];
236 
238  public static $qpReplaceValues = [
239  "=00",
240  "=01",
241  "=02",
242  "=03",
243  "=04",
244  "=05",
245  "=06",
246  "=07",
247  "=08",
248  "=09",
249  "=0A",
250  "=0B",
251  "=0C",
252  "=0D",
253  "=0E",
254  "=0F",
255  "=10",
256  "=11",
257  "=12",
258  "=13",
259  "=14",
260  "=15",
261  "=16",
262  "=17",
263  "=18",
264  "=19",
265  "=1A",
266  "=1B",
267  "=1C",
268  "=1D",
269  "=1E",
270  "=1F",
271  "=7F",
272  "=80",
273  "=81",
274  "=82",
275  "=83",
276  "=84",
277  "=85",
278  "=86",
279  "=87",
280  "=88",
281  "=89",
282  "=8A",
283  "=8B",
284  "=8C",
285  "=8D",
286  "=8E",
287  "=8F",
288  "=90",
289  "=91",
290  "=92",
291  "=93",
292  "=94",
293  "=95",
294  "=96",
295  "=97",
296  "=98",
297  "=99",
298  "=9A",
299  "=9B",
300  "=9C",
301  "=9D",
302  "=9E",
303  "=9F",
304  "=A0",
305  "=A1",
306  "=A2",
307  "=A3",
308  "=A4",
309  "=A5",
310  "=A6",
311  "=A7",
312  "=A8",
313  "=A9",
314  "=AA",
315  "=AB",
316  "=AC",
317  "=AD",
318  "=AE",
319  "=AF",
320  "=B0",
321  "=B1",
322  "=B2",
323  "=B3",
324  "=B4",
325  "=B5",
326  "=B6",
327  "=B7",
328  "=B8",
329  "=B9",
330  "=BA",
331  "=BB",
332  "=BC",
333  "=BD",
334  "=BE",
335  "=BF",
336  "=C0",
337  "=C1",
338  "=C2",
339  "=C3",
340  "=C4",
341  "=C5",
342  "=C6",
343  "=C7",
344  "=C8",
345  "=C9",
346  "=CA",
347  "=CB",
348  "=CC",
349  "=CD",
350  "=CE",
351  "=CF",
352  "=D0",
353  "=D1",
354  "=D2",
355  "=D3",
356  "=D4",
357  "=D5",
358  "=D6",
359  "=D7",
360  "=D8",
361  "=D9",
362  "=DA",
363  "=DB",
364  "=DC",
365  "=DD",
366  "=DE",
367  "=DF",
368  "=E0",
369  "=E1",
370  "=E2",
371  "=E3",
372  "=E4",
373  "=E5",
374  "=E6",
375  "=E7",
376  "=E8",
377  "=E9",
378  "=EA",
379  "=EB",
380  "=EC",
381  "=ED",
382  "=EE",
383  "=EF",
384  "=F0",
385  "=F1",
386  "=F2",
387  "=F3",
388  "=F4",
389  "=F5",
390  "=F6",
391  "=F7",
392  "=F8",
393  "=F9",
394  "=FA",
395  "=FB",
396  "=FC",
397  "=FD",
398  "=FE",
399  "=FF",
400  ];
401  // @codingStandardsIgnoreStart
402  public static $qpKeysString =
403  "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF";
404  // @codingStandardsIgnoreEnd
405 
415  public static function isPrintable($str)
416  {
417  return strcspn($str, static::$qpKeysString) === strlen($str);
418  }
419 
428  public static function encodeQuotedPrintable(
429  $str,
430  $lineLength = self::LINELENGTH,
431  $lineEnd = self::LINEEND
432  ) {
433  $out = '';
434  $str = self::_encodeQuotedPrintable($str);
435 
436  // Split encoded text into separate lines
437  $initialPtr = 0;
438  $strLength = strlen($str);
439  while ($initialPtr < $strLength) {
440  $continueAt = $strLength - $initialPtr;
441 
442  if ($continueAt > $lineLength) {
443  $continueAt = $lineLength;
444  }
445 
446  $chunk = substr($str, $initialPtr, $continueAt);
447 
448  // Ensure we are not splitting across an encoded character
449  $endingMarkerPos = strrpos($chunk, '=');
450  if ($endingMarkerPos !== false && $endingMarkerPos >= strlen($chunk) - 2) {
451  $chunk = substr($chunk, 0, $endingMarkerPos);
452  $continueAt = $endingMarkerPos;
453  }
454 
455  if (ord($chunk[0]) === 0x2E) { // 0x2E is a dot
456  $chunk = '=2E' . substr($chunk, 1);
457  }
458 
459  // copied from swiftmailer https://git.io/vAXU1
460  switch (ord(substr($chunk, strlen($chunk) - 1))) {
461  case 0x09: // Horizontal Tab
462  $chunk = substr_replace($chunk, '=09', strlen($chunk) - 1, 1);
463  break;
464  case 0x20: // Space
465  $chunk = substr_replace($chunk, '=20', strlen($chunk) - 1, 1);
466  break;
467  }
468 
469  // Add string and continue
470  $out .= $chunk . '=' . $lineEnd;
471  $initialPtr += $continueAt;
472  }
473 
474  $out = rtrim($out, $lineEnd);
475  $out = rtrim($out, '=');
476  return $out;
477  }
478 
485  // @codingStandardsIgnoreStart
486  private static function _encodeQuotedPrintable($str)
487  {
488  // @codingStandardsIgnoreEnd
489  $str = str_replace('=', '=3D', $str);
490  $str = str_replace(static::$qpKeys, static::$qpReplaceValues, $str);
491  $str = rtrim($str);
492  return $str;
493  }
494 
511  public static function encodeQuotedPrintableHeader(
512  $str,
513  $charset,
514  $lineLength = self::LINELENGTH,
515  $lineEnd = self::LINEEND,
516  $headerNameSize = 0
517  ) {
518  // Reduce line-length by the length of the required delimiter, charsets and encoding
519  $prefix = sprintf('=?%s?Q?', $charset);
520  $lineLength = $lineLength - strlen($prefix) - 3;
521 
522  $str = self::_encodeQuotedPrintable($str);
523 
524  // Mail-Header required chars have to be encoded also:
525  $str = str_replace(['?', ',', ' ', '_', '(', ')'], ['=3F', '=2C', '=20', '=5F', '=28', '=29'], $str);
526 
527  // initialize first line, we need it anyways
528  $lines = [0 => ''];
529 
530  // Split encoded text into separate lines
531  $tmp = '';
532  while (strlen($str) > 0) {
533  $currentLine = max(count($lines) - 1, 0);
534  $token = static::getNextQuotedPrintableToken($str);
535  $substr = substr($str, strlen($token));
536  $str = false === $substr ? '' : $substr;
537 
538  $tmp .= $token;
539  if ($token === '=20') {
540  // only if we have a single char token or space, we can append the
541  // tempstring it to the current line or start a new line if necessary.
542  if ($currentLine === 0) {
543  // The size of the first line should be calculated with the header name.
544  $currentLineLength = strlen($lines[$currentLine] . $tmp) + $headerNameSize;
545  } else {
546  $currentLineLength = strlen($lines[$currentLine] . $tmp);
547  }
548 
549  $lineLimitReached = $currentLineLength > $lineLength;
550  $noCurrentLine = $lines[$currentLine] === '';
551  if ($noCurrentLine && $lineLimitReached) {
552  $lines[$currentLine] = $tmp;
553  $lines[$currentLine + 1] = '';
554  } elseif ($lineLimitReached) {
555  $lines[$currentLine + 1] = $tmp;
556  } else {
557  $lines[$currentLine] .= $tmp;
558  }
559  $tmp = '';
560  }
561  // don't forget to append the rest to the last line
562  if (strlen($str) === 0) {
563  $lines[$currentLine] .= $tmp;
564  }
565  }
566 
567  // assemble the lines together by pre- and appending delimiters, charset, encoding.
568  for ($i = 0, $count = count($lines); $i < $count; $i++) {
569  $lines[$i] = " " . $prefix . $lines[$i] . "?=";
570  }
571  $str = trim(implode($lineEnd, $lines));
572  return $str;
573  }
574 
581  private static function getNextQuotedPrintableToken($str)
582  {
583  if (0 === strpos($str, '=')) {
584  $token = substr($str, 0, 3);
585  } else {
586  $token = substr($str, 0, 1);
587  }
588  return $token;
589  }
590 
600  public static function encodeBase64Header(
601  $str,
602  $charset,
603  $lineLength = self::LINELENGTH,
604  $lineEnd = self::LINEEND
605  ) {
606  $prefix = '=?' . $charset . '?B?';
607  $suffix = '?=';
608  $remainingLength = $lineLength - strlen($prefix) - strlen($suffix);
609 
610  $encodedValue = static::encodeBase64($str, $remainingLength, $lineEnd);
611  $encodedValue = str_replace($lineEnd, $suffix . $lineEnd . ' ' . $prefix, $encodedValue);
612  $encodedValue = $prefix . $encodedValue . $suffix;
613  return $encodedValue;
614  }
615 
625  public static function encodeBase64(
626  $str,
627  $lineLength = self::LINELENGTH,
628  $lineEnd = self::LINEEND
629  ) {
630  $lineLength = $lineLength - ($lineLength % 4);
631  return rtrim(chunk_split(base64_encode($str), $lineLength, $lineEnd));
632  }
633 
640  public function __construct($boundary = null)
641  {
642  // This string needs to be somewhat unique
643  if ($boundary === null) {
644  $this->boundary = '=_' . md5(microtime(1) . static::$makeUnique++);
645  } else {
646  $this->boundary = $boundary;
647  }
648  }
649 
650  // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps
651 
660  public static function encode($str, $encoding, $EOL = self::LINEEND)
661  {
662  switch ($encoding) {
663  case self::ENCODING_BASE64:
664  return static::encodeBase64($str, self::LINELENGTH, $EOL);
665 
666  case self::ENCODING_QUOTEDPRINTABLE:
667  return static::encodeQuotedPrintable($str, self::LINELENGTH, $EOL);
668 
669  default:
673  return $str;
674  }
675  }
676 
683  public function boundary()
684  {
685  return $this->boundary;
686  }
687 
695  public function boundaryLine($EOL = self::LINEEND)
696  {
697  return $EOL . '--' . $this->boundary . $EOL;
698  }
699 
707  public function mimeEnd($EOL = self::LINEEND)
708  {
709  return $EOL . '--' . $this->boundary . '--' . $EOL;
710  }
711 
720  public static function mimeDetectCharset($str)
721  {
722  if (preg_match(self::CHARSET_REGEX, $str, $matches)) {
723  return strtoupper($matches['charset']);
724  }
725 
726  return 'ASCII';
727  }
728 }
const ENCODING_BASE64
Definition: Mime.php:47
const TYPE_XML
Definition: Mime.php:43
const TYPE_OCTETSTREAM
Definition: Mime.php:39
const LINEEND
Definition: Mime.php:51
const ENCODING_8BIT
Definition: Mime.php:45
const MULTIPART_REPORT
Definition: Mime.php:56
boundaryLine($EOL=self::LINEEND)
Return a MIME boundary line.
Definition: Mime.php:695
const MULTIPART_MIXED
Definition: Mime.php:53
static encodeBase64Header($str, $charset, $lineLength=self::LINELENGTH, $lineEnd=self::LINEEND)
Encode a given string in mail header compatible base64 encoding.
Definition: Mime.php:600
const DISPOSITION_ATTACHMENT
Definition: Mime.php:48
Support class for MultiPart Mime Messages.
Definition: Mime.php:36
const TYPE_ENRICHED
Definition: Mime.php:42
const LINELENGTH
Definition: Mime.php:50
const TYPE_HTML
Definition: Mime.php:41
static encodeQuotedPrintableHeader($str, $charset, $lineLength=self::LINELENGTH, $lineEnd=self::LINEEND, $headerNameSize=0)
Encode a given string with the QUOTED_PRINTABLE mechanism for Mail Headers.
Definition: Mime.php:511
const ENCODING_QUOTEDPRINTABLE
Definition: Mime.php:46
static mimeDetectCharset($str)
Detect MIME charset.
Definition: Mime.php:720
const MULTIPART_RELATED
Definition: Mime.php:54
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif($item instanceof\ElggRelationship) elseif(is_callable([$item, 'getType']))
Definition: item.php:48
mimeEnd($EOL=self::LINEEND)
Return MIME ending.
Definition: Mime.php:707
static $makeUnique
Definition: Mime.php:66
static isPrintable($str)
Check if the given string is "printable".
Definition: Mime.php:415
const TYPE_TEXT
Definition: Mime.php:40
const MULTIPART_ALTERNATIVE
Definition: Mime.php:52
$suffix
Definition: excerpt.php:13
static encode($str, $encoding, $EOL=self::LINEEND)
Encode the given string with the given encoding.
Definition: Mime.php:660
const ENCODING_7BIT
Definition: Mime.php:44
$count
Definition: ban.php:24
static $qpKeys
Definition: Mime.php:73
$token
boundary()
Return a MIME boundary.
Definition: Mime.php:683
static encodeBase64($str, $lineLength=self::LINELENGTH, $lineEnd=self::LINEEND)
Encode a given string in base64 encoding and break lines according to the maximum linelength...
Definition: Mime.php:625
static encodeQuotedPrintable($str, $lineLength=self::LINELENGTH, $lineEnd=self::LINEEND)
Encode a given string with the QUOTED_PRINTABLE mechanism and wrap the lines.
Definition: Mime.php:428
const CHARSET_REGEX
Definition: Mime.php:59
const MESSAGE_RFC822
Definition: Mime.php:57
__construct($boundary=null)
Constructor.
Definition: Mime.php:640
static $qpKeysString
Definition: Mime.php:402
const DISPOSITION_INLINE
Definition: Mime.php:49
const MULTIPART_RELATIVE
Definition: Mime.php:55
const MESSAGE_DELIVERY_STATUS
Definition: Mime.php:58
static $qpReplaceValues
Definition: Mime.php:238