diff options
Diffstat (limited to 'program/lib/Mail/mimePart.php')
-rw-r--r-- | program/lib/Mail/mimePart.php | 299 |
1 files changed, 293 insertions, 6 deletions
diff --git a/program/lib/Mail/mimePart.php b/program/lib/Mail/mimePart.php index 7863028c8..21811703d 100644 --- a/program/lib/Mail/mimePart.php +++ b/program/lib/Mail/mimePart.php @@ -247,6 +247,15 @@ class Mail_mimePart } } + if (!empty($headers['Content-Description'])) { + $headers['Content-Description'] = $this->encodeHeader( + 'Content-Description', $headers['Content-Description'], + isset($c_type['charset']) ? $c_type['charset'] : 'US-ASCII', + isset($params['name_encoding']) ? $params['name_encoding'] : 'quoted-printable', + $this->_eol + ); + } + // Default content-type if (!isset($headers['Content-Type'])) { $headers['Content-Type'] = 'text/plain'; @@ -769,17 +778,281 @@ class Mail_mimePart } /** - * Callback function to replace extended characters (\x80-xFF) with their - * ASCII values (RFC2231) + * Encodes a header as per RFC2047 * - * @param array $matches Preg_replace's matches array + * @param string $name The header name + * @param string $value The header data to encode + * @param string $charset Character set name + * @param string $encoding Encoding name (base64 or quoted-printable) + * @param string $eol End-of-line sequence. Default: "\r\n" * - * @return string Encoded character string + * @return string Encoded header data (without a name) + * @access public + * @since 1.6.1 + */ + function encodeHeader($name, $value, $charset='ISO-8859-1', + $encoding='quoted-printable', $eol="\r\n" + ) { + // Structured headers + $comma_headers = array( + 'from', 'to', 'cc', 'bcc', 'sender', 'reply-to', + 'resent-from', 'resent-to', 'resent-cc', 'resent-bcc', + 'resent-sender', 'resent-reply-to', + 'return-receipt-to', 'disposition-notification-to', + ); + $other_headers = array( + 'references', 'in-reply-to', 'message-id', 'resent-message-id', + ); + + $name = strtolower($name); + + if (in_array($name, $comma_headers)) { + $separator = ','; + } else if (in_array($name, $other_headers)) { + $separator = ' '; + } + + if (!$charset) { + $charset = 'ISO-8859-1'; + } + + // Structured header (make sure addr-spec inside is not encoded) + if (!empty($separator)) { + $parts = Mail_mimePart::_explodeQuotedString($separator, $value); + $value = ''; + + foreach ($parts as $part) { + $part = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $part); + $part = trim($part); + + if (!$part) { + continue; + } + if ($value) { + $value .= $separator==',' ? $separator.' ' : ' '; + } else { + $value = $name . ': '; + } + + // let's find phrase (name) and/or addr-spec + if (preg_match('/^<\S+@\S+>$/', $part)) { + $value .= $part; + } else if (preg_match('/^\S+@\S+$/', $part)) { + // address without brackets and without name + $value .= $part; + } else if (preg_match('/<*\S+@\S+>*$/', $part, $matches)) { + // address with name (handle name) + $address = $matches[0]; + $word = str_replace($address, '', $part); + $word = trim($word); + // check if phrase requires quoting + if ($word) { + // non-ASCII: require encoding + if (preg_match('#([\x80-\xFF]){1}#', $word)) { + if ($word[0] == '"' && $word[strlen($word)-1] == '"') { + // de-quote quoted-string, encoding changes + // string to atom + $search = array("\\\"", "\\\\"); + $replace = array("\"", "\\"); + $word = str_replace($search, $replace, $word); + $word = substr($word, 1, -1); + } + // find length of last line + if (($pos = strrpos($value, $eol)) !== false) { + $last_len = strlen($value) - $pos; + } else { + $last_len = strlen($value); + } + $word = Mail_mimePart::encodeHeaderValue( + $word, $charset, $encoding, $last_len, $eol + ); + } else if (($word[0] != '"' || $word[strlen($word)-1] != '"') + && preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $word) + ) { + // ASCII: quote string if needed + $word = '"'.addcslashes($word, '\\"').'"'; + } + } + $value .= $word.' '.$address; + } else { + // addr-spec not found, don't encode (?) + $value .= $part; + } + + // RFC2822 recommends 78 characters limit, use 76 from RFC2047 + $value = wordwrap($value, 76, $eol . ' '); + } + + // remove header name prefix (there could be EOL too) + $value = preg_replace( + '/^'.$name.':('.preg_quote($eol, '/').')* /', '', $value + ); + + } else { + // Unstructured header + // non-ASCII: require encoding + if (preg_match('#([\x80-\xFF]){1}#', $value)) { + if ($value[0] == '"' && $value[strlen($value)-1] == '"') { + // de-quote quoted-string, encoding changes + // string to atom + $search = array("\\\"", "\\\\"); + $replace = array("\"", "\\"); + $value = str_replace($search, $replace, $value); + $value = substr($value, 1, -1); + } + $value = Mail_mimePart::encodeHeaderValue( + $value, $charset, $encoding, strlen($name) + 2, $eol + ); + } else if (strlen($name.': '.$value) > 78) { + // ASCII: check if header line isn't too long and use folding + $value = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $value); + $tmp = wordwrap($name.': '.$value, 78, $eol . ' '); + $value = preg_replace('/^'.$name.':\s*/', '', $tmp); + // hard limit 998 (RFC2822) + $value = wordwrap($value, 998, $eol . ' ', true); + } + } + + return $value; + } + + /** + * Explode quoted string + * + * @param string $delimiter Delimiter expression string for preg_match() + * @param string $string Input string + * + * @return array String tokens array * @access private */ - function _encodeReplaceCallback($matches) + function _explodeQuotedString($delimiter, $string) { - return sprintf('%%%02X', ord($matches[1])); + $result = array(); + $strlen = strlen($string); + + for ($q=$p=$i=0; $i < $strlen; $i++) { + if ($string[$i] == "\"" + && (empty($string[$i-1]) || $string[$i-1] != "\\") + ) { + $q = $q ? false : true; + } else if (!$q && preg_match("/$delimiter/", $string[$i])) { + $result[] = substr($string, $p, $i - $p); + $p = $i + 1; + } + } + + $result[] = substr($string, $p); + return $result; + } + + /** + * Encodes a header value as per RFC2047 + * + * @param string $value The header data to encode + * @param string $charset Character set name + * @param string $encoding Encoding name (base64 or quoted-printable) + * @param int $prefix_len Prefix length. Default: 0 + * @param string $eol End-of-line sequence. Default: "\r\n" + * + * @return string Encoded header data + * @access public + * @since 1.6.1 + */ + function encodeHeaderValue($value, $charset, $encoding, $prefix_len=0, $eol="\r\n") + { + if ($encoding == 'base64') { + // Base64 encode the entire string + $value = base64_encode($value); + + // Generate the header using the specified params and dynamicly + // determine the maximum length of such strings. + // 75 is the value specified in the RFC. + $prefix = '=?' . $charset . '?B?'; + $suffix = '?='; + $maxLength = 75 - strlen($prefix . $suffix) - 2; + $maxLength1stLine = $maxLength - $prefix_len; + + // We can cut base4 every 4 characters, so the real max + // we can get must be rounded down. + $maxLength = $maxLength - ($maxLength % 4); + $maxLength1stLine = $maxLength1stLine - ($maxLength1stLine % 4); + + $cutpoint = $maxLength1stLine; + $value_out = $value; + $output = ''; + while ($value_out) { + // Split translated string at every $maxLength + $part = substr($value_out, 0, $cutpoint); + $value_out = substr($value_out, $cutpoint); + $cutpoint = $maxLength; + // RFC 2047 specifies that any split header should + // be seperated by a CRLF SPACE. + if ($output) { + $output .= $eol . ' '; + } + $output .= $prefix . $part . $suffix; + } + $value = $output; + } else { + // quoted-printable encoding has been selected + $value = Mail_mimePart::encodeQP($value); + + // Generate the header using the specified params and dynamicly + // determine the maximum length of such strings. + // 75 is the value specified in the RFC. + $prefix = '=?' . $charset . '?Q?'; + $suffix = '?='; + $maxLength = 75 - strlen($prefix . $suffix) - 3; + $maxLength1stLine = $maxLength - $prefix_len; + $maxLength = $maxLength - 1; + + // This regexp will break QP-encoded text at every $maxLength + // but will not break any encoded letters. + $reg1st = "|(.{0,$maxLength1stLine}[^\=][^\=])|"; + $reg2nd = "|(.{0,$maxLength}[^\=][^\=])|"; + + $value_out = $value; + $realMax = $maxLength1stLine + strlen($prefix . $suffix); + if (strlen($value_out) >= $realMax) { + // Begin with the regexp for the first line. + $reg = $reg1st; + $output = ''; + while ($value_out) { + // Split translated string at every $maxLength + // But make sure not to break any translated chars. + $found = preg_match($reg, $value_out, $matches); + + // After this first line, we need to use a different + // regexp for the first line. + $reg = $reg2nd; + + // Save the found part and encapsulate it in the + // prefix & suffix. Then remove the part from the + // $value_out variable. + if ($found) { + $part = $matches[0]; + $len = strlen($matches[0]); + $value_out = substr($value_out, $len); + } else { + $part = $value_out; + $value_out = ""; + } + + // RFC 2047 specifies that any split header should + // be seperated by a CRLF SPACE + if ($output) { + $output .= $eol . ' '; + } + $output .= $prefix . $part . $suffix; + } + $value_out = $output; + } else { + $value_out = $prefix . $value_out . $suffix; + } + $value = $value_out; + } + + return $value; } /** @@ -819,4 +1092,18 @@ class Mail_mimePart return sprintf('=%02X', ord($matches[1])); } + /** + * Callback function to replace extended characters (\x80-xFF) with their + * ASCII values (RFC2231) + * + * @param array $matches Preg_replace's matches array + * + * @return string Encoded character string + * @access private + */ + function _encodeReplaceCallback($matches) + { + return sprintf('%%%02X', ord($matches[1])); + } + } // End of class |