From ffae15e5fcde50cf8c1b168fa313f4ec3454a693 Mon Sep 17 00:00:00 2001 From: alecpl Date: Mon, 29 Sep 2008 07:38:16 +0000 Subject: - Added 'mime_param_folding' option with possibility to choose long/non-ascii attachment names encoding eg. to be readable in MS Outlook/OE (#1485320) - Added "advanced options" feature in User Preferences --- program/lib/Mail/mime.php | 23 +++++++-- program/lib/Mail/mimePart.php | 112 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 119 insertions(+), 16 deletions(-) (limited to 'program/lib/Mail') diff --git a/program/lib/Mail/mime.php b/program/lib/Mail/mime.php index 7641b0c9b..688c326a5 100644 --- a/program/lib/Mail/mime.php +++ b/program/lib/Mail/mime.php @@ -323,6 +323,8 @@ class Mail_mime * of this attachment. * @param string $language The language of the attachment * @param string $location The RFC 2557.4 location of the attachment + * @param string $n_encoding Use RFC 2047 for attachment name (Content-Type) encoding + * @param string $f_encoding Use RFC 2047 for attachment filename (Content-Disposition) encoding * * @return mixed true on success or PEAR_Error object * @access public @@ -335,7 +337,9 @@ class Mail_mime $disposition = 'attachment', $charset = '', $language = '', - $location = '') + $location = '', + $n_encoding = NULL, + $f_encoding = NULL) { $filedata = ($isfile === true) ? $this->_file2str($file) : $file; @@ -363,7 +367,9 @@ class Mail_mime 'charset' => $charset, 'language' => $language, 'location' => $location, - 'disposition' => $disposition + 'disposition' => $disposition, + 'name-encoding' => $n_encoding, + 'filename-encoding'=> $f_encoding ); return true; } @@ -532,6 +538,12 @@ class Mail_mime $params['disposition'] = 'inline'; $params['dfilename'] = $value['name']; $params['cid'] = $value['cid']; + if ($value['name-encoding']) { + $params['name-encoding'] = $value['name-encoding']; + } + if ($value['filename-encoding']) { + $params['filename-encoding'] = $value['filename-encoding']; + } $ret = $obj->addSubpart($value['body'], $params); return $ret; @@ -561,6 +573,12 @@ class Mail_mime if ($value['location']) { $params['location'] = $value['location']; } + if ($value['name-encoding']) { + $params['name-encoding'] = $value['name-encoding']; + } + if ($value['filename-encoding']) { + $params['filename-encoding'] = $value['filename-encoding']; + } $params['content_type'] = $value['c_type']; $params['disposition'] = isset($value['disposition']) ? $value['disposition'] : 'attachment'; @@ -914,7 +932,6 @@ class Mail_mime */ function _encodeHeaders($input, $params = array()) { - $build_params = $this->_build_params; while (list($key, $value) = each($params)) { $build_params[$key] = $value; diff --git a/program/lib/Mail/mimePart.php b/program/lib/Mail/mimePart.php index bd3047724..9b9f26cc3 100644 --- a/program/lib/Mail/mimePart.php +++ b/program/lib/Mail/mimePart.php @@ -182,13 +182,16 @@ class Mail_mimePart { } } + if (isset($contentType['type'])) { $headers['Content-Type'] = $contentType['type']; if (isset($contentType['name'])) { $headers['Content-Type'] .= ';' . MAIL_MIMEPART_CRLF; - $headers['Content-Type'] .= $this->_buildHeaderParam('name', $contentType['name'], - isset($contentType['charset']) ? $contentType['charset'] : 'US-ASCII', - isset($contentType['language']) ? $contentType['language'] : NULL); + $headers['Content-Type'] .= + $this->_buildHeaderParam('name', $contentType['name'], + isset($contentType['charset']) ? $contentType['charset'] : 'US-ASCII', + isset($contentType['language']) ? $contentType['language'] : NULL, + isset($params['name-encoding']) ? $params['name-encoding'] : NULL); } elseif (isset($contentType['charset'])) { $headers['Content-Type'] .= "; charset=\"{$contentType['charset']}\""; } @@ -199,15 +202,14 @@ class Mail_mimePart { $headers['Content-Disposition'] = $contentDisp['disp']; if (isset($contentDisp['filename'])) { $headers['Content-Disposition'] .= ';' . MAIL_MIMEPART_CRLF; - $headers['Content-Disposition'] .= $this->_buildHeaderParam('filename', $contentDisp['filename'], - isset($contentDisp['charset']) ? $contentDisp['charset'] : 'US-ASCII', - isset($contentDisp['language']) ? $contentDisp['language'] : NULL); + $headers['Content-Disposition'] .= + $this->_buildHeaderParam('filename', $contentDisp['filename'], + isset($contentDisp['charset']) ? $contentDisp['charset'] : 'US-ASCII', + isset($contentDisp['language']) ? $contentDisp['language'] : NULL, + isset($params['filename-encoding']) ? $params['filename-encoding'] : NULL); } } - - - - + // Default content-type if (!isset($headers['Content-Type'])) { $headers['Content-Type'] = 'text/plain'; @@ -388,21 +390,25 @@ class Mail_mimePart { * @param $value The value of the paramter * @param $charset The characterset of $value * @param $language The language used in $value + * @param $paramEnc Parameter encoding type * @param $maxLength The maximum length of a line. Defauls to 78 * * @access private */ - function _buildHeaderParam($name, $value, $charset=NULL, $language=NULL, $maxLength=78) + function _buildHeaderParam($name, $value, $charset=NULL, $language=NULL, $paramEnc=NULL, $maxLength=78) { // RFC 2183/2184/2822: // value needs encoding if contains non-ASCII chars or is longer than 78 chars - if (!preg_match('#[^\x20-\x7E]#', $value)) { // ASCII $quoted = addcslashes($value, '\\"'); if (strlen($name) + strlen($quoted) + 6 <= $maxLength) return " {$name}=\"{$quoted}\"; "; } + // use quoted-printable/base64 encoding (RFC2047) + if ($paramEnc == 'quoted-printable' || $paramEnc == 'base64') + return $this->_buildRFC2047Param($name, $value, $charset, $paramEnc); + $encValue = preg_replace('#([^\x20-\x7E])#e', '"%" . strtoupper(dechex(ord("\1")))', $value); $value = "$charset'$language'$encValue"; @@ -413,7 +419,7 @@ class Mail_mimePart { $preLength = strlen(" {$name}*0*=\""); $sufLength = strlen("\";"); - $maxLength = MAX(16, $maxLength - $preLength - $sufLength - 2); + $maxLength = max(16, $maxLength - $preLength - $sufLength - 2); $maxLengthReg = "|(.{0,$maxLength}[^\%][^\%])|"; $headers = array(); @@ -433,4 +439,84 @@ class Mail_mimePart { $headers = implode(MAIL_MIMEPART_CRLF, $headers) . ';'; return $headers; } + + /** + * Encodes header parameter as per RFC2047 if needed (values too long will be truncated) + * + * @param string $name The parameter name + * @param string $value The parameter value + * @param string $charset The parameter charset + * @param string $encoding Encoding type (quoted-printable or base64) + * @param int $maxLength Encoded parameter max length (75 is the value specified in the RFC) + * + * @return string Parameter line + * @access private + */ + function _buildRFC2047Param($name, $value, $charset, $encoding='quoted-printable', $maxLength=75) + { + if (!preg_match('#([^\x20-\x7E]){1}#', $value)) + { + $quoted = addcslashes($value, '\\"'); + $maxLength = $maxLength - 6; + if (strlen($quoted) > $maxLength) + { + // truncate filename leaving extension + $ext = strrchr($quoted, '.'); + $quoted = substr($quoted, 0, $maxLength - strlen($ext)); + // remove backslashes from the end of filename + preg_replace('/[\\\\]+$/', '', $quoted); + $quoted .= $ext; + } + } + else if ($encoding == 'base64') + { + $ext = strrchr($value, '.'); + $value = substr($value, 0, strlen($value) - strlen($ext)); + + $ext = base64_encode($ext); + $value = base64_encode($value); + + $prefix = '=?' . $charset . '?B?'; + $suffix = '?='; + $maxLength = $maxLength - strlen($prefix . $suffix) - strlen($ext) - 2; + + //We can cut base64 every 4 characters, so the real max + //we can get must be rounded down. + $maxLength = $maxLength - ($maxLength % 4); + $quoted = $prefix . substr($value, 0, $maxLength) . $ext . $suffix; + } + else // quoted-printable + { + $ext = strrchr($value, '.'); + $value = substr($value, 0, strlen($value) - strlen($ext)); + + // Replace all special characters used by the encoder. + $search = array('=', '_', '?', ' '); + $replace = array('=3D', '=5F', '=3F', '_'); + $ext = str_replace($search, $replace, $ext); + $value = str_replace($search, $replace, $value); + + // Replace all extended characters (\x80-xFF) with their + // ASCII values. + $ext = preg_replace('/([\x80-\xFF])/e', + '"=" . strtoupper(dechex(ord("\1")))', $ext); + $value = preg_replace('/([\x80-\xFF])/e', + '"=" . strtoupper(dechex(ord("\1")))', $value); + + $prefix = '=?' . $charset . '?Q?'; + $suffix = '?='; + + $maxLength = $maxLength - strlen($prefix . $suffix) - strlen($ext) - 2; + + // Truncate QP-encoded text at $maxLength + // but not break any encoded letters. + if(preg_match("/^(.{0,$maxLength}[^\=][^\=])/", $value, $matches)) + $value = $matches[1]; + + $quoted = $prefix . $value . $ext . $suffix; + } + + return " {$name}=\"{$quoted}\"; "; + } + } // End of class -- cgit v1.2.3