| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721 |
- <?php
- namespace Laminas\Mime;
- use function base64_encode;
- use function chunk_split;
- use function count;
- use function implode;
- use function max;
- use function md5;
- use function microtime;
- use function ord;
- use function preg_match;
- use function rtrim;
- use function sprintf;
- use function str_replace;
- use function strcspn;
- use function strlen;
- use function strpos;
- use function strrpos;
- use function strtoupper;
- use function substr;
- use function substr_replace;
- use function trim;
- /**
- * Support class for MultiPart Mime Messages
- */
- class Mime
- {
- // phpcs:disable Generic.Files.LineLength.TooLong
- public const TYPE_OCTETSTREAM = 'application/octet-stream';
- public const TYPE_TEXT = 'text/plain';
- public const TYPE_HTML = 'text/html';
- public const TYPE_ENRICHED = 'text/enriched';
- public const TYPE_XML = 'text/xml';
- public const ENCODING_7BIT = '7bit';
- public const ENCODING_8BIT = '8bit';
- public const ENCODING_QUOTEDPRINTABLE = 'quoted-printable';
- public const ENCODING_BASE64 = 'base64';
- public const DISPOSITION_ATTACHMENT = 'attachment';
- public const DISPOSITION_INLINE = 'inline';
- public const LINELENGTH = 72;
- public const LINEEND = "\n";
- public const MULTIPART_ALTERNATIVE = 'multipart/alternative';
- public const MULTIPART_MIXED = 'multipart/mixed';
- public const MULTIPART_RELATED = 'multipart/related';
- public const MULTIPART_RELATIVE = 'multipart/relative';
- public const MULTIPART_REPORT = 'multipart/report';
- public const MESSAGE_RFC822 = 'message/rfc822';
- public const MESSAGE_DELIVERY_STATUS = 'message/delivery-status';
- 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]+)#';
- // phpcs:enable
- /** @var null|string */
- protected $boundary;
- /** @var int */
- protected static $makeUnique = 0;
- /**
- * Lookup-tables for QuotedPrintable
- *
- * @var string[]
- */
- public static $qpKeys = [
- "\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",
- ];
- /** @var string[] */
- public static $qpReplaceValues = [
- "=00",
- "=01",
- "=02",
- "=03",
- "=04",
- "=05",
- "=06",
- "=07",
- "=08",
- "=09",
- "=0A",
- "=0B",
- "=0C",
- "=0D",
- "=0E",
- "=0F",
- "=10",
- "=11",
- "=12",
- "=13",
- "=14",
- "=15",
- "=16",
- "=17",
- "=18",
- "=19",
- "=1A",
- "=1B",
- "=1C",
- "=1D",
- "=1E",
- "=1F",
- "=7F",
- "=80",
- "=81",
- "=82",
- "=83",
- "=84",
- "=85",
- "=86",
- "=87",
- "=88",
- "=89",
- "=8A",
- "=8B",
- "=8C",
- "=8D",
- "=8E",
- "=8F",
- "=90",
- "=91",
- "=92",
- "=93",
- "=94",
- "=95",
- "=96",
- "=97",
- "=98",
- "=99",
- "=9A",
- "=9B",
- "=9C",
- "=9D",
- "=9E",
- "=9F",
- "=A0",
- "=A1",
- "=A2",
- "=A3",
- "=A4",
- "=A5",
- "=A6",
- "=A7",
- "=A8",
- "=A9",
- "=AA",
- "=AB",
- "=AC",
- "=AD",
- "=AE",
- "=AF",
- "=B0",
- "=B1",
- "=B2",
- "=B3",
- "=B4",
- "=B5",
- "=B6",
- "=B7",
- "=B8",
- "=B9",
- "=BA",
- "=BB",
- "=BC",
- "=BD",
- "=BE",
- "=BF",
- "=C0",
- "=C1",
- "=C2",
- "=C3",
- "=C4",
- "=C5",
- "=C6",
- "=C7",
- "=C8",
- "=C9",
- "=CA",
- "=CB",
- "=CC",
- "=CD",
- "=CE",
- "=CF",
- "=D0",
- "=D1",
- "=D2",
- "=D3",
- "=D4",
- "=D5",
- "=D6",
- "=D7",
- "=D8",
- "=D9",
- "=DA",
- "=DB",
- "=DC",
- "=DD",
- "=DE",
- "=DF",
- "=E0",
- "=E1",
- "=E2",
- "=E3",
- "=E4",
- "=E5",
- "=E6",
- "=E7",
- "=E8",
- "=E9",
- "=EA",
- "=EB",
- "=EC",
- "=ED",
- "=EE",
- "=EF",
- "=F0",
- "=F1",
- "=F2",
- "=F3",
- "=F4",
- "=F5",
- "=F6",
- "=F7",
- "=F8",
- "=F9",
- "=FA",
- "=FB",
- "=FC",
- "=FD",
- "=FE",
- "=FF",
- ];
- // @codingStandardsIgnoreStart
- public static $qpKeysString =
- "\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";
- // @codingStandardsIgnoreEnd
- /**
- * Check if the given string is "printable"
- *
- * Checks that a string contains no unprintable characters. If this returns
- * false, encode the string for secure delivery.
- *
- * @param string $str
- * @return bool
- */
- public static function isPrintable($str)
- {
- return strcspn($str, static::$qpKeysString) === strlen($str);
- }
- /**
- * Encode a given string with the QUOTED_PRINTABLE mechanism and wrap the lines.
- *
- * @param string $str
- * @param int $lineLength Defaults to {@link LINELENGTH}
- * @param string $lineEnd Defaults to {@link LINEEND}
- * @return string
- */
- public static function encodeQuotedPrintable(
- $str,
- $lineLength = self::LINELENGTH,
- $lineEnd = self::LINEEND
- ) {
- $out = '';
- $str = self::_encodeQuotedPrintable($str);
- // Split encoded text into separate lines
- $initialPtr = 0;
- $strLength = strlen($str);
- while ($initialPtr < $strLength) {
- $continueAt = $strLength - $initialPtr;
- if ($continueAt > $lineLength) {
- $continueAt = $lineLength;
- }
- $chunk = substr($str, $initialPtr, $continueAt);
- // Ensure we are not splitting across an encoded character
- $endingMarkerPos = strrpos($chunk, '=');
- if ($endingMarkerPos !== false && $endingMarkerPos >= strlen($chunk) - 2) {
- $chunk = substr($chunk, 0, $endingMarkerPos);
- $continueAt = $endingMarkerPos;
- }
- if (ord($chunk[0]) === 0x2E) { // 0x2E is a dot
- $chunk = '=2E' . substr($chunk, 1);
- }
- // copied from swiftmailer https://git.io/vAXU1
- switch (ord(substr($chunk, strlen($chunk) - 1))) {
- case 0x09: // Horizontal Tab
- $chunk = substr_replace($chunk, '=09', strlen($chunk) - 1, 1);
- break;
- case 0x20: // Space
- $chunk = substr_replace($chunk, '=20', strlen($chunk) - 1, 1);
- break;
- }
- // Add string and continue
- $out .= $chunk . '=' . $lineEnd;
- $initialPtr += $continueAt;
- }
- $out = rtrim($out, $lineEnd);
- $out = rtrim($out, '=');
- return $out;
- }
- /**
- * Converts a string into quoted printable format.
- *
- * @param string $str
- * @return string
- */
- // @codingStandardsIgnoreStart
- private static function _encodeQuotedPrintable($str)
- {
- // @codingStandardsIgnoreEnd
- $str = str_replace('=', '=3D', $str);
- $str = str_replace(static::$qpKeys, static::$qpReplaceValues, $str);
- $str = rtrim($str);
- return $str;
- }
- /**
- * Encode a given string with the QUOTED_PRINTABLE mechanism for Mail Headers.
- *
- * Mail headers depend on an extended quoted printable algorithm otherwise
- * a range of bugs can occur.
- *
- * @param string $str
- * @param string $charset
- * @param int $lineLength Defaults to {@link LINELENGTH}
- * @param string $lineEnd Defaults to {@link LINEEND}
- * @param positive-int|0 $headerNameSize When folding a line, it is necessary to calculate
- * the length of the entire line (together with the header name).
- * Therefore, you can specify the header name and colon length
- * in this argument to fold the string properly.
- * @return string
- */
- public static function encodeQuotedPrintableHeader(
- $str,
- $charset,
- $lineLength = self::LINELENGTH,
- $lineEnd = self::LINEEND,
- $headerNameSize = 0
- ) {
- // Reduce line-length by the length of the required delimiter, charsets and encoding
- $prefix = sprintf('=?%s?Q?', $charset);
- $lineLength = $lineLength - strlen($prefix) - 3;
- $str = self::_encodeQuotedPrintable($str);
- // Mail-Header required chars have to be encoded also:
- $str = str_replace(['?', ',', ' ', '_'], ['=3F', '=2C', '=20', '=5F'], $str);
- // initialize first line, we need it anyways
- $lines = [0 => ''];
- // Split encoded text into separate lines
- $tmp = '';
- while (strlen($str) > 0) {
- $currentLine = max(count($lines) - 1, 0);
- $token = static::getNextQuotedPrintableToken($str);
- $substr = substr($str, strlen($token));
- $str = false === $substr ? '' : $substr;
- $tmp .= $token;
- if ($token === '=20') {
- // only if we have a single char token or space, we can append the
- // tempstring it to the current line or start a new line if necessary.
- if ($currentLine === 0) {
- // The size of the first line should be calculated with the header name.
- $currentLineLength = strlen($lines[$currentLine] . $tmp) + $headerNameSize;
- } else {
- $currentLineLength = strlen($lines[$currentLine] . $tmp);
- }
- $lineLimitReached = $currentLineLength > $lineLength;
- $noCurrentLine = $lines[$currentLine] === '';
- if ($noCurrentLine && $lineLimitReached) {
- $lines[$currentLine] = $tmp;
- $lines[$currentLine + 1] = '';
- } elseif ($lineLimitReached) {
- $lines[$currentLine + 1] = $tmp;
- } else {
- $lines[$currentLine] .= $tmp;
- }
- $tmp = '';
- }
- // don't forget to append the rest to the last line
- if (strlen($str) === 0) {
- $lines[$currentLine] .= $tmp;
- }
- }
- // assemble the lines together by pre- and appending delimiters, charset, encoding.
- for ($i = 0, $count = count($lines); $i < $count; $i++) {
- $lines[$i] = " " . $prefix . $lines[$i] . "?=";
- }
- $str = trim(implode($lineEnd, $lines));
- return $str;
- }
- /**
- * Retrieves the first token from a quoted printable string.
- *
- * @param string $str
- * @return string
- */
- private static function getNextQuotedPrintableToken($str)
- {
- if (0 === strpos($str, '=')) {
- $token = substr($str, 0, 3);
- } else {
- $token = substr($str, 0, 1);
- }
- return $token;
- }
- /**
- * Encode a given string in mail header compatible base64 encoding.
- *
- * @param string $str
- * @param string $charset
- * @param int $lineLength Defaults to {@link LINELENGTH}
- * @param string $lineEnd Defaults to {@link LINEEND}
- * @return string
- */
- public static function encodeBase64Header(
- $str,
- $charset,
- $lineLength = self::LINELENGTH,
- $lineEnd = self::LINEEND
- ) {
- $prefix = '=?' . $charset . '?B?';
- $suffix = '?=';
- $remainingLength = $lineLength - strlen($prefix) - strlen($suffix);
- $encodedValue = static::encodeBase64($str, $remainingLength, $lineEnd);
- $encodedValue = str_replace($lineEnd, $suffix . $lineEnd . ' ' . $prefix, $encodedValue);
- $encodedValue = $prefix . $encodedValue . $suffix;
- return $encodedValue;
- }
- /**
- * Encode a given string in base64 encoding and break lines
- * according to the maximum linelength.
- *
- * @param string $str
- * @param int $lineLength Defaults to {@link LINELENGTH}
- * @param string $lineEnd Defaults to {@link LINEEND}
- * @return string
- */
- public static function encodeBase64(
- $str,
- $lineLength = self::LINELENGTH,
- $lineEnd = self::LINEEND
- ) {
- $lineLength = $lineLength - ($lineLength % 4);
- return rtrim(chunk_split(base64_encode($str), $lineLength, $lineEnd));
- }
- /**
- * Constructor
- *
- * @param null|string $boundary
- * @access public
- */
- public function __construct($boundary = null)
- {
- // This string needs to be somewhat unique
- if ($boundary === null) {
- $this->boundary = '=_' . md5(microtime(1) . static::$makeUnique++);
- } else {
- $this->boundary = $boundary;
- }
- }
- // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps
- /**
- * Encode the given string with the given encoding.
- *
- * @param string $str
- * @param string $encoding
- * @param string $EOL EOL string; defaults to {@link LINEEND}
- * @return string
- */
- public static function encode($str, $encoding, $EOL = self::LINEEND)
- {
- switch ($encoding) {
- case self::ENCODING_BASE64:
- return static::encodeBase64($str, self::LINELENGTH, $EOL);
- case self::ENCODING_QUOTEDPRINTABLE:
- return static::encodeQuotedPrintable($str, self::LINELENGTH, $EOL);
- default:
- /**
- * @todo 7Bit and 8Bit is currently handled the same way.
- */
- return $str;
- }
- }
- /**
- * Return a MIME boundary
- *
- * @access public
- * @return string
- */
- public function boundary()
- {
- return $this->boundary;
- }
- /**
- * Return a MIME boundary line
- *
- * @param string $EOL Defaults to {@link LINEEND}
- * @access public
- * @return string
- */
- public function boundaryLine($EOL = self::LINEEND)
- {
- return $EOL . '--' . $this->boundary . $EOL;
- }
- /**
- * Return MIME ending
- *
- * @param string $EOL Defaults to {@link LINEEND}
- * @access public
- * @return string
- */
- public function mimeEnd($EOL = self::LINEEND)
- {
- return $EOL . '--' . $this->boundary . '--' . $EOL;
- }
- /**
- * Detect MIME charset
- *
- * Extract parts according to https://tools.ietf.org/html/rfc2047#section-2
- *
- * @param string $str
- * @return string
- */
- public static function mimeDetectCharset($str)
- {
- if (preg_match(self::CHARSET_REGEX, $str, $matches)) {
- return strtoupper($matches['charset']);
- }
- return 'ASCII';
- }
- }
|