diff options
Diffstat (limited to 'program/lib/Mail')
-rw-r--r-- | program/lib/Mail/mime.php | 312 | ||||
-rw-r--r-- | program/lib/Mail/mimePart.php | 299 |
2 files changed, 345 insertions, 266 deletions
diff --git a/program/lib/Mail/mime.php b/program/lib/Mail/mime.php index d6d63d68f..c08c7cfcb 100644 --- a/program/lib/Mail/mime.php +++ b/program/lib/Mail/mime.php @@ -388,6 +388,7 @@ class Mail_mime * or base64) instead * @param string $f_encoding Encoding for attachment filename (Content-Disposition) * See $n_encoding description + * @param string $description Content-Description header * * @return mixed True on success or PEAR_Error object * @access public @@ -402,7 +403,8 @@ class Mail_mime $language = '', $location = '', $n_encoding = null, - $f_encoding = null + $f_encoding = null, + $description = '' ) { $bodyfile = null; @@ -440,6 +442,7 @@ class Mail_mime 'language' => $language, 'location' => $location, 'disposition' => $disposition, + 'description' => $description, 'name_encoding' => $n_encoding, 'filename_encoding' => $f_encoding ); @@ -668,6 +671,9 @@ class Mail_mime if (!empty($value['filename_encoding'])) { $params['filename_encoding'] = $value['filename_encoding']; } + if (!empty($value['description'])) { + $params['description'] = $value['description']; + } $ret = $obj->addSubpart($value['body'], $params); return $ret; @@ -865,11 +871,13 @@ class Mail_mime } } + $this->_checkParams(); + $null = null; $attachments = count($this->_parts) ? true : false; $html_images = count($this->_html_images) ? true : false; $html = strlen($this->_htmlbody) ? true : false; - $text = (!$html && strlen($this->_txtbody)) ? true : false; + $text = (!$html && strlen($this->_txtbody)) ? true : false; switch (true) { case $text && !$attachments: @@ -1233,265 +1241,9 @@ class Mail_mime */ function encodeHeader($name, $value, $charset, $encoding) { - // 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', + return Mail_mimePart::encodeHeader( + $name, $value, $charset, $encoding, $this->_build_params['eol'] ); - $other_headers = array( - 'references', 'in-reply-to', 'message-id', 'resent-message-id', - ); - - $name = strtolower($name); - $eol = $this->_build_params['eol']; - - 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 = $this->_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 = $this->_encodeString( - $word, $charset, $encoding, $last_len - ); - } 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 = $this->_encodeString( - $value, $charset, $encoding, strlen($name) + 2 - ); - } 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; - } - - /** - * 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 - * - * @return string Encoded header data - * @access private - */ - function _encodeString($value, $charset, $encoding, $prefix_len=0) - { - 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 .= $this->_build_params['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 .= $this->_build_params['eol'] . ' '; - } - $output .= $prefix . $part . $suffix; - } - $value_out = $output; - } else { - $value_out = $prefix . $value_out . $suffix; - } - $value = $value_out; - } - - 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 _explodeQuotedString($delimiter, $string) - { - $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; } /** @@ -1556,6 +1308,8 @@ class Mail_mime return $headers; } + $this->_checkParams(); + $eol = !empty($this->_build_params['eol']) ? $this->_build_params['eol'] : "\r\n"; @@ -1590,4 +1344,42 @@ class Mail_mime return $headers; } + /** + * Validate and set build parameters + * + * @return void + * @access private + */ + function _checkParams() + { + $encodings = array('7bit', '8bit', 'base64', 'quoted-printable'); + + $this->_build_params['text_encoding'] + = strtolower($this->_build_params['text_encoding']); + $this->_build_params['html_encoding'] + = strtolower($this->_build_params['html_encoding']); + + if (!in_array($this->_build_params['text_encoding'], $encodings)) { + $this->_build_params['text_encoding'] = '7bit'; + } + if (!in_array($this->_build_params['html_encoding'], $encodings)) { + $this->_build_params['html_encoding'] = '7bit'; + } + + // text body + if ($this->_build_params['text_encoding'] == '7bit' + && !preg_match('/ascii/i', $this->_build_params['text_charset']) + && preg_match('/[^\x00-\x7F]/', $this->_txtbody) + ) { + $this->_build_params['text_encoding'] = 'quoted-printable'; + } + // html body + if ($this->_build_params['html_encoding'] == '7bit' + && !preg_match('/ascii/i', $this->_build_params['html_charset']) + && preg_match('/[^\x00-\x7F]/', $this->_htmlbody) + ) { + $this->_build_params['html_encoding'] = 'quoted-printable'; + } + } + } // End of class 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 |