From b28e8ff0f885e11aac1eaef1c3efa22336734c56 Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Tue, 24 Sep 2013 21:27:58 +0000 Subject: [PATCH] Implement RFC5987 for fetched attachments Previously, filenames specified in email headers fetched using the PHP imap extension were not decoded properly. Specifically, if RFC5987 was used to encode the filenames, no decoding was performed at all. This patch properly detects the filename attribute, using a decoding scheme for RFC5987 if the attribute was encoded properly. References: http://osticket.com/forums/showthread.php?t=6129 --- include/class.format.php | 17 ++++++++++++ include/class.mailfetch.php | 52 +++++++++++++++++++++++++------------ include/class.mailparse.php | 21 ++------------- 3 files changed, 54 insertions(+), 36 deletions(-) diff --git a/include/class.format.php b/include/class.format.php index 892cc42f1..f016896de 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -86,6 +86,23 @@ class Format { return $text; } + /** + * 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::utf8encode(urldecode($match[3]), $match[1]); + else + return $filename; + } + function phone($phone) { $stripped= preg_replace("/[^0-9]/", "", $phone); diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index 7fee6868a..c4d0d0b32 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -308,6 +308,31 @@ class MailFetcher { return $text; } + /** + * Searches the attribute list for a possible filename attribute. If + * found, the attribute value is returned. If the attribute uses rfc5987 + * to encode the attribute value, the value is returned properly decoded + * if possible + * + * Attribute Search Preference: + * filename + * filename* + * name + * name* + */ + function findFilename($attributes) { + foreach (array('filename', 'name') as $pref) { + foreach ($attributes as $a) { + if (strtolower($a->attribute) == $pref) + return $a->value; + // Allow the RFC5987 specification of the filename + elseif (strtolower($a->attribute) == $pref.'*') + return Format::decodeRfc5987($a->value); + } + } + return false; + } + /* getAttachments @@ -319,23 +344,16 @@ class MailFetcher { if($part && !$part->parts) { //Check if the part is an attachment. - $filename = ''; - if($part->ifdisposition && in_array(strtolower($part->disposition), array('attachment', 'inline'))) { - $filename = $part->dparameters[0]->value; - //Some inline attachments have multiple parameters. - if(count($part->dparameters)>1) { - foreach($part->dparameters as $dparameter) { - if(!in_array(strtoupper($dparameter->attribute), array('FILENAME', 'NAME'))) continue; - $filename = $dparameter->value; - break; - } - } - } elseif($part->ifparameters && $part->parameters && $part->type > 0) { //inline attachments without disposition. - foreach($part->parameters as $parameter) { - if(!in_array(strtoupper($parameter->attribute), array('FILENAME', 'NAME'))) continue; - $filename = $parameter->value; - break; - } + $filename = false; + if ($part->ifdisposition + && in_array(strtolower($part->disposition), + array('attachment', 'inline'))) { + $filename = $this->findFilename($part->dparameters); + } + // Inline attachments without disposition. + if (!$filename && $part->ifparameters && $part->parameters + && $part->type > 0) { + $filename = $this->findFilename($part->parameters); } if($filename) { diff --git a/include/class.mailparse.php b/include/class.mailparse.php index b02f2448a..1132073d0 100644 --- a/include/class.mailparse.php +++ b/include/class.mailparse.php @@ -199,23 +199,6 @@ 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) @@ -240,7 +223,7 @@ class Mail_Parse { $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( + $filename = Format::decodeRfc5987( $part->d_parameters['filename*']); // Support attachments that do not specify a content-disposition @@ -248,7 +231,7 @@ class Mail_Parse { elseif (isset($part->ctype_parameters['name'])) $filename=$part->ctype_parameters['name']; elseif (isset($part->ctype_parameters['name*'])) - $filename = self::decodeRfc5987( + $filename = Format::decodeRfc5987( $part->ctype_parameters['name*']); else // Not an attachment? -- GitLab