From 275f75b835d35c3eb67a6e7cbbcfcf7abf2ad6fa Mon Sep 17 00:00:00 2001 From: uckelman Date: Tue, 2 Nov 2010 22:15:27 +0000 Subject: Refactored to use mailparse instead of Mail_mimeDecode. This solves the multipart/alternative bug (Mail_mimeDecode produced no output for such parts!). git-svn-id: https://vassalengine.svn.sourceforge.net/svnroot/vassalengine/site-src/trunk@7441 67b53d14-2c14-4ace-a08f-0dab2b34000c --- src/EmailMessage.php | 210 ++++++++++++++++++++++++++++++------------------- src/MailmanMessage.php | 8 +- src/Message.php | 2 - 3 files changed, 136 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/EmailMessage.php b/src/EmailMessage.php index 69b7ab0..22f012e 100644 --- a/src/EmailMessage.php +++ b/src/EmailMessage.php @@ -20,137 +20,189 @@ # along with this program. If not, see . # -# FIXME: maybe use Mailparse instead of Mail_mimeDecode - -require_once('Mail/mimeDecode.php'); -require_once('Mail/RFC822.php'); - require_once(__DIR__ . '/Message.php'); abstract class EmailMessage implements Message { + protected $data; protected $msg; - + protected $parts; + public function __construct($input) { - $this->msg = self::decode_raw_message($input); + $this->data = &$input; + + # build the message structure + $this->msg = mailparse_msg_create(); + mailparse_msg_parse($this->msg, $this->data); + + # get the part data + $this->parts = array(); + foreach (mailparse_msg_get_structure($this->msg) as $part_id) { + $part = mailparse_msg_get_part($this->msg, $part_id); + $this->parts[$part_id] = mailparse_msg_get_part_data($part); + } + } + + public function __destruct() { + mailparse_msg_free($this->msg); } public function getPostId() { return null; } + protected function getHeader($name) { + return $this->getPartHeader($this->parts[1], $name); + } + + protected function getPartHeaders(&$part_data) { + $headers = &$part_data['headers']; + return $headers; + } + + protected function getPartHeader(&$part_data, $name) { + $headers = $this->getPartHeaders($part_data); + return array_key_exists($name, $headers) ? $headers[$name] : false; + } + public function getFrom() { - return self::parse_addr($this->msg->headers['from']); + $from = mailparse_rfc822_parse_addresses($this->getHeader('from')); + return $from[0]['address']; } public function getSubject() { - return $this->msg->headers['subject']; + return $this->getHeader('subject'); } public function getMessageId() { - return $this->msg->headers['message-id']; + return $this->getHeader('message-id'); } public function getInReplyTo() { - return $this->msg->headers['in-reply-to']; + return $this->getHeader('in-reply-to'); } public function getReferences() { - return $this->msg->headers['references']; + return $this->getHeader('references'); } public function getParts() { - return $this->msg; + } + + protected function getPartBody(&$part_data) { + $beg = $part_data['starting-pos-body']; + $end = $part_data['ending-pos-body']; + return substr($this->data, $beg, $end-$beg); + } + + protected function decode($str, $encoding) { + if ($encoding == 'base64') { + return base64_decode($str); + } + else if ($encoding == 'quoted-printable') { + return quoted_printable_decode($str); + } + else { + return $str; + } } public function getFlattenedParts() { $text = ''; $attachments = array(); - self::flatten_parts($this->msg, $text, $attachments); + $this->flatten_parts('1', $this->parts[1], $text, $attachments); return array($text, $attachments); } - protected static function decode_raw_message($input) { - $params['include_bodies'] = true; - $params['decode_bodies'] = true; - $params['decode_headers'] = true; - $params['input'] = $input; - $params['crlf'] = "\r\n"; - - $msg = Mail_mimeDecode::decode($params); - - if (count($msg->headers) == 1 && array_key_exists(null, $msg->headers)) { - # An empty message has one null header. - throw new Exception('No message'); + protected function flatten_subparts($part_id, &$text, &$attachments) { + for ($i = 1, $child_id = "$part_id.$i"; + array_key_exists($child_id, $this->parts); + ++$i, $child_id = "$part_id.$i") + { + $child = $this->parts[$child_id]; + $this->flatten_parts($child_id, $child, $text, $attachments); } - - return $msg; } - protected static function parse_addr($s) { - $addr = Mail_RFC822::parseAddressList($s); - return strtolower($addr[0]->mailbox . '@' . $addr[0]->host); - } + protected function flatten_parts($part_id, &$part_data, + &$text, &$attachments) { + $type = $part_data['content-type']; + list($major, $minor) = split('/', $type, 2); - protected static function flatten_parts($part, &$text, &$attachments) { - switch ($part->ctype_primary) { + switch ($major) { case 'multipart': - if (!isset($part->parts)) { - throw new Exception('multipart without parts!'); - } + switch ($minor) { + case 'alternative': + # check alternatives for text/plain + $plain = false; + + for ($i = 1, $child_id = "$part_id.$i"; + array_key_exists($child_id, $this->parts); + ++$i, $child_id = "$part_id.$i") + { + $child = $this->parts[$child_id]; + + $ctype = $child['content-type']; + if ($ctype == 'text/plain') { + # keep text/plain, chuck the rest + $this->flatten_parts($child_id, $child, $text, $attachments); + $plain = true; + break; + } + } - foreach ($part->parts as $subpart) { - self::flatten_parts($subpart, $text, $attachments); - } + if (!$plain) { + # no text/plain, handle the subparts as attachments + $this->flatten_subparts($part_id, $text, $attachments); + } + break; + + case 'mixed': + default: + # handle all subparts + $this->flatten_subparts($part_id, $text, $attachments); + break; + } break; case 'text': - # text/* parts go into the message body. - if (!isset($part->body)) { - throw new Exception('text without body!'); + # NB: We don't worry about text/html here because Mailman will have + # already stripped it. + + # Text is appended to the main text. + $enc = $this->getPartHeader($part_data, 'content-transfer-encoding'); + $body = $this->getPartBody($part_data); + $body = $this->decode($body, $enc); + + $charset = $part_data['content-charset']; + if (strtoupper($charset) != 'UTF-8') { + if (mb_check_encoding($body, $charset)) { + $body = mb_convert_encoding($body, 'UTF-8', $charset); + } + else { + $body = mb_convert_encoding($body, 'UTF-8'); + } } - $text .= $part->body; + $text .= "$body\n"; break; default: # Everything else goes into phpBB as an attachment. - if (!isset($part->body)) { - throw new Exception('attachment without body!'); - } - - # try to find a filename - $filename = ''; - if (isset($part->d_parameters)) { - if (array_key_exists('filename', $part->d_parameters)) { - $filename = $part->d_parameters['filename']; - } - else if (array_key_exists('name', $part->d_parameters)) { - $filename = $part->d_parameters['name']; - } + $enc = $this->getPartHeader($part_data, 'content-transfer-encoding'); + $data = $this->getPartBody($part_data); + $data = $this->decode($data, $enc); + +echo "$major\n"; + $disp = $part_data['content-disposition']; + if ($disp == 'attachment' || $disp == 'inline') { + $attachments[] = array( + 'filename' => $part_data['disposition-filename'], + 'mimetype' => $part_data['content-type'], + 'comment' => $part_data['content-description'], + 'data' => $data + ); } - - if ($filename == '') { - if (isset($part->ctype_parameters)) { - if (array_key_exists('name', $part->ctype_parameters)) { - $filename = $part->d_parameters['name']; - } - } - } - - $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary; - - $comment = array_key_exists('content-description', $part->headers) ? - $part->headers['content-description'] : ''; - - $params = array( - 'filename' => $filename, - 'mimetype' => $mimetype, - 'comment' => $comment, - 'data' => $part->body - ); - - $attachments[] = $params; } } } diff --git a/src/MailmanMessage.php b/src/MailmanMessage.php index dfd100e..6ec35c6 100644 --- a/src/MailmanMessage.php +++ b/src/MailmanMessage.php @@ -28,9 +28,11 @@ class MailmanMessage extends EmailMessage { } public function getSource() { - return self::parse_addr( - substr_replace($this->msg->headers['list-post'], '', 1, 7) - ); + # remove 'mailto:' + $lp = substr_replace($this->getHeader('list-post'), '', 1, 7); + + $src = mailparse_rfc822_parse_addresses($lp); + return $src[0]['address']; } } diff --git a/src/Message.php b/src/Message.php index 9bb082d..00e956a 100644 --- a/src/Message.php +++ b/src/Message.php @@ -34,8 +34,6 @@ interface Message { public function getInReplyTo(); public function getReferences(); - - public function getParts(); } ?> -- cgit v1.2.3