From be02c8d43cc26a45688c6633dc1455c10b557627 Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Mon, 23 Sep 2013 20:29:27 +0000 Subject: [PATCH] Detect attachments with Content-Type: ...; name= RFC2045, section 5.1 seems to indicate that arbitrary parameters can be appended to a Content-Type header specification. Some email clients seem to use the Content-Type header to specify an attachment without giving a separate Content-Disposition header normally used to signify attachments. This patch corrects attachment detection for piped emails to detect such emails. The patch also correctly decodes filenames specified either in the Content-Disposition or Content-Type headers using RFC5987, where the filename can be encoded using an arbitrary character set (ie, not us-ascii). --- include/class.mailparse.php | 56 ++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/include/class.mailparse.php b/include/class.mailparse.php index b9ab3c33f..b02f2448a 100644 --- a/include/class.mailparse.php +++ b/include/class.mailparse.php @@ -199,18 +199,60 @@ class Mail_Parse { return Format::encode($text, $charset, $encoding); } + /** + * Decodes filenames given in the content-disposition header according + * to RFC5987, such as filename*=utf-8''filename.png. Note that the + * language sub-component is defined in RFC5646, and that the filename + * is URL encoded (in the charset specified) + */ + function decodeRfc5987($filename) { + $match = array(); + if (preg_match("/([\w!#$%&+^_`{}~-]+)'([\w-]*)'(.*)$/", + $filename, $match)) + // XXX: Currently we don't care about the language component. + // The encoding hint is sufficient. + return self::mime_encode(urldecode($match[3]), $match[1]); + else + return $filename; + } + function getAttachments($part=null){ if($part==null) $part=$this->getStruct(); - if($part && $part->disposition - && (!strcasecmp($part->disposition,'attachment') - || !strcasecmp($part->disposition,'inline') - || !strcasecmp($part->ctype_primary,'image'))){ - - if(!($filename=$part->d_parameters['filename']) && $part->d_parameters['filename*']) - $filename=$part->d_parameters['filename*']; //Do we need to decode? + /* Consider this part as an attachment if + * * It has a Content-Disposition header + * * AND it is specified as either 'attachment' or 'inline' + * * The Content-Type header specifies + * * type is image/* or application/* + * * has a name parameter + */ + if($part && ( + ($part->disposition + && (!strcasecmp($part->disposition,'attachment') + || !strcasecmp($part->disposition,'inline')) + ) + || (!strcasecmp($part->ctype_primary,'image') + || !strcasecmp($part->ctype_primary,'application')))) { + + if (isset($part->d_parameters['filename'])) + $filename = $part->d_parameters['filename']; + elseif (isset($part->d_parameters['filename*'])) + // Support RFC 6266, section 4.3 and RFC, and RFC 5987 + $filename = self::decodeRfc5987( + $part->d_parameters['filename*']); + + // Support attachments that do not specify a content-disposition + // but do specify a "name" parameter in the content-type header. + elseif (isset($part->ctype_parameters['name'])) + $filename=$part->ctype_parameters['name']; + elseif (isset($part->ctype_parameters['name*'])) + $filename = self::decodeRfc5987( + $part->ctype_parameters['name*']); + else + // Not an attachment? + return false; $file=array( 'name' => $filename, -- GitLab