diff options
Diffstat (limited to 'program')
| -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 | 
