Elgg  Version 4.3
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 
507  public static function encodeQuotedPrintableHeader(
508  $str,
509  $charset,
510  $lineLength = self::LINELENGTH,
511  $lineEnd = self::LINEEND
512  ) {
513  // Reduce line-length by the length of the required delimiter, charsets and encoding
514  $prefix = sprintf('=?%s?Q?', $charset);
515  $lineLength = $lineLength - strlen($prefix) - 3;
516 
517  $str = self::_encodeQuotedPrintable($str);
518 
519  // Mail-Header required chars have to be encoded also:
520  $str = str_replace(['?', ',', ' ', '_', '(', ')'], ['=3F', '=2C', '=20', '=5F', '=28', '=29'], $str);
521 
522  // initialize first line, we need it anyways
523  $lines = [0 => ''];
524 
525  // Split encoded text into separate lines
526  $tmp = '';
527  while (strlen($str) > 0) {
528  $currentLine = max(count($lines) - 1, 0);
529  $token = static::getNextQuotedPrintableToken($str);
530  $substr = substr($str, strlen($token));
531  $str = false === $substr ? '' : $substr;
532 
533  $tmp .= $token;
534  if ($token === '=20') {
535  // only if we have a single char token or space, we can append the
536  // tempstring it to the current line or start a new line if necessary.
537  $lineLimitReached = strlen($lines[$currentLine] . $tmp) > $lineLength;
538  $noCurrentLine = $lines[$currentLine] === '';
539  if ($noCurrentLine && $lineLimitReached) {
540  $lines[$currentLine] = $tmp;
541  $lines[$currentLine + 1] = '';
542  } elseif ($lineLimitReached) {
543  $lines[$currentLine + 1] = $tmp;
544  } else {
545  $lines[$currentLine] .= $tmp;
546  }
547  $tmp = '';
548  }
549  // don't forget to append the rest to the last line
550  if (strlen($str) === 0) {
551  $lines[$currentLine] .= $tmp;
552  }
553  }
554 
555  // assemble the lines together by pre- and appending delimiters, charset, encoding.
556  for ($i = 0, $count = count($lines); $i < $count; $i++) {
557  $lines[$i] = " " . $prefix . $lines[$i] . "?=";
558  }
559  $str = trim(implode($lineEnd, $lines));
560  return $str;
561  }
562 
569  private static function getNextQuotedPrintableToken($str)
570  {
571  if (0 === strpos($str, '=')) {
572  $token = substr($str, 0, 3);
573  } else {
574  $token = substr($str, 0, 1);
575  }
576  return $token;
577  }
578 
588  public static function encodeBase64Header(
589  $str,
590  $charset,
591  $lineLength = self::LINELENGTH,
592  $lineEnd = self::LINEEND
593  ) {
594  $prefix = '=?' . $charset . '?B?';
595  $suffix = '?=';
596  $remainingLength = $lineLength - strlen($prefix) - strlen($suffix);
597 
598  $encodedValue = static::encodeBase64($str, $remainingLength, $lineEnd);
599  $encodedValue = str_replace($lineEnd, $suffix . $lineEnd . ' ' . $prefix, $encodedValue);
600  $encodedValue = $prefix . $encodedValue . $suffix;
601  return $encodedValue;
602  }
603 
613  public static function encodeBase64(
614  $str,
615  $lineLength = self::LINELENGTH,
616  $lineEnd = self::LINEEND
617  ) {
618  $lineLength = $lineLength - ($lineLength % 4);
619  return rtrim(chunk_split(base64_encode($str), $lineLength, $lineEnd));
620  }
621 
628  public function __construct($boundary = null)
629  {
630  // This string needs to be somewhat unique
631  if ($boundary === null) {
632  $this->boundary = '=_' . md5(microtime(1) . static::$makeUnique++);
633  } else {
634  $this->boundary = $boundary;
635  }
636  }
637 
638  // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps
639 
648  public static function encode($str, $encoding, $EOL = self::LINEEND)
649  {
650  switch ($encoding) {
651  case self::ENCODING_BASE64:
652  return static::encodeBase64($str, self::LINELENGTH, $EOL);
653 
654  case self::ENCODING_QUOTEDPRINTABLE:
655  return static::encodeQuotedPrintable($str, self::LINELENGTH, $EOL);
656 
657  default:
661  return $str;
662  }
663  }
664 
671  public function boundary()
672  {
673  return $this->boundary;
674  }
675 
683  public function boundaryLine($EOL = self::LINEEND)
684  {
685  return $EOL . '--' . $this->boundary . $EOL;
686  }
687 
695  public function mimeEnd($EOL = self::LINEEND)
696  {
697  return $EOL . '--' . $this->boundary . '--' . $EOL;
698  }
699 
708  public static function mimeDetectCharset($str)
709  {
710  if (preg_match(self::CHARSET_REGEX, $str, $matches)) {
711  return strtoupper($matches['charset']);
712  }
713 
714  return 'ASCII';
715  }
716 }
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:683
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:588
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
const ENCODING_QUOTEDPRINTABLE
Definition: Mime.php:46
static mimeDetectCharset($str)
Detect MIME charset.
Definition: Mime.php:708
const MULTIPART_RELATED
Definition: Mime.php:54
mimeEnd($EOL=self::LINEEND)
Return MIME ending.
Definition: Mime.php:695
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:648
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:671
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:613
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
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif($item instanceof ElggRelationship) elseif(is_callable([$item, 'getType']))
Definition: item.php:48
const CHARSET_REGEX
Definition: Mime.php:59
const MESSAGE_RFC822
Definition: Mime.php:57
__construct($boundary=null)
Constructor.
Definition: Mime.php:628
static $qpKeysString
Definition: Mime.php:402
const DISPOSITION_INLINE
Definition: Mime.php:49
const MULTIPART_RELATIVE
Definition: Mime.php:55
static encodeQuotedPrintableHeader($str, $charset, $lineLength=self::LINELENGTH, $lineEnd=self::LINEEND)
Encode a given string with the QUOTED_PRINTABLE mechanism for Mail Headers.
Definition: Mime.php:507
const MESSAGE_DELIVERY_STATUS
Definition: Mime.php:58
static $qpReplaceValues
Definition: Mime.php:238