Mail_Mime
[ class tree: Mail_Mime ] [ index: Mail_Mime ] [ all elements ]

Source for file mimePart.php

Documentation is available at mimePart.php

  1. <?php
  2. /**
  3.  * The Mail_mimePart class is used to create MIME E-mail messages
  4.  *
  5.  * This class enables you to manipulate and build a mime email
  6.  * from the ground up. The Mail_Mime class is a userfriendly api
  7.  * to this class for people who aren't interested in the internals
  8.  * of mime mail.
  9.  * This class however allows full control over the email.
  10.  *
  11.  * Compatible with PHP versions 4 and 5
  12.  *
  13.  * LICENSE: This LICENSE is in the BSD license style.
  14.  * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
  15.  * Copyright (c) 2003-2006, PEAR <pear-group@php.net>
  16.  * All rights reserved.
  17.  *
  18.  * Redistribution and use in source and binary forms, with or
  19.  * without modification, are permitted provided that the following
  20.  * conditions are met:
  21.  *
  22.  * - Redistributions of source code must retain the above copyright
  23.  *   notice, this list of conditions and the following disclaimer.
  24.  * - Redistributions in binary form must reproduce the above copyright
  25.  *   notice, this list of conditions and the following disclaimer in the
  26.  *   documentation and/or other materials provided with the distribution.
  27.  * - Neither the name of the authors, nor the names of its contributors
  28.  *   may be used to endorse or promote products derived from this
  29.  *   software without specific prior written permission.
  30.  *
  31.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  32.  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  33.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  34.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  35.  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  36.  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  37.  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  38.  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  39.  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  41.  * THE POSSIBILITY OF SUCH DAMAGE.
  42.  *
  43.  * @category  Mail
  44.  * @package   Mail_Mime
  45.  * @author    Richard Heyes  <richard@phpguru.org>
  46.  * @author    Cipriano Groenendal <cipri@php.net>
  47.  * @author    Sean Coates <sean@php.net>
  48.  * @author    Aleksander Machniak <alec@php.net>
  49.  * @copyright 2003-2006 PEAR <pear-group@php.net>
  50.  * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
  51.  * @version   CVS: $Id$
  52.  * @link      http://pear.php.net/package/Mail_mime
  53.  */
  54.  
  55.  
  56. /**
  57.  * The Mail_mimePart class is used to create MIME E-mail messages
  58.  *
  59.  * This class enables you to manipulate and build a mime email
  60.  * from the ground up. The Mail_Mime class is a userfriendly api
  61.  * to this class for people who aren't interested in the internals
  62.  * of mime mail.
  63.  * This class however allows full control over the email.
  64.  *
  65.  * @category  Mail
  66.  * @package   Mail_Mime
  67.  * @author    Richard Heyes  <richard@phpguru.org>
  68.  * @author    Cipriano Groenendal <cipri@php.net>
  69.  * @author    Sean Coates <sean@php.net>
  70.  * @author    Aleksander Machniak <alec@php.net>
  71.  * @copyright 2003-2006 PEAR <pear-group@php.net>
  72.  * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
  73.  * @version   Release: @package_version@
  74.  * @link      http://pear.php.net/package/Mail_mime
  75.  */
  76. {
  77.     /**
  78.     * The encoding type of this part
  79.     *
  80.     * @var string 
  81.     * @access private
  82.     */
  83.     var $_encoding;
  84.  
  85.     /**
  86.     * An array of subparts
  87.     *
  88.     * @var array 
  89.     * @access private
  90.     */
  91.     var $_subparts;
  92.  
  93.     /**
  94.     * The output of this part after being built
  95.     *
  96.     * @var string 
  97.     * @access private
  98.     */
  99.     var $_encoded;
  100.  
  101.     /**
  102.     * Headers for this part
  103.     *
  104.     * @var array 
  105.     * @access private
  106.     */
  107.     var $_headers;
  108.  
  109.     /**
  110.     * The body of this part (not encoded)
  111.     *
  112.     * @var string 
  113.     * @access private
  114.     */
  115.     var $_body;
  116.  
  117.     /**
  118.     * The location of file with body of this part (not encoded)
  119.     *
  120.     * @var string 
  121.     * @access private
  122.     */
  123.     var $_body_file;
  124.  
  125.     /**
  126.     * The end-of-line sequence
  127.     *
  128.     * @var string 
  129.     * @access private
  130.     */
  131.     var $_eol "\r\n";
  132.  
  133.  
  134.     /**
  135.     * Constructor.
  136.     *
  137.     * Sets up the object.
  138.     *
  139.     * @param string $body   The body of the mime part if any.
  140.     * @param array  $params An associative array of optional parameters:
  141.     *      content_type      - The content type for this part eg multipart/mixed
  142.     *      encoding          - The encoding to use, 7bit, 8bit,
  143.     *                          base64, or quoted-printable
  144.     *      charset           - Content character set
  145.     *      cid               - Content ID to apply
  146.     *      disposition       - Content disposition, inline or attachment
  147.     *      filename          - Filename parameter for content disposition
  148.     *      description       - Content description
  149.     *      name_encoding     - Encoding of the attachment name (Content-Type)
  150.     *                          By default filenames are encoded using RFC2231
  151.     *                          Here you can set RFC2047 encoding (quoted-printable
  152.     *                          or base64) instead
  153.     *      filename_encoding - Encoding of the attachment filename (Content-Disposition)
  154.     *                          See 'name_encoding'
  155.     *      headers_charset   - Charset of the headers e.g. filename, description.
  156.     *                          If not set, 'charset' will be used
  157.     *      eol               - End of line sequence. Default: "\r\n"
  158.     *      headers           - Hash array with additional part headers. Array keys can be
  159.     *                          in form of <header_name>:<parameter_name>
  160.     *      body_file         - Location of file with part's body (instead of $body)
  161.     *
  162.     * @access public
  163.     */
  164.     function Mail_mimePart($body ''$params = array())
  165.     {
  166.         if (!empty($params['eol'])) {
  167.             $this->_eol $params['eol'];
  168.         else if (defined('MAIL_MIMEPART_CRLF')) // backward-copat.
  169.             $this->_eol = MAIL_MIMEPART_CRLF;
  170.         }
  171.  
  172.         // Additional part headers
  173.         if (!empty($params['headers']&& is_array($params['headers'])) {
  174.             $headers $params['headers'];
  175.         }
  176.  
  177.         foreach ($params as $key => $value{
  178.             switch ($key{
  179.             case 'encoding':
  180.                 $this->_encoding $value;
  181.                 $headers['Content-Transfer-Encoding'$value;
  182.                 break;
  183.  
  184.             case 'cid':
  185.                 $headers['Content-ID''<' $value '>';
  186.                 break;
  187.  
  188.             case 'location':
  189.                 $headers['Content-Location'$value;
  190.                 break;
  191.  
  192.             case 'body_file':
  193.                 $this->_body_file $value;
  194.                 break;
  195.  
  196.             // for backward compatibility
  197.             case 'dfilename':
  198.                 $params['filename'$value;
  199.                 break;
  200.             }
  201.         }
  202.  
  203.         // Default content-type
  204.         if (empty($params['content_type'])) {
  205.             $params['content_type''text/plain';
  206.         }
  207.  
  208.         // Content-Type
  209.         $headers['Content-Type'$params['content_type'];
  210.         if (!empty($params['charset'])) {
  211.             $charset = "charset={$params['charset']}";
  212.             // place charset parameter in the same line, if possible
  213.             if ((strlen($headers['Content-Type']strlen($charset+ 16<= 76{
  214.                 $headers['Content-Type'.= '; ';
  215.             else {
  216.                 $headers['Content-Type'.= ';' $this->_eol ' ';
  217.             }
  218.             $headers['Content-Type'.= $charset;
  219.  
  220.             // Default headers charset
  221.             if (!isset($params['headers_charset'])) {
  222.                 $params['headers_charset'$params['charset'];
  223.             }
  224.         }
  225.  
  226.         // header values encoding parameters
  227.         $h_charset  !empty($params['headers_charset']$params['headers_charset''US-ASCII';
  228.         $h_language !empty($params['language']$params['language': null;
  229.         $h_encoding !empty($params['name_encoding']$params['name_encoding': null;
  230.  
  231.  
  232.         if (!empty($params['filename'])) {
  233.             $headers['Content-Type'.= ';' $this->_eol;
  234.             $headers['Content-Type'.= $this->_buildHeaderParam(
  235.                 'name'$params['filename']$h_charset$h_language$h_encoding
  236.             );
  237.         }
  238.  
  239.         // Content-Disposition
  240.         if (!empty($params['disposition'])) {
  241.             $headers['Content-Disposition'$params['disposition'];
  242.             if (!empty($params['filename'])) {
  243.                 $headers['Content-Disposition'.= ';' $this->_eol;
  244.                 $headers['Content-Disposition'.= $this->_buildHeaderParam(
  245.                     'filename'$params['filename']$h_charset$h_language,
  246.                     !empty($params['filename_encoding']$params['filename_encoding': null
  247.                 );
  248.             }
  249.  
  250.             // add attachment size
  251.             $size $this->_body_file filesize($this->_body_filestrlen($body);
  252.             if ($size{
  253.                 $headers['Content-Disposition'.= ';' $this->_eol ' size=' $size;
  254.             }
  255.         }
  256.  
  257.         if (!empty($params['description'])) {
  258.             $headers['Content-Description'$this->encodeHeader(
  259.                 'Content-Description'$params['description']$h_charset$h_encoding,
  260.                 $this->_eol
  261.             );
  262.         }
  263.  
  264.         // Search and add existing headers' parameters
  265.         foreach ($headers as $key => $value{
  266.             $items explode(':'$key);
  267.             if (count($items== 2{
  268.                 $header $items[0];
  269.                 $param  $items[1];
  270.                 if (isset($headers[$header])) {
  271.                     $headers[$header.= ';' $this->_eol;
  272.                 }
  273.                 $headers[$header.= $this->_buildHeaderParam(
  274.                     $param$value$h_charset$h_language$h_encoding
  275.                 );
  276.                 unset($headers[$key]);
  277.             }
  278.         }
  279.  
  280.         // Default encoding
  281.         if (!isset($this->_encoding)) {
  282.             $this->_encoding '7bit';
  283.         }
  284.  
  285.         // Assign stuff to member variables
  286.         $this->_encoded  = array();
  287.         $this->_headers  $headers;
  288.         $this->_body     $body;
  289.     }
  290.  
  291.     /**
  292.      * Encodes and returns the email. Also stores
  293.      * it in the encoded member variable
  294.      *
  295.      * @param string $boundary Pre-defined boundary string
  296.      *
  297.      * @return An associative array containing two elements,
  298.      *          body and headers. The headers element is itself
  299.      *          an indexed array. On error returns PEAR error object.
  300.      * @access public
  301.      */
  302.     function encode($boundary=null)
  303.     {
  304.         $encoded =$this->_encoded;
  305.  
  306.         if (count($this->_subparts)) {
  307.             $boundary $boundary $boundary '=_' md5(rand(microtime());
  308.             $eol $this->_eol;
  309.  
  310.             $this->_headers['Content-Type'.= ";$eol boundary=\"$boundary\"";
  311.  
  312.             $encoded['body'''
  313.  
  314.             for ($i = 0; $i count($this->_subparts)$i++{
  315.                 $encoded['body'.= '--' $boundary $eol;
  316.                 $tmp $this->_subparts[$i]->encode();
  317.                 if ($this->_isError($tmp)) {
  318.                     return $tmp;
  319.                 }
  320.                 foreach ($tmp['headers'as $key => $value{
  321.                     $encoded['body'.= $key ': ' $value $eol;
  322.                 }
  323.                 $encoded['body'.= $eol $tmp['body'$eol;
  324.             }
  325.  
  326.             $encoded['body'.= '--' $boundary '--' $eol;
  327.  
  328.         else if ($this->_body{
  329.             $encoded['body'$this->_getEncodedData($this->_body$this->_encoding);
  330.         else if ($this->_body_file{
  331.             // Temporarily reset magic_quotes_runtime for file reads and writes
  332.             if ($magic_quote_setting get_magic_quotes_runtime()) {
  333.                 @ini_set('magic_quotes_runtime'0);
  334.             }
  335.             $body $this->_getEncodedDataFromFile($this->_body_file$this->_encoding);
  336.             if ($magic_quote_setting{
  337.                 @ini_set('magic_quotes_runtime'$magic_quote_setting);
  338.             }
  339.  
  340.             if ($this->_isError($body)) {
  341.                 return $body;
  342.             }
  343.             $encoded['body'$body;
  344.         else {
  345.             $encoded['body''';
  346.         }
  347.  
  348.         // Add headers to $encoded
  349.         $encoded['headers'=$this->_headers;
  350.  
  351.         return $encoded;
  352.     }
  353.  
  354.     /**
  355.      * Encodes and saves the email into file. File must exist.
  356.      * Data will be appended to the file.
  357.      *
  358.      * @param string  $filename  Output file location
  359.      * @param string  $boundary  Pre-defined boundary string
  360.      * @param boolean $skip_head True if you don't want to save headers
  361.      *
  362.      * @return array An associative array containing message headers
  363.      *                or PEAR error object
  364.      * @access public
  365.      * @since 1.6.0
  366.      */
  367.     function encodeToFile($filename$boundary=null$skip_head=false)
  368.     {
  369.         if (file_exists($filename&& !is_writable($filename)) {
  370.             $err $this->_raiseError('File is not writeable: ' $filename);
  371.             return $err;
  372.         }
  373.  
  374.         if (!($fh fopen($filename'ab'))) {
  375.             $err $this->_raiseError('Unable to open file: ' $filename);
  376.             return $err;
  377.         }
  378.  
  379.         // Temporarily reset magic_quotes_runtime for file reads and writes
  380.         if ($magic_quote_setting get_magic_quotes_runtime()) {
  381.             @ini_set('magic_quotes_runtime'0);
  382.         }
  383.  
  384.         $res $this->_encodePartToFile($fh$boundary$skip_head);
  385.  
  386.         fclose($fh);
  387.  
  388.         if ($magic_quote_setting{
  389.             @ini_set('magic_quotes_runtime'$magic_quote_setting);
  390.         }
  391.  
  392.         return $this->_isError($res$res $this->_headers;
  393.     }
  394.  
  395.     /**
  396.      * Encodes given email part into file
  397.      *
  398.      * @param string  $fh        Output file handle
  399.      * @param string  $boundary  Pre-defined boundary string
  400.      * @param boolean $skip_head True if you don't want to save headers
  401.      *
  402.      * @return array True on sucess or PEAR error object
  403.      * @access private
  404.      */
  405.     function _encodePartToFile($fh$boundary=null$skip_head=false)
  406.     {
  407.         $eol $this->_eol;
  408.  
  409.         if (count($this->_subparts)) {
  410.             $boundary $boundary $boundary '=_' md5(rand(microtime());
  411.             $this->_headers['Content-Type'.= ";$eol boundary=\"$boundary\"";
  412.         }
  413.  
  414.         if (!$skip_head{
  415.             foreach ($this->_headers as $key => $value{
  416.                 fwrite($fh$key ': ' $value $eol);
  417.             }
  418.             $f_eol $eol;
  419.         else {
  420.             $f_eol '';
  421.         }
  422.  
  423.         if (count($this->_subparts)) {
  424.             for ($i = 0; $i count($this->_subparts)$i++{
  425.                 fwrite($fh$f_eol '--' $boundary $eol);
  426.                 $res $this->_subparts[$i]->_encodePartToFile($fh);
  427.                 if ($this->_isError($res)) {
  428.                     return $res;
  429.                 }
  430.                 $f_eol $eol;
  431.             }
  432.  
  433.             fwrite($fh$eol '--' $boundary '--' $eol);
  434.  
  435.         else if ($this->_body{
  436.             fwrite($fh$f_eol $this->_getEncodedData($this->_body$this->_encoding));
  437.         else if ($this->_body_file{
  438.             fwrite($fh$f_eol);
  439.             $res $this->_getEncodedDataFromFile(
  440.                 $this->_body_file$this->_encoding$fh
  441.             );
  442.             if ($this->_isError($res)) {
  443.                 return $res;
  444.             }
  445.         }
  446.  
  447.         return true;
  448.     }
  449.  
  450.     /**
  451.      * Adds a subpart to current mime part and returns
  452.      * a reference to it
  453.      *
  454.      * @param string $body   The body of the subpart, if any.
  455.      * @param array  $params The parameters for the subpart, same
  456.      *                        as the $params argument for constructor.
  457.      *
  458.      * @return Mail_mimePart A reference to the part you just added. In PHP4, it is
  459.      *                        crucial if using multipart/* in your subparts that
  460.      *                        you use =& in your script when calling this function,
  461.      *                        otherwise you will not be able to add further subparts.
  462.      * @access public
  463.      */
  464.     function &addSubpart($body$params)
  465.     {
  466.         $this->_subparts[$part = new Mail_mimePart($body$params);
  467.         return $part;
  468.     }
  469.  
  470.     /**
  471.      * Returns encoded data based upon encoding passed to it
  472.      *
  473.      * @param string $data     The data to encode.
  474.      * @param string $encoding The encoding type to use, 7bit, base64,
  475.      *                          or quoted-printable.
  476.      *
  477.      * @return string 
  478.      * @access private
  479.      */
  480.     function _getEncodedData($data$encoding)
  481.     {
  482.         switch ($encoding{
  483.         case 'quoted-printable':
  484.             return $this->_quotedPrintableEncode($data);
  485.             break;
  486.  
  487.         case 'base64':
  488.             return rtrim(chunk_split(base64_encode($data)76$this->_eol));
  489.             break;
  490.  
  491.         case '8bit':
  492.         case '7bit':
  493.         default:
  494.             return $data;
  495.         }
  496.     }
  497.  
  498.     /**
  499.      * Returns encoded data based upon encoding passed to it
  500.      *
  501.      * @param string   $filename Data file location
  502.      * @param string   $encoding The encoding type to use, 7bit, base64,
  503.      *                            or quoted-printable.
  504.      * @param resource $fh       Output file handle. If set, data will be
  505.      *                            stored into it instead of returning it
  506.      *
  507.      * @return string Encoded data or PEAR error object
  508.      * @access private
  509.      */
  510.     function _getEncodedDataFromFile($filename$encoding$fh=null)
  511.     {
  512.         if (!is_readable($filename)) {
  513.             $err $this->_raiseError('Unable to read file: ' $filename);
  514.             return $err;
  515.         }
  516.  
  517.         if (!($fd fopen($filename'rb'))) {
  518.             $err $this->_raiseError('Could not open file: ' $filename);
  519.             return $err;
  520.         }
  521.  
  522.         $data '';
  523.  
  524.         switch ($encoding{
  525.         case 'quoted-printable':
  526.             while (!feof($fd)) {
  527.                 $buffer $this->_quotedPrintableEncode(fgets($fd));
  528.                 if ($fh{
  529.                     fwrite($fh$buffer);
  530.                 else {
  531.                     $data .= $buffer;
  532.                 }
  533.             }
  534.             break;
  535.  
  536.         case 'base64':
  537.             while (!feof($fd)) {
  538.                 // Should read in a multiple of 57 bytes so that
  539.                 // the output is 76 bytes per line. Don't use big chunks
  540.                 // because base64 encoding is memory expensive
  541.                 $buffer fread($fd57 * 9198)// ca. 0.5 MB
  542.                 $buffer base64_encode($buffer);
  543.                 $buffer chunk_split($buffer76$this->_eol);
  544.                 if (feof($fd)) {
  545.                     $buffer rtrim($buffer);
  546.                 }
  547.  
  548.                 if ($fh{
  549.                     fwrite($fh$buffer);
  550.                 else {
  551.                     $data .= $buffer;
  552.                 }
  553.             }
  554.             break;
  555.  
  556.         case '8bit':
  557.         case '7bit':
  558.         default:
  559.             while (!feof($fd)) {
  560.                 $buffer fread($fd1048576)// 1 MB
  561.                 if ($fh{
  562.                     fwrite($fh$buffer);
  563.                 else {
  564.                     $data .= $buffer;
  565.                 }
  566.             }
  567.         }
  568.  
  569.         fclose($fd);
  570.  
  571.         if (!$fh{
  572.             return $data;
  573.         }
  574.     }
  575.  
  576.     /**
  577.      * Encodes data to quoted-printable standard.
  578.      *
  579.      * @param string $input    The data to encode
  580.      * @param int    $line_max Optional max line length. Should
  581.      *                          not be more than 76 chars
  582.      *
  583.      * @return string Encoded data
  584.      *
  585.      * @access private
  586.      */
  587.     function _quotedPrintableEncode($input $line_max = 76)
  588.     {
  589.         $eol $this->_eol;
  590.         /*
  591.         // imap_8bit() is extremely fast, but doesn't handle properly some characters
  592.         if (function_exists('imap_8bit') && $line_max == 76) {
  593.             $input = preg_replace('/\r?\n/', "\r\n", $input);
  594.             $input = imap_8bit($input);
  595.             if ($eol != "\r\n") {
  596.                 $input = str_replace("\r\n", $eol, $input);
  597.             }
  598.             return $input;
  599.         }
  600.         */
  601.         $lines  preg_split("/\r?\n/"$input);
  602.         $escape '=';
  603.         $output '';
  604.  
  605.         while (list($idx$lineeach($lines)) {
  606.             $newline '';
  607.             $i = 0;
  608.  
  609.             while (isset($line[$i])) {
  610.                 $char $line[$i];
  611.                 $dec  ord($char);
  612.                 $i++;
  613.  
  614.                 if (($dec == 32&& (!isset($line[$i]))) {
  615.                     // convert space at eol only
  616.                     $char '=20';
  617.                 elseif ($dec == 9 && isset($line[$i])) {
  618.                     ; // Do nothing if a TAB is not on eol
  619.                 elseif (($dec == 61|| ($dec < 32|| ($dec > 126)) {
  620.                     $char $escape sprintf('%02X'$dec);
  621.                 elseif (($dec == 46&& (($newline == '')
  622.                     || ((strlen($newlinestrlen("=2E")) >= $line_max))
  623.                 {
  624.                     // Bug #9722: convert full-stop at bol,
  625.                     // some Windows servers need this, won't break anything (cipri)
  626.                     // Bug #11731: full-stop at bol also needs to be encoded
  627.                     // if this line would push us over the line_max limit.
  628.                     $char '=2E';
  629.                 }
  630.  
  631.                 // Note, when changing this line, also change the ($dec == 46)
  632.                 // check line, as it mimics this line due to Bug #11731
  633.                 // EOL is not counted
  634.                 if ((strlen($newlinestrlen($char)) >= $line_max{
  635.                     // soft line break; " =\r\n" is okay
  636.                     $output  .= $newline $escape $eol;
  637.                     $newline  '';
  638.                 }
  639.                 $newline .= $char;
  640.             // end of for
  641.             $output .= $newline $eol;
  642.             unset($lines[$idx]);
  643.         }
  644.         // Don't want last crlf
  645.         $output substr($output0-1 * strlen($eol));
  646.         return $output;
  647.     }
  648.  
  649.     /**
  650.      * Encodes the parameter of a header.
  651.      *
  652.      * @param string $name      The name of the header-parameter
  653.      * @param string $value     The value of the paramter
  654.      * @param string $charset   The characterset of $value
  655.      * @param string $language  The language used in $value
  656.      * @param string $encoding  Parameter encoding. If not set, parameter value
  657.      *                           is encoded according to RFC2231
  658.      * @param int    $maxLength The maximum length of a line. Defauls to 75
  659.      *
  660.      * @return string 
  661.      *
  662.      * @access private
  663.      */
  664.     function _buildHeaderParam($name$value$charset=null$language=null,
  665.         $encoding=null$maxLength=75
  666.     {
  667.         // RFC 2045:
  668.         // value needs encoding if contains non-ASCII chars or is longer than 78 chars
  669.         if (!preg_match('#[^\x20-\x7E]#'$value)) {
  670.             $token_regexp '#([^\x21\x23-\x27\x2A\x2B\x2D'
  671.                 . '\x2E\x30-\x39\x41-\x5A\x5E-\x7E])#';
  672.             if (!preg_match($token_regexp$value)) {
  673.                 // token
  674.                 if (strlen($namestrlen($value+ 3 <= $maxLength{
  675.                     return " {$name}={$value}";
  676.                 }
  677.             else {
  678.                 // quoted-string
  679.                 $quoted addcslashes($value'\\"');
  680.                 if (strlen($namestrlen($quoted+ 5 <= $maxLength{
  681.                     return " {$name}=\"{$quoted}\"";
  682.                 }
  683.             }
  684.         }
  685.  
  686.         // RFC2047: use quoted-printable/base64 encoding
  687.         if ($encoding == 'quoted-printable' || $encoding == 'base64'{
  688.             return $this->_buildRFC2047Param($name$value$charset$encoding);
  689.         }
  690.  
  691.         // RFC2231:
  692.         $encValue preg_replace_callback(
  693.             '/([^\x21\x23\x24\x26\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E])/',
  694.             array($this'_encodeReplaceCallback')$value
  695.         );
  696.         $value = "$charset'$language'$encValue";
  697.  
  698.         $header = " {$name}*={$value}";
  699.         if (strlen($header<= $maxLength{
  700.             return $header;
  701.         }
  702.  
  703.         $preLength strlen(" {$name}*0*=");
  704.         $maxLength max(16$maxLength $preLength - 3);
  705.         $maxLengthReg = "|(.{0,$maxLength}[^\%][^\%])|";
  706.  
  707.         $headers = array();
  708.         $headCount = 0;
  709.         while ($value{
  710.             $matches = array();
  711.             $found preg_match($maxLengthReg$value$matches);
  712.             if ($found{
  713.                 $headers[= " {$name}*{$headCount}*={$matches[0]}";
  714.                 $value substr($valuestrlen($matches[0]));
  715.             else {
  716.                 $headers[= " {$name}*{$headCount}*={$value}";
  717.                 $value '';
  718.             }
  719.             $headCount++;
  720.         }
  721.  
  722.         $headers implode(';' $this->_eol$headers);
  723.         return $headers;
  724.     }
  725.  
  726.     /**
  727.      * Encodes header parameter as per RFC2047 if needed
  728.      *
  729.      * @param string $name      The parameter name
  730.      * @param string $value     The parameter value
  731.      * @param string $charset   The parameter charset
  732.      * @param string $encoding  Encoding type (quoted-printable or base64)
  733.      * @param int    $maxLength Encoded parameter max length. Default: 76
  734.      *
  735.      * @return string Parameter line
  736.      * @access private
  737.      */
  738.     function _buildRFC2047Param($name$value$charset,
  739.         $encoding='quoted-printable'$maxLength=76
  740.     {
  741.         // WARNING: RFC 2047 says: "An 'encoded-word' MUST NOT be used in
  742.         // parameter of a MIME Content-Type or Content-Disposition field",
  743.         // but... it's supported by many clients/servers
  744.         $quoted '';
  745.  
  746.         if ($encoding == 'base64'{
  747.             $value base64_encode($value);
  748.             $prefix '=?' $charset '?B?';
  749.             $suffix '?=';
  750.  
  751.             // 2 x SPACE, 2 x '"', '=', ';'
  752.             $add_len strlen($prefix $suffixstrlen($name+ 6;
  753.             $len $add_len strlen($value);
  754.  
  755.             while ($len $maxLength
  756.                 // We can cut base64-encoded string every 4 characters
  757.                 $real_len floor(($maxLength $add_len/ 4* 4;
  758.                 $_quote substr($value0$real_len);
  759.                 $value substr($value$real_len);
  760.  
  761.                 $quoted .= $prefix $_quote $suffix $this->_eol ' ';
  762.                 $add_len strlen($prefix $suffix+ 4; // 2 x SPACE, '"', ';'
  763.                 $len strlen($value$add_len;
  764.             }
  765.             $quoted .= $prefix $value $suffix;
  766.  
  767.         else {
  768.             // quoted-printable
  769.             $value $this->encodeQP($value);
  770.             $prefix '=?' $charset '?Q?';
  771.             $suffix '?=';
  772.  
  773.             // 2 x SPACE, 2 x '"', '=', ';'
  774.             $add_len strlen($prefix $suffixstrlen($name+ 6;
  775.             $len $add_len strlen($value);
  776.  
  777.             while ($len $maxLength{
  778.                 $length $maxLength $add_len;
  779.                 // don't break any encoded letters
  780.                 if (preg_match("/^(.{0,$length}[^\=][^\=])/"$value$matches)) {
  781.                     $_quote $matches[1];
  782.                 }
  783.  
  784.                 $quoted .= $prefix $_quote $suffix $this->_eol ' ';
  785.                 $value substr($valuestrlen($_quote));
  786.                 $add_len strlen($prefix $suffix+ 4; // 2 x SPACE, '"', ';'
  787.                 $len strlen($value$add_len;
  788.             }
  789.  
  790.             $quoted .= $prefix $value $suffix;
  791.         }
  792.  
  793.         return " {$name}=\"{$quoted}\"";
  794.     }
  795.  
  796.     /**
  797.      * Encodes a header as per RFC2047
  798.      *
  799.      * @param string $name     The header name
  800.      * @param string $value    The header data to encode
  801.      * @param string $charset  Character set name
  802.      * @param string $encoding Encoding name (base64 or quoted-printable)
  803.      * @param string $eol      End-of-line sequence. Default: "\r\n"
  804.      *
  805.      * @return string          Encoded header data (without a name)
  806.      * @access public
  807.      * @since 1.6.1
  808.      */
  809.     function encodeHeader($name$value$charset='ISO-8859-1',
  810.         $encoding='quoted-printable'$eol="\r\n"
  811.     {
  812.         // Structured headers
  813.         $comma_headers = array(
  814.             'from''to''cc''bcc''sender''reply-to',
  815.             'resent-from''resent-to''resent-cc''resent-bcc',
  816.             'resent-sender''resent-reply-to',
  817.             'mail-reply-to''mail-followup-to',
  818.             'return-receipt-to''disposition-notification-to',
  819.         );
  820.         $other_headers = array(
  821.             'references''in-reply-to''message-id''resent-message-id',
  822.         );
  823.  
  824.         $name strtolower($name);
  825.  
  826.         if (in_array($name$comma_headers)) {
  827.             $separator ',';
  828.         else if (in_array($name$other_headers)) {
  829.             $separator ' ';
  830.         }
  831.  
  832.         if (!$charset{
  833.             $charset 'ISO-8859-1';
  834.         }
  835.  
  836.         // Structured header (make sure addr-spec inside is not encoded)
  837.         if (!empty($separator)) {
  838.             // Simple e-mail address regexp
  839.             $email_regexp '([^\s<]+|("[^\r\n"]+"))@\S+';
  840.  
  841.             $parts Mail_mimePart::_explodeQuotedString($separator$value);
  842.             $value '';
  843.  
  844.             foreach ($parts as $part{
  845.                 $part preg_replace('/\r?\n[\s\t]*/'$eol ' '$part);
  846.                 $part trim($part);
  847.  
  848.                 if (!$part{
  849.                     continue;
  850.                 }
  851.                 if ($value{
  852.                     $value .= $separator==',' $separator.' ' ' ';
  853.                 else {
  854.                     $value $name ': ';
  855.                 }
  856.  
  857.                 // let's find phrase (name) and/or addr-spec
  858.                 if (preg_match('/^<' $email_regexp '>$/'$part)) {
  859.                     $value .= $part;
  860.                 else if (preg_match('/^' $email_regexp '$/'$part)) {
  861.                     // address without brackets and without name
  862.                     $value .= $part;
  863.                 else if (preg_match('/<*' $email_regexp '>*$/'$part$matches)) {
  864.                     // address with name (handle name)
  865.                     $address $matches[0];
  866.                     $word str_replace($address''$part);
  867.                     $word trim($word);
  868.                     // check if phrase requires quoting
  869.                     if ($word{
  870.                         // non-ASCII: require encoding
  871.                         if (preg_match('#([\x80-\xFF]){1}#'$word)) {
  872.                             if ($word[0== '"' && $word[strlen($word)-1== '"'{
  873.                                 // de-quote quoted-string, encoding changes
  874.                                 // string to atom
  875.                                 $search = array("\\\"""\\\\");
  876.                                 $replace = array("\"""\\");
  877.                                 $word str_replace($search$replace$word);
  878.                                 $word substr($word1-1);
  879.                             }
  880.                             // find length of last line
  881.                             if (($pos strrpos($value$eol)) !== false{
  882.                                 $last_len strlen($value$pos;
  883.                             else {
  884.                                 $last_len strlen($value);
  885.                             }
  886.                             $word Mail_mimePart::encodeHeaderValue(
  887.                                 $word$charset$encoding$last_len$eol
  888.                             );
  889.                         else if (($word[0!= '"' || $word[strlen($word)-1!= '"')
  890.                             && preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/'$word)
  891.                         {
  892.                             // ASCII: quote string if needed
  893.                             $word '"'.addcslashes($word'\\"').'"';
  894.                         }
  895.                     }
  896.                     $value .= $word.' '.$address;
  897.                 else {
  898.                     // addr-spec not found, don't encode (?)
  899.                     $value .= $part;
  900.                 }
  901.  
  902.                 // RFC2822 recommends 78 characters limit, use 76 from RFC2047
  903.                 $value wordwrap($value76$eol ' ');
  904.             }
  905.  
  906.             // remove header name prefix (there could be EOL too)
  907.             $value preg_replace(
  908.                 '/^'.$name.':('.preg_quote($eol'/').')* /'''$value
  909.             );
  910.  
  911.         else {
  912.             // Unstructured header
  913.             // non-ASCII: require encoding
  914.             if (preg_match('#([\x80-\xFF]){1}#'$value)) {
  915.                 if ($value[0== '"' && $value[strlen($value)-1== '"'{
  916.                     // de-quote quoted-string, encoding changes
  917.                     // string to atom
  918.                     $search = array("\\\"""\\\\");
  919.                     $replace = array("\"""\\");
  920.                     $value str_replace($search$replace$value);
  921.                     $value substr($value1-1);
  922.                 }
  923.                 $value Mail_mimePart::encodeHeaderValue(
  924.                     $value$charset$encodingstrlen($name+ 2$eol
  925.                 );
  926.             else if (strlen($name.': '.$value> 78{
  927.                 // ASCII: check if header line isn't too long and use folding
  928.                 $value preg_replace('/\r?\n[\s\t]*/'$eol ' '$value);
  929.                 $tmp wordwrap($name.': '.$value78$eol ' ');
  930.                 $value preg_replace('/^'.$name.':\s*/'''$tmp);
  931.                 // hard limit 998 (RFC2822)
  932.                 $value wordwrap($value998$eol ' 'true);
  933.             }
  934.         }
  935.  
  936.         return $value;
  937.     }
  938.  
  939.     /**
  940.      * Explode quoted string
  941.      *
  942.      * @param string $delimiter Delimiter expression string for preg_match()
  943.      * @param string $string    Input string
  944.      *
  945.      * @return array            String tokens array
  946.      * @access private
  947.      */
  948.     function _explodeQuotedString($delimiter$string)
  949.     {
  950.         $result = array();
  951.         $strlen strlen($string);
  952.  
  953.         for ($q=$p=$i=0; $i $strlen$i++{
  954.             if ($string[$i== "\""
  955.                 && (empty($string[$i-1]|| $string[$i-1!= "\\")
  956.             {
  957.                 $q $q ? false : true;
  958.             else if (!$q && preg_match("/$delimiter/"$string[$i])) {
  959.                 $result[substr($string$p$i $p);
  960.                 $p $i + 1;
  961.             }
  962.         }
  963.  
  964.         $result[substr($string$p);
  965.         return $result;
  966.     }
  967.  
  968.     /**
  969.      * Encodes a header value as per RFC2047
  970.      *
  971.      * @param string $value      The header data to encode
  972.      * @param string $charset    Character set name
  973.      * @param string $encoding   Encoding name (base64 or quoted-printable)
  974.      * @param int    $prefix_len Prefix length. Default: 0
  975.      * @param string $eol        End-of-line sequence. Default: "\r\n"
  976.      *
  977.      * @return string            Encoded header data
  978.      * @access public
  979.      * @since 1.6.1
  980.      */
  981.     function encodeHeaderValue($value$charset$encoding$prefix_len=0$eol="\r\n")
  982.     {
  983.         // #17311: Use multibyte aware method (requires mbstring extension)
  984.         if ($result Mail_mimePart::encodeMB($value$charset$encoding$prefix_len$eol)) {
  985.             return $result;
  986.         }
  987.  
  988.         // Generate the header using the specified params and dynamicly
  989.         // determine the maximum length of such strings.
  990.         // 75 is the value specified in the RFC.
  991.         $encoding $encoding == 'base64' 'B' 'Q';
  992.         $prefix '=?' $charset '?' $encoding .'?';
  993.         $suffix '?=';
  994.         $maxLength = 75 - strlen($prefix $suffix);
  995.         $maxLength1stLine $maxLength $prefix_len;
  996.  
  997.         if ($encoding == 'B'{
  998.             // Base64 encode the entire string
  999.             $value base64_encode($value);
  1000.  
  1001.             // We can cut base64 every 4 characters, so the real max
  1002.             // we can get must be rounded down.
  1003.             $maxLength $maxLength ($maxLength % 4);
  1004.             $maxLength1stLine $maxLength1stLine ($maxLength1stLine % 4);
  1005.  
  1006.             $cutpoint $maxLength1stLine;
  1007.             $output '';
  1008.  
  1009.             while ($value{
  1010.                 // Split translated string at every $maxLength
  1011.                 $part substr($value0$cutpoint);
  1012.                 $value substr($value$cutpoint);
  1013.                 $cutpoint $maxLength;
  1014.                 // RFC 2047 specifies that any split header should
  1015.                 // be separated by a CRLF SPACE.
  1016.                 if ($output{
  1017.                     $output .= $eol ' ';
  1018.                 }
  1019.                 $output .= $prefix $part $suffix;
  1020.             }
  1021.             $value $output;
  1022.         else {
  1023.             // quoted-printable encoding has been selected
  1024.             $value Mail_mimePart::encodeQP($value);
  1025.  
  1026.             // This regexp will break QP-encoded text at every $maxLength
  1027.             // but will not break any encoded letters.
  1028.             $reg1st = "|(.{0,$maxLength1stLine}[^\=][^\=])|";
  1029.             $reg2nd = "|(.{0,$maxLength}[^\=][^\=])|";
  1030.  
  1031.             if (strlen($value$maxLength1stLine{
  1032.                 // Begin with the regexp for the first line.
  1033.                 $reg $reg1st;
  1034.                 $output '';
  1035.                 while ($value{
  1036.                     // Split translated string at every $maxLength
  1037.                     // But make sure not to break any translated chars.
  1038.                     $found preg_match($reg$value$matches);
  1039.  
  1040.                     // After this first line, we need to use a different
  1041.                     // regexp for the first line.
  1042.                     $reg $reg2nd;
  1043.  
  1044.                     // Save the found part and encapsulate it in the
  1045.                     // prefix & suffix. Then remove the part from the
  1046.                     // $value_out variable.
  1047.                     if ($found{
  1048.                         $part $matches[0];
  1049.                         $len strlen($matches[0]);
  1050.                         $value substr($value$len);
  1051.                     else {
  1052.                         $part $value;
  1053.                         $value '';
  1054.                     }
  1055.  
  1056.                     // RFC 2047 specifies that any split header should
  1057.                     // be separated by a CRLF SPACE
  1058.                     if ($output{
  1059.                         $output .= $eol ' ';
  1060.                     }
  1061.                     $output .= $prefix $part $suffix;
  1062.                 }
  1063.                 $value $output;
  1064.             else {
  1065.                 $value $prefix $value $suffix;
  1066.             }
  1067.         }
  1068.  
  1069.         return $value;
  1070.     }
  1071.  
  1072.     /**
  1073.      * Encodes the given string using quoted-printable
  1074.      *
  1075.      * @param string $str String to encode
  1076.      *
  1077.      * @return string     Encoded string
  1078.      * @access public
  1079.      * @since 1.6.0
  1080.      */
  1081.     function encodeQP($str)
  1082.     {
  1083.         // Bug #17226 RFC 2047 restricts some characters
  1084.         // if the word is inside a phrase, permitted chars are only:
  1085.         // ASCII letters, decimal digits, "!", "*", "+", "-", "/", "=", and "_"
  1086.  
  1087.         // "=",  "_",  "?" must be encoded
  1088.         $regexp '/([\x22-\x29\x2C\x2E\x3A-\x40\x5B-\x60\x7B-\x7E\x80-\xFF])/';
  1089.         $str preg_replace_callback(
  1090.             $regexparray('Mail_mimePart''_qpReplaceCallback')$str
  1091.         );
  1092.  
  1093.         return str_replace(' ''_'$str);
  1094.     }
  1095.  
  1096.     /**
  1097.      * Encodes the given string using base64 or quoted-printable.
  1098.      * This method makes sure that encoded-word represents an integral
  1099.      * number of characters as per RFC2047.
  1100.      *
  1101.      * @param string $str        String to encode
  1102.      * @param string $charset    Character set name
  1103.      * @param string $encoding   Encoding name (base64 or quoted-printable)
  1104.      * @param int    $prefix_len Prefix length. Default: 0
  1105.      * @param string $eol        End-of-line sequence. Default: "\r\n"
  1106.      *
  1107.      * @return string     Encoded string
  1108.      * @access public
  1109.      * @since 1.8.0
  1110.      */
  1111.     function encodeMB($str$charset$encoding$prefix_len=0$eol="\r\n")
  1112.     {
  1113.         if (!function_exists('mb_substr'|| !function_exists('mb_strlen')) {
  1114.             return;
  1115.         }
  1116.  
  1117.         $encoding $encoding == 'base64' 'B' 'Q';
  1118.         // 75 is the value specified in the RFC
  1119.         $prefix '=?' $charset '?'.$encoding.'?';
  1120.         $suffix '?=';
  1121.         $maxLength = 75 - strlen($prefix $suffix);
  1122.  
  1123.         // A multi-octet character may not be split across adjacent encoded-words
  1124.         // So, we'll loop over each character
  1125.         // mb_stlen() with wrong charset will generate a warning here and return null
  1126.         $length      mb_strlen($str$charset);
  1127.         $result      '';
  1128.         $line_length $prefix_len;
  1129.  
  1130.         if ($encoding == 'B'{
  1131.             // base64
  1132.             $start = 0;
  1133.             $prev  '';
  1134.  
  1135.             for ($i=1; $i<=$length$i++{
  1136.                 // See #17311
  1137.                 $chunk mb_substr($str$start$i-$start$charset);
  1138.                 $chunk base64_encode($chunk);
  1139.                 $chunk_len strlen($chunk);
  1140.  
  1141.                 if ($line_length $chunk_len == $maxLength || $i == $length{
  1142.                     if ($result{
  1143.                         $result .= "\n";
  1144.                     }
  1145.                     $result .= $chunk;
  1146.                     $line_length = 0;
  1147.                     $start $i;
  1148.                 else if ($line_length $chunk_len $maxLength{
  1149.                     if ($result{
  1150.                         $result .= "\n";
  1151.                     }
  1152.                     if ($prev{
  1153.                         $result .= $prev;
  1154.                     }
  1155.                     $line_length = 0;
  1156.                     $start $i - 1;
  1157.                 else {
  1158.                     $prev $chunk;
  1159.                 }
  1160.             }
  1161.         else {
  1162.             // quoted-printable
  1163.             // see encodeQP()
  1164.             $regexp '/([\x22-\x29\x2C\x2E\x3A-\x40\x5B-\x60\x7B-\x7E\x80-\xFF])/';
  1165.  
  1166.             for ($i=0; $i<=$length$i++{
  1167.                 $char mb_substr($str$i1$charset);
  1168.                 // RFC recommends underline (instead of =20) in place of the space
  1169.                 // that's one of the reasons why we're not using iconv_mime_encode()
  1170.                 if ($char == ' '{
  1171.                     $char '_';
  1172.                     $char_len = 1;
  1173.                 else {
  1174.                     $char preg_replace_callback(
  1175.                         $regexparray('Mail_mimePart''_qpReplaceCallback')$char
  1176.                     );
  1177.                     $char_len strlen($char);
  1178.                 }
  1179.  
  1180.                 if ($line_length $char_len $maxLength{
  1181.                     if ($result{
  1182.                         $result .= "\n";
  1183.                     }
  1184.                     $line_length = 0;
  1185.                 }
  1186.  
  1187.                 $result      .= $char;
  1188.                 $line_length += $char_len;
  1189.             }
  1190.         }
  1191.  
  1192.         if ($result{
  1193.             $result $prefix
  1194.                 .str_replace("\n"$suffix.$eol.' '.$prefix$result).$suffix;
  1195.         }
  1196.  
  1197.         return $result;
  1198.     }
  1199.  
  1200.     /**
  1201.      * Callback function to replace extended characters (\x80-xFF) with their
  1202.      * ASCII values (RFC2047: quoted-printable)
  1203.      *
  1204.      * @param array $matches Preg_replace's matches array
  1205.      *
  1206.      * @return string        Encoded character string
  1207.      * @access private
  1208.      */
  1209.     function _qpReplaceCallback($matches)
  1210.     {
  1211.         return sprintf('=%02X'ord($matches[1]));
  1212.     }
  1213.  
  1214.     /**
  1215.      * Callback function to replace extended characters (\x80-xFF) with their
  1216.      * ASCII values (RFC2231)
  1217.      *
  1218.      * @param array $matches Preg_replace's matches array
  1219.      *
  1220.      * @return string        Encoded character string
  1221.      * @access private
  1222.      */
  1223.     function _encodeReplaceCallback($matches)
  1224.     {
  1225.         return sprintf('%%%02X'ord($matches[1]));
  1226.     }
  1227.  
  1228.     /**
  1229.      * PEAR::isError implementation
  1230.      *
  1231.      * @param mixed $data Object
  1232.      *
  1233.      * @return bool True if object is an instance of PEAR_Error
  1234.      * @access private
  1235.      */
  1236.     function _isError($data)
  1237.     {
  1238.         // PEAR::isError() is not PHP 5.4 compatible (see Bug #19473)
  1239.         if (is_object($data&& is_a($data'PEAR_Error')) {
  1240.             return true;
  1241.         }
  1242.  
  1243.         return false;
  1244.     }
  1245.  
  1246.     /**
  1247.      * PEAR::raiseError implementation
  1248.      *
  1249.      * @param $message A text error message
  1250.      *
  1251.      * @return PEAR_Error Instance of PEAR_Error
  1252.      * @access private
  1253.      */
  1254.     function _raiseError($message)
  1255.     {
  1256.         // PEAR::raiseError() is not PHP 5.4 compatible
  1257.         return new PEAR_Error($message);
  1258.     }
  1259.  
  1260. // End of class

Documentation generated on Fri, 05 Jul 2013 11:00:05 +0000 by phpDocumentor 1.4.3. PEAR Logo Copyright © PHP Group 2004.