diff --git a/plugins/php-mime-mail-parser/src/Attachment.php b/plugins/php-mime-mail-parser/src/Attachment.php new file mode 100644 index 00000000..1a731635 --- /dev/null +++ b/plugins/php-mime-mail-parser/src/Attachment.php @@ -0,0 +1,276 @@ +filename = $filename; + $this->contentType = $contentType; + $this->stream = $stream; + $this->content = null; + $this->contentDisposition = $contentDisposition; + $this->contentId = $contentId; + $this->headers = $headers; + $this->mimePartStr = $mimePartStr; + } + + /** + * retrieve the attachment filename + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + + /** + * Retrieve the Attachment Content-Type + * + * @return string + */ + public function getContentType() + { + return $this->contentType; + } + + /** + * Retrieve the Attachment Content-Disposition + * + * @return string + */ + public function getContentDisposition() + { + return $this->contentDisposition; + } + + /** + * Retrieve the Attachment Content-ID + * + * @return string + */ + public function getContentID() + { + return $this->contentId; + } + + /** + * Retrieve the Attachment Headers + * + * @return array + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Get a handle to the stream + * + * @return resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * Rename a file if it already exists at its destination. + * Renaming is done by adding a duplicate number to the file name. E.g. existingFileName_1.ext. + * After a max duplicate number, renaming the file will switch over to generating a random suffix. + * + * @param string $fileName Complete path to the file. + * @return string The suffixed file name. + */ + protected function suffixFileName(string $fileName): string + { + $pathInfo = pathinfo($fileName); + $dirname = $pathInfo['dirname'].DIRECTORY_SEPARATOR; + $filename = $pathInfo['filename']; + $extension = empty($pathInfo['extension']) ? '' : '.'.$pathInfo['extension']; + + $i = 0; + do { + $i++; + + if ($i > $this->maxDuplicateNumber) { + $duplicateExtension = uniqid(); + } else { + $duplicateExtension = $i; + } + + $resultName = $dirname.$filename."_$duplicateExtension".$extension; + } while (file_exists($resultName)); + + return $resultName; + } + + /** + * Read the contents a few bytes at a time until completed + * Once read to completion, it always returns false + * + * @param int $bytes (default: 2082) + * + * @return string|bool + */ + public function read($bytes = 2082) + { + return feof($this->stream) ? false : fread($this->stream, $bytes); + } + + /** + * Retrieve the file content in one go + * Once you retrieve the content you cannot use MimeMailParser_attachment::read() + * + * @return string + */ + public function getContent() + { + if ($this->content === null) { + fseek($this->stream, 0); + while (($buf = $this->read()) !== false) { + $this->content .= $buf; + } + } + + return $this->content; + } + + /** + * Get mime part string for this attachment + * + * @return string + */ + public function getMimePartStr() + { + return $this->mimePartStr; + } + + /** + * Save the attachment individually + * + * @param string $attach_dir + * @param string $filenameStrategy + * + * @return string + */ + public function save( + $attach_dir, + $filenameStrategy = Parser::ATTACHMENT_DUPLICATE_SUFFIX + ) { + $attach_dir = rtrim($attach_dir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; + if (!is_dir($attach_dir)) { + mkdir($attach_dir); + } + + // Determine filename + switch ($filenameStrategy) { + case Parser::ATTACHMENT_RANDOM_FILENAME: + $fileInfo = pathinfo($this->getFilename()); + $extension = empty($fileInfo['extension']) ? '' : '.'.$fileInfo['extension']; + $attachment_path = $attach_dir.uniqid().$extension; + break; + case Parser::ATTACHMENT_DUPLICATE_THROW: + case Parser::ATTACHMENT_DUPLICATE_SUFFIX: + $attachment_path = $attach_dir.$this->getFilename(); + break; + default: + throw new Exception('Invalid filename strategy argument provided.'); + } + + // Handle duplicate filename + if (file_exists($attachment_path)) { + switch ($filenameStrategy) { + case Parser::ATTACHMENT_DUPLICATE_THROW: + throw new Exception('Could not create file for attachment: duplicate filename.'); + case Parser::ATTACHMENT_DUPLICATE_SUFFIX: + $attachment_path = $this->suffixFileName($attachment_path); + break; + } + } + + /** @var resource $fp */ + if ($fp = fopen($attachment_path, 'w')) { + while ($bytes = $this->read()) { + fwrite($fp, $bytes); + } + fclose($fp); + return realpath($attachment_path); + } else { + throw new Exception('Could not write attachments. Your directory may be unwritable by PHP.'); + } + } +} diff --git a/plugins/php-mime-mail-parser/src/Charset.php b/plugins/php-mime-mail-parser/src/Charset.php new file mode 100644 index 00000000..cd219f22 --- /dev/null +++ b/plugins/php-mime-mail-parser/src/Charset.php @@ -0,0 +1,370 @@ + 'us-ascii', + 'us-ascii' => 'us-ascii', + 'ansi_x3.4-1968' => 'us-ascii', + '646' => 'us-ascii', + 'iso-8859-1' => 'iso-8859-1', + 'iso-8859-2' => 'iso-8859-2', + 'iso-8859-3' => 'iso-8859-3', + 'iso-8859-4' => 'iso-8859-4', + 'iso-8859-5' => 'iso-8859-5', + 'iso-8859-6' => 'iso-8859-6', + 'iso-8859-6-i' => 'iso-8859-6-i', + 'iso-8859-6-e' => 'iso-8859-6-e', + 'iso-8859-7' => 'iso-8859-7', + 'iso-8859-8' => 'iso-8859-8', + 'iso-8859-8-i' => 'iso-8859-8', + 'iso-8859-8-e' => 'iso-8859-8-e', + 'iso-8859-9' => 'iso-8859-9', + 'iso-8859-10' => 'iso-8859-10', + 'iso-8859-11' => 'iso-8859-11', + 'iso-8859-13' => 'iso-8859-13', + 'iso-8859-14' => 'iso-8859-14', + 'iso-8859-15' => 'iso-8859-15', + 'iso-8859-16' => 'iso-8859-16', + 'iso-ir-111' => 'iso-ir-111', + 'iso-2022-cn' => 'iso-2022-cn', + 'iso-2022-cn-ext' => 'iso-2022-cn', + 'iso-2022-kr' => 'iso-2022-kr', + 'iso-2022-jp' => 'iso-2022-jp', + 'utf-16be' => 'utf-16be', + 'utf-16le' => 'utf-16le', + 'utf-16' => 'utf-16', + 'windows-1250' => 'windows-1250', + 'windows-1251' => 'windows-1251', + 'windows-1252' => 'windows-1252', + 'windows-1253' => 'windows-1253', + 'windows-1254' => 'windows-1254', + 'windows-1255' => 'windows-1255', + 'windows-1256' => 'windows-1256', + 'windows-1257' => 'windows-1257', + 'windows-1258' => 'windows-1258', + 'ibm866' => 'ibm866', + 'ibm850' => 'ibm850', + 'ibm852' => 'ibm852', + 'ibm855' => 'ibm855', + 'ibm857' => 'ibm857', + 'ibm862' => 'ibm862', + 'ibm864' => 'ibm864', + 'utf-8' => 'utf-8', + 'utf-7' => 'utf-7', + 'shift_jis' => 'shift_jis', + 'big5' => 'big5', + 'euc-jp' => 'euc-jp', + 'euc-kr' => 'euc-kr', + 'gb2312' => 'gb2312', + 'gb18030' => 'gb18030', + 'viscii' => 'viscii', + 'koi8-r' => 'koi8-r', + 'koi8_r' => 'koi8-r', + 'cskoi8r' => 'koi8-r', + 'koi' => 'koi8-r', + 'koi8' => 'koi8-r', + 'koi8-u' => 'koi8-u', + 'tis-620' => 'tis-620', + 't.61-8bit' => 't.61-8bit', + 'hz-gb-2312' => 'hz-gb-2312', + 'big5-hkscs' => 'big5-hkscs', + 'gbk' => 'gbk', + 'cns11643' => 'x-euc-tw', + 'x-imap4-modified-utf7' => 'x-imap4-modified-utf7', + 'x-euc-tw' => 'x-euc-tw', + 'x-mac-ce' => 'macce', + 'x-mac-turkish' => 'macturkish', + 'x-mac-greek' => 'macgreek', + 'x-mac-icelandic' => 'macicelandic', + 'x-mac-croatian' => 'maccroatian', + 'x-mac-romanian' => 'macromanian', + 'x-mac-cyrillic' => 'maccyrillic', + 'x-mac-ukrainian' => 'macukrainian', + 'x-mac-hebrew' => 'machebrew', + 'x-mac-arabic' => 'macarabic', + 'x-mac-farsi' => 'macfarsi', + 'x-mac-devanagari' => 'macdevanagari', + 'x-mac-gujarati' => 'macgujarati', + 'x-mac-gurmukhi' => 'macgurmukhi', + 'armscii-8' => 'armscii-8', + 'x-viet-tcvn5712' => 'x-viet-tcvn5712', + 'x-viet-vps' => 'x-viet-vps', + 'iso-10646-ucs-2' => 'utf-16be', + 'x-iso-10646-ucs-2-be' => 'utf-16be', + 'x-iso-10646-ucs-2-le' => 'utf-16le', + 'x-user-defined' => 'x-user-defined', + 'x-johab' => 'x-johab', + 'latin1' => 'iso-8859-1', + 'iso_8859-1' => 'iso-8859-1', + 'iso8859-1' => 'iso-8859-1', + 'iso8859-2' => 'iso-8859-2', + 'iso8859-3' => 'iso-8859-3', + 'iso8859-4' => 'iso-8859-4', + 'iso8859-5' => 'iso-8859-5', + 'iso8859-6' => 'iso-8859-6', + 'iso8859-7' => 'iso-8859-7', + 'iso8859-8' => 'iso-8859-8', + 'iso8859-9' => 'iso-8859-9', + 'iso8859-10' => 'iso-8859-10', + 'iso8859-11' => 'iso-8859-11', + 'iso8859-13' => 'iso-8859-13', + 'iso8859-14' => 'iso-8859-14', + 'iso8859-15' => 'iso-8859-15', + 'iso_8859-1:1987' => 'iso-8859-1', + 'iso-ir-100' => 'iso-8859-1', + 'l1' => 'iso-8859-1', + 'ibm819' => 'iso-8859-1', + 'cp819' => 'iso-8859-1', + 'csisolatin1' => 'iso-8859-1', + 'latin2' => 'iso-8859-2', + 'iso_8859-2' => 'iso-8859-2', + 'iso_8859-2:1987' => 'iso-8859-2', + 'iso-ir-101' => 'iso-8859-2', + 'l2' => 'iso-8859-2', + 'csisolatin2' => 'iso-8859-2', + 'latin3' => 'iso-8859-3', + 'iso_8859-3' => 'iso-8859-3', + 'iso_8859-3:1988' => 'iso-8859-3', + 'iso-ir-109' => 'iso-8859-3', + 'l3' => 'iso-8859-3', + 'csisolatin3' => 'iso-8859-3', + 'latin4' => 'iso-8859-4', + 'iso_8859-4' => 'iso-8859-4', + 'iso_8859-4:1988' => 'iso-8859-4', + 'iso-ir-110' => 'iso-8859-4', + 'l4' => 'iso-8859-4', + 'csisolatin4' => 'iso-8859-4', + 'cyrillic' => 'iso-8859-5', + 'iso_8859-5' => 'iso-8859-5', + 'iso_8859-5:1988' => 'iso-8859-5', + 'iso-ir-144' => 'iso-8859-5', + 'csisolatincyrillic' => 'iso-8859-5', + 'arabic' => 'iso-8859-6', + 'iso_8859-6' => 'iso-8859-6', + 'iso_8859-6:1987' => 'iso-8859-6', + 'iso-ir-127' => 'iso-8859-6', + 'ecma-114' => 'iso-8859-6', + 'asmo-708' => 'iso-8859-6', + 'csisolatinarabic' => 'iso-8859-6', + 'csiso88596i' => 'iso-8859-6-i', + 'csiso88596e' => 'iso-8859-6-e', + 'greek' => 'iso-8859-7', + 'greek8' => 'iso-8859-7', + 'sun_eu_greek' => 'iso-8859-7', + 'iso_8859-7' => 'iso-8859-7', + 'iso_8859-7:1987' => 'iso-8859-7', + 'iso-ir-126' => 'iso-8859-7', + 'elot_928' => 'iso-8859-7', + 'ecma-118' => 'iso-8859-7', + 'csisolatingreek' => 'iso-8859-7', + 'hebrew' => 'iso-8859-8', + 'iso_8859-8' => 'iso-8859-8', + 'visual' => 'iso-8859-8', + 'iso_8859-8:1988' => 'iso-8859-8', + 'iso-ir-138' => 'iso-8859-8', + 'csisolatinhebrew' => 'iso-8859-8', + 'csiso88598i' => 'iso-8859-8', + 'iso-8859-8i' => 'iso-8859-8', + 'logical' => 'iso-8859-8', + 'csiso88598e' => 'iso-8859-8-e', + 'latin5' => 'iso-8859-9', + 'iso_8859-9' => 'iso-8859-9', + 'iso_8859-9:1989' => 'iso-8859-9', + 'iso-ir-148' => 'iso-8859-9', + 'l5' => 'iso-8859-9', + 'csisolatin5' => 'iso-8859-9', + 'unicode-1-1-utf-8' => 'utf-8', + 'utf8' => 'utf-8', + 'x-sjis' => 'shift_jis', + 'shift-jis' => 'shift_jis', + 'ms_kanji' => 'shift_jis', + 'csshiftjis' => 'shift_jis', + 'windows-31j' => 'shift_jis', + 'cp932' => 'shift_jis', + 'sjis' => 'shift_jis', + 'cseucpkdfmtjapanese' => 'euc-jp', + 'x-euc-jp' => 'euc-jp', + 'csiso2022jp' => 'iso-2022-jp', + 'iso-2022-jp-2' => 'iso-2022-jp', + 'csiso2022jp2' => 'iso-2022-jp', + 'csbig5' => 'big5', + 'cn-big5' => 'big5', + 'x-x-big5' => 'big5', + 'zh_tw-big5' => 'big5', + 'cseuckr' => 'euc-kr', + 'ks_c_5601-1987' => 'euc-kr', + 'iso-ir-149' => 'euc-kr', + 'ks_c_5601-1989' => 'euc-kr', + 'ksc_5601' => 'euc-kr', + 'ksc5601' => 'euc-kr', + 'korean' => 'euc-kr', + 'csksc56011987' => 'euc-kr', + '5601' => 'euc-kr', + 'windows-949' => 'euc-kr', + 'gb_2312-80' => 'gb2312', + 'iso-ir-58' => 'gb2312', + 'chinese' => 'gb2312', + 'csiso58gb231280' => 'gb2312', + 'csgb2312' => 'gb2312', + 'zh_cn.euc' => 'gb2312', + 'gb_2312' => 'gb2312', + 'x-cp1250' => 'windows-1250', + 'x-cp1251' => 'windows-1251', + 'x-cp1252' => 'windows-1252', + 'x-cp1253' => 'windows-1253', + 'x-cp1254' => 'windows-1254', + 'x-cp1255' => 'windows-1255', + 'x-cp1256' => 'windows-1256', + 'x-cp1257' => 'windows-1257', + 'x-cp1258' => 'windows-1258', + 'windows-874' => 'windows-874', + 'ibm874' => 'windows-874', + 'dos-874' => 'windows-874', + 'macintosh' => 'macintosh', + 'x-mac-roman' => 'macintosh', + 'mac' => 'macintosh', + 'csmacintosh' => 'macintosh', + 'cp866' => 'ibm866', + 'cp-866' => 'ibm866', + '866' => 'ibm866', + 'csibm866' => 'ibm866', + 'cp850' => 'ibm850', + '850' => 'ibm850', + 'csibm850' => 'ibm850', + 'cp852' => 'ibm852', + '852' => 'ibm852', + 'csibm852' => 'ibm852', + 'cp855' => 'ibm855', + '855' => 'ibm855', + 'csibm855' => 'ibm855', + 'cp857' => 'ibm857', + '857' => 'ibm857', + 'csibm857' => 'ibm857', + 'cp862' => 'ibm862', + '862' => 'ibm862', + 'csibm862' => 'ibm862', + 'cp864' => 'ibm864', + '864' => 'ibm864', + 'csibm864' => 'ibm864', + 'ibm-864' => 'ibm864', + 't.61' => 't.61-8bit', + 'iso-ir-103' => 't.61-8bit', + 'csiso103t618bit' => 't.61-8bit', + 'x-unicode-2-0-utf-7' => 'utf-7', + 'unicode-2-0-utf-7' => 'utf-7', + 'unicode-1-1-utf-7' => 'utf-7', + 'csunicode11utf7' => 'utf-7', + 'csunicode' => 'utf-16be', + 'csunicode11' => 'utf-16be', + 'iso-10646-ucs-basic' => 'utf-16be', + 'csunicodeascii' => 'utf-16be', + 'iso-10646-unicode-latin1' => 'utf-16be', + 'csunicodelatin1' => 'utf-16be', + 'iso-10646' => 'utf-16be', + 'iso-10646-j-1' => 'utf-16be', + 'latin6' => 'iso-8859-10', + 'iso-ir-157' => 'iso-8859-10', + 'l6' => 'iso-8859-10', + 'csisolatin6' => 'iso-8859-10', + 'iso_8859-15' => 'iso-8859-15', + 'csisolatin9' => 'iso-8859-15', + 'l9' => 'iso-8859-15', + 'ecma-cyrillic' => 'iso-ir-111', + 'csiso111ecmacyrillic' => 'iso-ir-111', + 'csiso2022kr' => 'iso-2022-kr', + 'csviscii' => 'viscii', + 'zh_tw-euc' => 'x-euc-tw', + 'iso88591' => 'iso-8859-1', + 'iso88592' => 'iso-8859-2', + 'iso88593' => 'iso-8859-3', + 'iso88594' => 'iso-8859-4', + 'iso88595' => 'iso-8859-5', + 'iso88596' => 'iso-8859-6', + 'iso88597' => 'iso-8859-7', + 'iso88598' => 'iso-8859-8', + 'iso88599' => 'iso-8859-9', + 'iso885910' => 'iso-8859-10', + 'iso885911' => 'iso-8859-11', + 'iso885912' => 'iso-8859-12', + 'iso885913' => 'iso-8859-13', + 'iso885914' => 'iso-8859-14', + 'iso885915' => 'iso-8859-15', + 'tis620' => 'tis-620', + 'cp1250' => 'windows-1250', + 'cp1251' => 'windows-1251', + 'cp1252' => 'windows-1252', + 'cp1253' => 'windows-1253', + 'cp1254' => 'windows-1254', + 'cp1255' => 'windows-1255', + 'cp1256' => 'windows-1256', + 'cp1257' => 'windows-1257', + 'cp1258' => 'windows-1258', + 'x-gbk' => 'gbk', + 'windows-936' => 'gbk', + 'ansi-1251' => 'windows-1251', + ]; + + /** + * {@inheritdoc} + */ + public function decodeCharset($encodedString, $charset) + { + $charset = $this->getCharsetAlias($charset); + + if ($charset == 'utf-8' || $charset == 'us-ascii') { + return $encodedString; + } + + if (function_exists('mb_convert_encoding')) { + if ($charset == 'iso-2022-jp') { + return mb_convert_encoding($encodedString, 'utf-8', 'iso-2022-jp-ms'); + } + + if (array_search($charset, $this->getSupportedEncodings())) { + return mb_convert_encoding($encodedString, 'utf-8', $charset); + } + } + + return iconv($charset, 'utf-8//translit//ignore', $encodedString); + } + + /** + * {@inheritdoc} + */ + public function getCharsetAlias($charset) + { + $charset = strtolower($charset); + + if (array_key_exists($charset, $this->charsetAlias)) { + return $this->charsetAlias[$charset]; + } + + return 'us-ascii'; + } + + private function getSupportedEncodings() + { + return + array_map( + 'strtolower', + array_unique( + array_merge( + $enc = mb_list_encodings(), + call_user_func_array( + 'array_merge', + array_map( + "mb_encoding_aliases", + $enc + ) + ) + ) + ) + ); + } +} diff --git a/plugins/php-mime-mail-parser/src/Contracts/CharsetManager.php b/plugins/php-mime-mail-parser/src/Contracts/CharsetManager.php new file mode 100644 index 00000000..660ec00c --- /dev/null +++ b/plugins/php-mime-mail-parser/src/Contracts/CharsetManager.php @@ -0,0 +1,24 @@ +parser = $fn; + } + + /** + * Process a mime part, optionally delegating parsing to the $next MiddlewareStack + */ + public function parse(MimePart $part, MiddlewareStack $next) + { + return call_user_func($this->parser, $part, $next); + } +} diff --git a/plugins/php-mime-mail-parser/src/MiddlewareStack.php b/plugins/php-mime-mail-parser/src/MiddlewareStack.php new file mode 100644 index 00000000..3ef6da93 --- /dev/null +++ b/plugins/php-mime-mail-parser/src/MiddlewareStack.php @@ -0,0 +1,89 @@ +add($Middleware) + * + * @param Middleware $middleware + */ + public function __construct(MiddleWareContracts $middleware = null) + { + $this->middleware = $middleware; + } + + /** + * Creates a chained middleware in MiddlewareStack + * + * @param Middleware $middleware + * @return MiddlewareStack Immutable MiddlewareStack + */ + public function add(MiddleWareContracts $middleware) + { + $stack = new static($middleware); + $stack->next = $this; + return $stack; + } + + /** + * Parses the MimePart by passing it through the Middleware + * @param MimePart $part + * @return MimePart + */ + public function parse(MimePart $part) + { + if (!$this->middleware) { + return $part; + } + $part = call_user_func(array($this->middleware, 'parse'), $part, $this->next); + return $part; + } + + /** + * Creates a MiddlewareStack based on an array of middleware + * + * @param Middleware[] $middlewares + * @return MiddlewareStack + */ + public static function factory(array $middlewares = array()) + { + $stack = new static; + foreach ($middlewares as $middleware) { + $stack = $stack->add($middleware); + } + return $stack; + } + + /** + * Allow calling MiddlewareStack instance directly to invoke parse() + * + * @param MimePart $part + * @return MimePart + */ + public function __invoke(MimePart $part) + { + return $this->parse($part); + } +} diff --git a/plugins/php-mime-mail-parser/src/MimePart.php b/plugins/php-mime-mail-parser/src/MimePart.php new file mode 100644 index 00000000..d2211b7c --- /dev/null +++ b/plugins/php-mime-mail-parser/src/MimePart.php @@ -0,0 +1,119 @@ +getPart(); + * $part['headers']['from'] = 'modified@example.com'; + * $MimePart->setPart($part); + */ +class MimePart implements \ArrayAccess +{ + /** + * Internal mime part + * + * @var array + */ + protected $part = array(); + + /** + * Immutable Part Id + * + * @var string + */ + private $id; + + /** + * Create a mime part + * + * @param array $part + * @param string $id + */ + public function __construct($id, array $part) + { + $this->part = $part; + $this->id = $id; + } + + /** + * Retrieve the part Id + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Retrieve the part data + * + * @return array + */ + public function getPart() + { + return $this->part; + } + + /** + * Set the mime part data + * + * @param array $part + * @return void + */ + public function setPart(array $part) + { + $this->part = $part; + } + + /** + * ArrayAccess + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->part[] = $value; + return; + } + $this->part[$offset] = $value; + } + + /** + * ArrayAccess + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + return isset($this->part[$offset]); + } + + /** + * ArrayAccess + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + unset($this->part[$offset]); + } + + /** + * ArrayAccess + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return isset($this->part[$offset]) ? $this->part[$offset] : null; + } +} diff --git a/plugins/php-mime-mail-parser/src/Parser.php b/plugins/php-mime-mail-parser/src/Parser.php new file mode 100644 index 00000000..6502b57e --- /dev/null +++ b/plugins/php-mime-mail-parser/src/Parser.php @@ -0,0 +1,923 @@ +saveAttachments(). + */ + const ATTACHMENT_DUPLICATE_THROW = 'DuplicateThrow'; + const ATTACHMENT_DUPLICATE_SUFFIX = 'DuplicateSuffix'; + const ATTACHMENT_RANDOM_FILENAME = 'RandomFilename'; + + /** + * PHP MimeParser Resource ID + * + * @var resource $resource + */ + protected $resource; + + /** + * A file pointer to email + * + * @var resource $stream + */ + protected $stream; + + /** + * A text of an email + * + * @var string $data + */ + protected $data; + + /** + * Parts of an email + * + * @var array $parts + */ + protected $parts; + + /** + * @var CharsetManager object + */ + protected $charset; + + /** + * Valid stream modes for reading + * + * @var array + */ + protected static $readableModes = [ + 'r', 'r+', 'w+', 'a+', 'x+', 'c+', 'rb', 'r+b', 'w+b', 'a+b', + 'x+b', 'c+b', 'rt', 'r+t', 'w+t', 'a+t', 'x+t', 'c+t' + ]; + + /** + * Stack of middleware registered to process data + * + * @var MiddlewareStack + */ + protected $middlewareStack; + + /** + * Parser constructor. + * + * @param CharsetManager|null $charset + */ + public function __construct(CharsetManager $charset = null) + { + if ($charset == null) { + $charset = new Charset(); + } + + $this->charset = $charset; + $this->middlewareStack = new MiddlewareStack(); + } + + /** + * Free the held resources + * + * @return void + */ + public function __destruct() + { + // clear the email file resource + if (is_resource($this->stream)) { + fclose($this->stream); + } + // clear the MailParse resource + if (is_resource($this->resource)) { + mailparse_msg_free($this->resource); + } + } + + /** + * Set the file path we use to get the email text + * + * @param string $path File path to the MIME mail + * + * @return Parser MimeMailParser Instance + */ + public function setPath($path) + { + if (is_writable($path)) { + $file = fopen($path, 'a+'); + fseek($file, -1, SEEK_END); + if (fread($file, 1) != "\n") { + fwrite($file, PHP_EOL); + } + fclose($file); + } + + // should parse message incrementally from file + $this->resource = mailparse_msg_parse_file($path); + $this->stream = fopen($path, 'r'); + $this->parse(); + + return $this; + } + + /** + * Set the Stream resource we use to get the email text + * + * @param resource $stream + * + * @return Parser MimeMailParser Instance + * @throws Exception + */ + public function setStream($stream) + { + // streams have to be cached to file first + $meta = @stream_get_meta_data($stream); + if (!$meta || !$meta['mode'] || !in_array($meta['mode'], self::$readableModes, true) || $meta['eof']) { + throw new Exception( + 'setStream() expects parameter stream to be readable stream resource.' + ); + } + + /** @var resource $tmp_fp */ + $tmp_fp = tmpfile(); + if ($tmp_fp) { + while (!feof($stream)) { + fwrite($tmp_fp, fread($stream, 2028)); + } + + if (fread($tmp_fp, 1) != "\n") { + fwrite($tmp_fp, PHP_EOL); + } + + fseek($tmp_fp, 0); + $this->stream = &$tmp_fp; + } else { + throw new Exception( + 'Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.' + ); + } + fclose($stream); + + $this->resource = mailparse_msg_create(); + // parses the message incrementally (low memory usage but slower) + while (!feof($this->stream)) { + mailparse_msg_parse($this->resource, fread($this->stream, 2082)); + } + $this->parse(); + + return $this; + } + + /** + * Set the email text + * + * @param string $data + * + * @return Parser MimeMailParser Instance + */ + public function setText($data) + { + if (empty($data)) { + throw new Exception('You must not call MimeMailParser::setText with an empty string parameter'); + } + + if (substr($data, -1) != "\n") { + $data = $data.PHP_EOL; + } + + $this->resource = mailparse_msg_create(); + // does not parse incrementally, fast memory hog might explode + mailparse_msg_parse($this->resource, $data); + $this->data = $data; + $this->parse(); + + return $this; + } + + /** + * Parse the Message into parts + * + * @return void + */ + protected function parse() + { + $structure = mailparse_msg_get_structure($this->resource); + $this->parts = []; + foreach ($structure as $part_id) { + $part = mailparse_msg_get_part($this->resource, $part_id); + $part_data = mailparse_msg_get_part_data($part); + $mimePart = new MimePart($part_id, $part_data); + // let each middleware parse the part before saving + $this->parts[$part_id] = $this->middlewareStack->parse($mimePart)->getPart(); + } + } + + /** + * Retrieve a specific Email Header, without charset conversion. + * + * @param string $name Header name (case-insensitive) + * + * @return string|bool + * @throws Exception + */ + public function getRawHeader($name) + { + $name = strtolower($name); + if (isset($this->parts[1])) { + $headers = $this->getPart('headers', $this->parts[1]); + + return isset($headers[$name]) ? $headers[$name] : false; + } else { + throw new Exception( + 'setPath() or setText() or setStream() must be called before retrieving email headers.' + ); + } + } + + /** + * Retrieve a specific Email Header + * + * @param string $name Header name (case-insensitive) + * + * @return string|bool + */ + public function getHeader($name) + { + $rawHeader = $this->getRawHeader($name); + if ($rawHeader === false) { + return false; + } + + return $this->decodeHeader($rawHeader); + } + + /** + * Retrieve all mail headers + * + * @return array + * @throws Exception + */ + public function getHeaders() + { + if (isset($this->parts[1])) { + $headers = $this->getPart('headers', $this->parts[1]); + foreach ($headers as &$value) { + if (is_array($value)) { + foreach ($value as &$v) { + $v = $this->decodeSingleHeader($v); + } + } else { + $value = $this->decodeSingleHeader($value); + } + } + + return $headers; + } else { + throw new Exception( + 'setPath() or setText() or setStream() must be called before retrieving email headers.' + ); + } + } + + /** + * Retrieve the raw mail headers as a string + * + * @return string + * @throws Exception + */ + public function getHeadersRaw() + { + if (isset($this->parts[1])) { + return $this->getPartHeader($this->parts[1]); + } else { + throw new Exception( + 'setPath() or setText() or setStream() must be called before retrieving email headers.' + ); + } + } + + /** + * Retrieve the raw Header of a MIME part + * + * @return String + * @param $part Object + * @throws Exception + */ + protected function getPartHeader(&$part) + { + $header = ''; + if ($this->stream) { + $header = $this->getPartHeaderFromFile($part); + } elseif ($this->data) { + $header = $this->getPartHeaderFromText($part); + } + return $header; + } + + /** + * Retrieve the Header from a MIME part from file + * + * @return String Mime Header Part + * @param $part Array + */ + protected function getPartHeaderFromFile(&$part) + { + $start = $part['starting-pos']; + $end = $part['starting-pos-body']; + fseek($this->stream, $start, SEEK_SET); + $header = fread($this->stream, $end - $start); + return $header; + } + + /** + * Retrieve the Header from a MIME part from text + * + * @return String Mime Header Part + * @param $part Array + */ + protected function getPartHeaderFromText(&$part) + { + $start = $part['starting-pos']; + $end = $part['starting-pos-body']; + $header = substr($this->data, $start, $end - $start); + return $header; + } + + /** + * Checks whether a given part ID is a child of another part + * eg. an RFC822 attachment may have one or more text parts + * + * @param string $partId + * @param string $parentPartId + * @return bool + */ + protected function partIdIsChildOfPart($partId, $parentPartId) + { + $parentPartId = $parentPartId.'.'; + return substr($partId, 0, strlen($parentPartId)) == $parentPartId; + } + + /** + * Whether the given part ID is a child of any attachment part in the message. + * + * @param string $checkPartId + * @return bool + */ + protected function partIdIsChildOfAnAttachment($checkPartId) + { + foreach ($this->parts as $partId => $part) { + if ($this->getPart('content-disposition', $part) == 'attachment') { + if ($this->partIdIsChildOfPart($checkPartId, $partId)) { + return true; + } + } + } + return false; + } + + /** + * Returns the email message body in the specified format + * + * @param string $type text, html or htmlEmbedded + * + * @return string Body + * @throws Exception + */ + public function getMessageBody($type = 'text') + { + $mime_types = [ + 'text' => 'text/plain', + 'html' => 'text/html', + 'htmlEmbedded' => 'text/html', + ]; + + if (in_array($type, array_keys($mime_types))) { + $part_type = $type === 'htmlEmbedded' ? 'html' : $type; + $inline_parts = $this->getInlineParts($part_type); + $body = empty($inline_parts) ? '' : $inline_parts[0]; + } else { + throw new Exception( + 'Invalid type specified for getMessageBody(). Expected: text, html or htmlEmbeded.' + ); + } + + if ($type == 'htmlEmbedded') { + $attachments = $this->getAttachments(); + foreach ($attachments as $attachment) { + if ($attachment->getContentID() != '') { + $body = str_replace( + '"cid:'.$attachment->getContentID().'"', + '"'.$this->getEmbeddedData($attachment->getContentID()).'"', + $body + ); + } + } + } + + return $body; + } + + /** + * Returns the embedded data structure + * + * @param string $contentId Content-Id + * + * @return string + */ + protected function getEmbeddedData($contentId) + { + foreach ($this->parts as $part) { + if ($this->getPart('content-id', $part) == $contentId) { + $embeddedData = 'data:'; + $embeddedData .= $this->getPart('content-type', $part); + $embeddedData .= ';'.$this->getPart('transfer-encoding', $part); + $embeddedData .= ','.$this->getPartBody($part); + return $embeddedData; + } + } + return ''; + } + + /** + * Return an array with the following keys display, address, is_group + * + * @param string $name Header name (case-insensitive) + * + * @return array + */ + public function getAddresses($name) + { + $value = $this->getRawHeader($name); + $value = (is_array($value)) ? $value[0] : $value; + $addresses = mailparse_rfc822_parse_addresses($value); + foreach ($addresses as $i => $item) { + $addresses[$i]['display'] = $this->decodeHeader($item['display']); + } + return $addresses; + } + + /** + * Returns the attachments contents in order of appearance + * + * @return Attachment[] + */ + public function getInlineParts($type = 'text') + { + $inline_parts = []; + $mime_types = [ + 'text' => 'text/plain', + 'html' => 'text/html', + ]; + + if (!in_array($type, array_keys($mime_types))) { + throw new Exception('Invalid type specified for getInlineParts(). "type" can either be text or html.'); + } + + foreach ($this->parts as $partId => $part) { + if ($this->getPart('content-type', $part) == $mime_types[$type] + && $this->getPart('content-disposition', $part) != 'attachment' + && !$this->partIdIsChildOfAnAttachment($partId) + ) { + $headers = $this->getPart('headers', $part); + $encodingType = array_key_exists('content-transfer-encoding', $headers) ? + $headers['content-transfer-encoding'] : ''; + $undecoded_body = $this->decodeContentTransfer($this->getPartBody($part), $encodingType); + $inline_parts[] = $this->charset->decodeCharset($undecoded_body, $this->getPartCharset($part)); + } + } + + return $inline_parts; + } + + /** + * Returns the attachments contents in order of appearance + * + * @return Attachment[] + */ + public function getAttachments($include_inline = true) + { + $attachments = []; + $dispositions = $include_inline ? ['attachment', 'inline'] : ['attachment']; + $non_attachment_types = ['text/plain', 'text/html']; + $nonameIter = 0; + + foreach ($this->parts as $part) { + $disposition = $this->getPart('content-disposition', $part); + $filename = 'noname'; + + if (isset($part['disposition-filename'])) { + $filename = $this->decodeHeader($part['disposition-filename']); + } elseif (isset($part['content-name'])) { + // if we have no disposition but we have a content-name, it's a valid attachment. + // we simulate the presence of an attachment disposition with a disposition filename + $filename = $this->decodeHeader($part['content-name']); + $disposition = 'attachment'; + } elseif (in_array($part['content-type'], $non_attachment_types, true) + && $disposition !== 'attachment') { + // it is a message body, no attachment + continue; + } elseif (substr($part['content-type'], 0, 10) !== 'multipart/' + && $part['content-type'] !== 'text/plain; (error)') { + // if we cannot get it by getMessageBody(), we assume it is an attachment + $disposition = 'attachment'; + } + if (in_array($disposition, ['attachment', 'inline']) === false && !empty($disposition)) { + $disposition = 'attachment'; + } + + if (in_array($disposition, $dispositions) === true) { + if ($filename == 'noname') { + $nonameIter++; + $filename = 'noname'.$nonameIter; + } else { + // Escape all potentially unsafe characters from the filename + $filename = preg_replace('((^\.)|\/|[\n|\r|\n\r]|(\.$))', '_', $filename); + } + + $headersAttachments = $this->getPart('headers', $part); + $contentidAttachments = $this->getPart('content-id', $part); + + $attachmentStream = $this->getAttachmentStream($part); + $mimePartStr = $this->getPartComplete($part); + + $attachments[] = new Attachment( + $filename, + $this->getPart('content-type', $part), + $attachmentStream, + $disposition, + $contentidAttachments, + $headersAttachments, + $mimePartStr + ); + } + } + + return $attachments; + } + + /** + * Save attachments in a folder + * + * @param string $attach_dir directory + * @param bool $include_inline + * @param string $filenameStrategy How to generate attachment filenames + * + * @return array Saved attachments paths + * @throws Exception + */ + public function saveAttachments( + $attach_dir, + $include_inline = true, + $filenameStrategy = self::ATTACHMENT_DUPLICATE_SUFFIX + ) { + $attachments = $this->getAttachments($include_inline); + + $attachments_paths = []; + foreach ($attachments as $attachment) { + $attachments_paths[] = $attachment->save($attach_dir, $filenameStrategy); + } + + return $attachments_paths; + } + + /** + * Read the attachment Body and save temporary file resource + * + * @param array $part + * + * @return resource Mime Body Part + * @throws Exception + */ + protected function getAttachmentStream(&$part) + { + /** @var resource $temp_fp */ + $temp_fp = tmpfile(); + + $headers = $this->getPart('headers', $part); + $encodingType = array_key_exists('content-transfer-encoding', $headers) ? + $headers['content-transfer-encoding'] : ''; + + if ($temp_fp) { + if ($this->stream) { + $start = $part['starting-pos-body']; + $end = $part['ending-pos-body']; + fseek($this->stream, $start, SEEK_SET); + $len = $end - $start; + $written = 0; + while ($written < $len) { + $write = $len; + $data = fread($this->stream, $write); + fwrite($temp_fp, $this->decodeContentTransfer($data, $encodingType)); + $written += $write; + } + } elseif ($this->data) { + $attachment = $this->decodeContentTransfer($this->getPartBodyFromText($part), $encodingType); + fwrite($temp_fp, $attachment, strlen($attachment)); + } + fseek($temp_fp, 0, SEEK_SET); + } else { + throw new Exception( + 'Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.' + ); + } + + return $temp_fp; + } + + /** + * Decode the string from Content-Transfer-Encoding + * + * @param string $encodedString The string in its original encoded state + * @param string $encodingType The encoding type from the Content-Transfer-Encoding header of the part. + * + * @return string The decoded string + */ + protected function decodeContentTransfer($encodedString, $encodingType) + { + if (is_array($encodingType)) { + $encodingType = $encodingType[0]; + } + + $encodingType = strtolower($encodingType); + if ($encodingType == 'base64') { + return base64_decode($encodedString); + } elseif ($encodingType == 'quoted-printable') { + return quoted_printable_decode($encodedString); + } else { + return $encodedString; + } + } + + /** + * $input can be a string or array + * + * @param string|array $input + * + * @return string + */ + protected function decodeHeader($input) + { + //Sometimes we have 2 label From so we take only the first + if (is_array($input)) { + return $this->decodeSingleHeader($input[0]); + } + + return $this->decodeSingleHeader($input); + } + + /** + * Decodes a single header (= string) + * + * @param string $input + * + * @return string + */ + protected function decodeSingleHeader($input) + { + // For each encoded-word... + while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)((\s+)=\?)?/i', $input, $matches)) { + $encoded = $matches[1]; + $charset = $matches[2]; + $encoding = $matches[3]; + $text = $matches[4]; + $space = isset($matches[6]) ? $matches[6] : ''; + + switch (strtolower($encoding)) { + case 'b': + $text = $this->decodeContentTransfer($text, 'base64'); + break; + + case 'q': + $text = str_replace('_', ' ', $text); + preg_match_all('/=([a-f0-9]{2})/i', $text, $matches); + foreach ($matches[1] as $value) { + $text = str_replace('='.$value, chr(hexdec($value)), $text); + } + break; + } + + $text = $this->charset->decodeCharset($text, $this->charset->getCharsetAlias($charset)); + $input = str_replace($encoded.$space, $text, $input); + } + + return $input; + } + + /** + * Return the charset of the MIME part + * + * @param array $part + * + * @return string + */ + protected function getPartCharset($part) + { + if (isset($part['charset'])) { + return $this->charset->getCharsetAlias($part['charset']); + } else { + return 'us-ascii'; + } + } + + /** + * Retrieve a specified MIME part + * + * @param string $type + * @param array $parts + * + * @return string|array + */ + protected function getPart($type, $parts) + { + return (isset($parts[$type])) ? $parts[$type] : false; + } + + /** + * Retrieve the Body of a MIME part + * + * @param array $part + * + * @return string + */ + protected function getPartBody(&$part) + { + $body = ''; + if ($this->stream) { + $body = $this->getPartBodyFromFile($part); + } elseif ($this->data) { + $body = $this->getPartBodyFromText($part); + } + + return $body; + } + + /** + * Retrieve the Body from a MIME part from file + * + * @param array $part + * + * @return string Mime Body Part + */ + protected function getPartBodyFromFile(&$part) + { + $start = $part['starting-pos-body']; + $end = $part['ending-pos-body']; + $body = ''; + if ($end - $start > 0) { + fseek($this->stream, $start, SEEK_SET); + $body = fread($this->stream, $end - $start); + } + + return $body; + } + + /** + * Retrieve the Body from a MIME part from text + * + * @param array $part + * + * @return string Mime Body Part + */ + protected function getPartBodyFromText(&$part) + { + $start = $part['starting-pos-body']; + $end = $part['ending-pos-body']; + + return substr($this->data, $start, $end - $start); + } + + /** + * Retrieve the content of a MIME part + * + * @param array $part + * + * @return string + */ + protected function getPartComplete(&$part) + { + $body = ''; + if ($this->stream) { + $body = $this->getPartFromFile($part); + } elseif ($this->data) { + $body = $this->getPartFromText($part); + } + + return $body; + } + + /** + * Retrieve the content from a MIME part from file + * + * @param array $part + * + * @return string Mime Content + */ + protected function getPartFromFile(&$part) + { + $start = $part['starting-pos']; + $end = $part['ending-pos']; + $body = ''; + if ($end - $start > 0) { + fseek($this->stream, $start, SEEK_SET); + $body = fread($this->stream, $end - $start); + } + + return $body; + } + + /** + * Retrieve the content from a MIME part from text + * + * @param array $part + * + * @return string Mime Content + */ + protected function getPartFromText(&$part) + { + $start = $part['starting-pos']; + $end = $part['ending-pos']; + + return substr($this->data, $start, $end - $start); + } + + /** + * Retrieve the resource + * + * @return resource resource + */ + public function getResource() + { + return $this->resource; + } + + /** + * Retrieve the file pointer to email + * + * @return resource stream + */ + public function getStream() + { + return $this->stream; + } + + /** + * Retrieve the text of an email + * + * @return string data + */ + public function getData() + { + return $this->data; + } + + /** + * Retrieve the parts of an email + * + * @return array parts + */ + public function getParts() + { + return $this->parts; + } + + /** + * Retrieve the charset manager object + * + * @return CharsetManager charset + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Add a middleware to the parser MiddlewareStack + * Each middleware is invoked when: + * a MimePart is retrieved by mailparse_msg_get_part_data() during $this->parse() + * The middleware will receive MimePart $part and the next MiddlewareStack $next + * + * Eg: + * + * $Parser->addMiddleware(function(MimePart $part, MiddlewareStack $next) { + * // do something with the $part + * return $next($part); + * }); + * + * @param callable $middleware Plain Function or Middleware Instance to execute + * @return void + */ + public function addMiddleware(callable $middleware) + { + if (!$middleware instanceof Middleware) { + $middleware = new Middleware($middleware); + } + $this->middlewareStack = $this->middlewareStack->add($middleware); + } +} diff --git a/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0.zip b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0.zip new file mode 100644 index 00000000..87c8087b Binary files /dev/null and b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0.zip differ diff --git a/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/.github/workflows/main.yml b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/.github/workflows/main.yml new file mode 100644 index 00000000..e1db814a --- /dev/null +++ b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/.github/workflows/main.yml @@ -0,0 +1,63 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +jobs: + + tests: + runs-on: ubuntu-latest + strategy: + matrix: + php: [8.0, 8.1, 8.2] + dependency-version: [prefer-lowest, prefer-stable] + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: mailparse + coverage: none + + - name: Install dependencies + run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest --no-progress + + - name: Execute tests + run: vendor/bin/phpunit + + php-cs: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install dependencies + run: composer update --no-progress --ignore-platform-reqs + + - name: Execute phpcs + run: vendor/bin/phpcs src tests --standard=psr2 + + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + extensions: mailparse + + - name: Install dependencies + run: composer update --prefer-dist --no-interaction --no-suggest --no-progress + + - name: Execute code coverage + run: | + vendor/bin/phpunit --coverage-clover=coverage.xml --whitelist src + bash <(curl -s https://codecov.io/bash) -t ${{ secrets.COVERALLS_REPO_TOKEN }} diff --git a/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/LICENSE b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/LICENSE new file mode 100644 index 00000000..ab85686e --- /dev/null +++ b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Vincent Dauce + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/README.md b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/README.md new file mode 100644 index 00000000..9aeacd5c --- /dev/null +++ b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/README.md @@ -0,0 +1,257 @@ +# php-mime-mail-parser + +A fully tested email parser for PHP 8.0+ (mailparse extension wrapper). + +It's the most effective php email parser around in terms of performance, foreign character encoding, attachment handling, and ease of use. +Internet Message Format RFC [822](https://tools.ietf.org/html/rfc822), [2822](https://tools.ietf.org/html/rfc2822), [5322](https://tools.ietf.org/html/rfc5322). + +[![Latest Version](https://img.shields.io/packagist/v/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://github.com/php-mime-mail-parser/php-mime-mail-parser/releases) +[![Total Downloads](https://img.shields.io/packagist/dt/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://packagist.org/packages/php-mime-mail-parser/php-mime-mail-parser) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +## Why? + +This extension can be used to... + * Parse and read email from Postfix + * For reading messages (Filename extension: eml) + * Create webmail + * Store email information such a subject, HTML body, attachments, and etc. into a database + +## Is it reliable? + +Yes. All known issues have been reproduced, fixed and tested. + +We use GitHub Actions, Codecov, Codacy to help ensure code quality. You can see real-time statistics below: + +[![CI](https://github.com/php-mime-mail-parser/php-mime-mail-parser/actions/workflows/main.yml/badge.svg?style=flat-square)](https://github.com/php-mime-mail-parser/php-mime-mail-parser/actions/workflows/main.yml) +[![Coverage](https://codecov.io/gh/php-mime-mail-parser/php-mime-mail-parser/branch/main/graph/badge.svg?token=wTSIbXJDL0)](https://codecov.io/gh/php-mime-mail-parser/php-mime-mail-parser) +[![Code Quality](https://app.codacy.com/project/badge/Grade/8cbfe0fcd84c4b2b9282b9a0b4467607)](https://www.codacy.com/gh/php-mime-mail-parser/php-mime-mail-parser/dashboard?utm_source=github.com&utm_medium=referral&utm_content=php-mime-mail-parser/php-mime-mail-parser&utm_campaign=Badge_Grade) + +## How do I install it? + +The easiest way is via [Composer](https://getcomposer.org/). + +To install the latest version of PHP MIME Mail Parser, run the command below: + + composer require php-mime-mail-parser/php-mime-mail-parser + +## Requirements + +The following versions of PHP are supported: + +* PHP 8.0 +* PHP 8.1 +* PHP 8.2 + +Previous Versions: + +| PHP Compatibility | Version | +|-------------------|-----------------------------| +| HHVM | php-mime-mail-parser 2.11.1 | +| PHP 5.4 | php-mime-mail-parser 2.11.1 | +| PHP 5.5 | php-mime-mail-parser 2.11.1 | +| PHP 5.6 | php-mime-mail-parser 3.0.4 | +| PHP 7.0 | php-mime-mail-parser 3.0.4 | +| PHP 7.1 | php-mime-mail-parser 5.0.5 | +| PHP 7.2 | php-mime-mail-parser 7.1.2 | +| PHP 7.3 | php-mime-mail-parser 7.1.2 | +| PHP 7.4 | php-mime-mail-parser 7.1.2 | + +Make sure you have the mailparse extension (http://php.net/manual/en/book.mailparse.php) properly installed. The command line `php -m | grep mailparse` need to return "mailparse". + + +### Install mailparse extension + +#### Ubuntu, Debian & derivatives +``` +sudo apt install php-cli php-mailparse +``` + +#### Others platforms +``` +sudo apt install php-cli php-pear php-dev php-mbstring +pecl install mailparse +``` + +#### From source + +AAAAMMDD should be `php-config --extension-dir` +``` +git clone https://github.com/php/pecl-mail-mailparse.git +cd pecl-mail-mailparse +phpize +./configure +sed -i 's/#if\s!HAVE_MBSTRING/#ifndef MBFL_MBFILTER_H/' ./mailparse.c +make +sudo mv modules/mailparse.so /usr/lib/php/AAAAMMDD/ +echo "extension=mailparse.so" | sudo tee /etc/php/7.1/mods-available/mailparse.ini +sudo phpenmod mailparse +``` + +#### Windows +You need to download mailparse DLL from http://pecl.php.net/package/mailparse and add the line "extension=php_mailparse.dll" to php.ini accordingly. + +## How do I use it? + +### Loading an email + +You can load an email with 4 differents ways. You only need to use one of the following four. + +```php +require_once __DIR__.'/vendor/autoload.php'; + +$path = 'path/to/email.eml'; +$parser = new PhpMimeMailParser\Parser(); + +// 1. Specify a file path (string) +$parser->setPath($path); + +// 2. Specify the raw mime mail text (string) +$parser->setText(file_get_contents($path)); + +// 3. Specify a php file resource (stream) +$parser->setStream(fopen($path, "r")); + +// 4. Specify a stream to work with mail server (stream) +$parser->setStream(fopen("php://stdin", "r")); +``` + +### Get the metadata of the message + +Get the sender and the receiver: + +```php +$rawHeaderTo = $parser->getHeader('to'); +// return "test" , "test2" + +$arrayHeaderTo = $parser->getAddresses('to'); +// return [["display"=>"test", "address"=>"test@example.com", false]] + +$rawHeaderFrom = $parser->getHeader('from'); +// return "test" + +$arrayHeaderFrom = $parser->getAddresses('from'); +// return [["display"=>"test", "address"=>"test@example.com", "is_group"=>false]] +``` + +Get the subject: + +```php +$subject = $parser->getHeader('subject'); +``` + +Get other headers: + +```php +$stringHeaders = $parser->getHeadersRaw(); +// return all headers as a string, no charset conversion + +$arrayHeaders = $parser->getHeaders(); +// return all headers as an array, with charset conversion +``` + +### Get the body of the message + +```php +$text = $parser->getMessageBody('text'); +// return the text version + +$html = $parser->getMessageBody('html'); +// return the html version + +$htmlEmbedded = $parser->getMessageBody('htmlEmbedded'); +// return the html version with the embedded contents like images + +``` + +### Get attachments + +Save all attachments in a directory + +```php +$parser->saveAttachments('/path/to/save/attachments/'); +// return all attachments saved in the directory (include inline attachments) + +$parser->saveAttachments('/path/to/save/attachments/', false); +// return all attachments saved in the directory (exclude inline attachments) + +// Save all attachments with the strategy ATTACHMENT_DUPLICATE_SUFFIX (default) +$parser->saveAttachments('/path/to/save/attachments/', false, Parser::ATTACHMENT_DUPLICATE_SUFFIX); +// return all attachments saved in the directory: logo.jpg, logo_1.jpg, ..., logo_100.jpg, YY34UFHBJ.jpg + +// Save all attachments with the strategy ATTACHMENT_RANDOM_FILENAME +$parser->saveAttachments('/path/to/save/attachments/', false, Parser::ATTACHMENT_RANDOM_FILENAME); +// return all attachments saved in the directory: YY34UFHBJ.jpg and F98DBZ9FZF.jpg + +// Save all attachments with the strategy ATTACHMENT_DUPLICATE_THROW +$parser->saveAttachments('/path/to/save/attachments/', false, Parser::ATTACHMENT_DUPLICATE_THROW); +// return an exception when there is attachments duplicate. + +``` + +Get all attachments + +```php +$attachments = $parser->getAttachments(); +// return an array of all attachments (include inline attachments) + +$attachments = $parser->getAttachments(false); +// return an array of all attachments (exclude inline attachments) +``` + + +Loop through all the Attachments +```php +foreach ($attachments as $attachment) { + echo 'Filename : '.$attachment->getFilename().'
'; + // return logo.jpg + + echo 'Filesize : '.filesize($attach_dir.$attachment->getFilename()).'
'; + // return 1000 + + echo 'Filetype : '.$attachment->getContentType().'
'; + // return image/jpeg + + echo 'MIME part string : '.$attachment->getMimePartStr().'
'; + // return the whole MIME part of the attachment + + $attachment->save('/path/to/save/myattachment/', Parser::ATTACHMENT_DUPLICATE_SUFFIX); + // return the path and the filename saved (same strategy available than saveAttachments) +} +``` + +## Postfix configuration to manage email from a mail server + +Next you need to forward emails to this script above. For that I'm using [Postfix](http://www.postfix.org/) like a mail server, you need to configure /etc/postfix/master.cf + +Add this line at the end of the file (specify myhook to send all emails to the script test.php) +``` +myhook unix - n n - - pipe + flags=F user=www-data argv=php -c /etc/php5/apache2/php.ini -f /var/www/test.php ${sender} ${size} ${recipient} +``` + +Edit this line (register myhook) +``` +smtp inet n - - - - smtpd + -o content_filter=myhook:dummy +``` + +The php script must use the fourth method to work with this configuration. + +And finally the easiest way is to use my SaaS https://mailcare.io + + +## Can I contribute? + +Feel free to contribute! + + git clone https://github.com/php-mime-mail-parser/php-mime-mail-parser + cd php-mime-mail-parser + composer install + ./vendor/bin/phpunit + +If you report an issue, please provide the raw email that triggered it. This helps us reproduce the issue and fix it more quickly. + +## License + +The php-mime-mail-parser/php-mime-mail-parser is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) diff --git a/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/compile_mailparse.sh b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/compile_mailparse.sh new file mode 100644 index 00000000..8505077f --- /dev/null +++ b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/compile_mailparse.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +git clone https://github.com/php/pecl-mail-mailparse.git +cd pecl-mail-mailparse +phpize +./configure +sed -i 's/#if\s!HAVE_MBSTRING/#ifndef MBFL_MBFILTER_H/' ./mailparse.c +make +sudo mv modules/mailparse.so /home/travis/.phpenv/versions/7.3.2/lib/php/extensions/no-debug-zts-20180731/ +echo 'extension=mailparse.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini \ No newline at end of file diff --git a/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/composer.json b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/composer.json new file mode 100644 index 00000000..6cc88c42 --- /dev/null +++ b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/composer.json @@ -0,0 +1,60 @@ +{ + "name": "php-mime-mail-parser/php-mime-mail-parser", + "type": "library", + "description": "A fully tested email parser for PHP 8.0+ (mailparse extension wrapper).", + "keywords": ["mime", "mail", "mailparse", "MimeMailParser", "parser", "php"], + "homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser", + "license": "MIT", + "authors": [ + { + "name":"eXorus", + "email":"exorus.spam@gmail.com", + "homepage":"https://github.com/eXorus/", + "role":"Developer" + }, + { + "name":"M.Valinskis", + "email":"M.Valins@gmail.com", + "homepage":"https://code.google.com/p/php-mime-mail-parser", + "role":"Developer" + }, + { + "name":"eugene.emmett.wood", + "email":"gene_w@cementhorizon.com", + "homepage":"https://code.google.com/p/php-mime-mail-parser", + "role":"Developer" + }, + { + "name":"alknetso", + "email":"alkne@gmail.com", + "homepage":"https://code.google.com/p/php-mime-mail-parser", + "role":"Developer" + }, + { + "name":"bucabay", + "email":"gabe@fijiwebdesign.com", + "homepage":"http://www.fijiwebdesign.com", + "role":"Developer" + } + ], + "repository":{ + "type":"git", + "url":"https://github.com/php-mime-mail-parser/php-mime-mail-parser.git" + }, + "require": { + "php": "^8.0", + "ext-mailparse": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "php-coveralls/php-coveralls": "^2.2", + "squizlabs/php_codesniffer": "^3.5" + }, + "replace": { + "exorus/php-mime-mail-parser": "*", + "messaged/php-mime-mail-parser": "*" + }, + "autoload": { + "psr-4": { "PhpMimeMailParser\\": "src/" } + } +} diff --git a/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/mailparse-stubs.php b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/mailparse-stubs.php new file mode 100644 index 00000000..e2b5e0e2 --- /dev/null +++ b/plugins/php-mime-mail-parser/src/php-mime-mail-parser-8.0.0/mailparse-stubs.php @@ -0,0 +1,303 @@ + + + + tests + +