"Fossies" - the Fresh Open Source Software Archive

Member "z-push/include/mimePart.php" (2 Aug 2013, 39876 Bytes) of package /linux/www/old/group-e_z-push_v3.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "mimePart.php" see the Fossies "Dox" file reference documentation.

    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: mimePart.php 296266 2010-03-16 10:21:59Z alec $
   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 class Mail_mimePart
   77 {
   78     /**
   79     * The encoding type of this part
   80     *
   81     * @var string
   82     * @access private
   83     */
   84     var $_encoding;
   85 
   86     /**
   87     * An array of subparts
   88     *
   89     * @var array
   90     * @access private
   91     */
   92     var $_subparts;
   93 
   94     /**
   95     * The output of this part after being built
   96     *
   97     * @var string
   98     * @access private
   99     */
  100     var $_encoded;
  101 
  102     /**
  103     * Headers for this part
  104     *
  105     * @var array
  106     * @access private
  107     */
  108     var $_headers;
  109 
  110     /**
  111     * The body of this part (not encoded)
  112     *
  113     * @var string
  114     * @access private
  115     */
  116     var $_body;
  117 
  118     /**
  119     * The location of file with body of this part (not encoded)
  120     *
  121     * @var string
  122     * @access private
  123     */
  124     var $_body_file;
  125 
  126     /**
  127     * The end-of-line sequence
  128     *
  129     * @var string
  130     * @access private
  131     */
  132     var $_eol = "\r\n";
  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     *     cid               - Content ID to apply
  145     *     disposition       - Content disposition, inline or attachment
  146     *     dfilename         - Filename parameter for content disposition
  147     *     description       - Content description
  148     *     charset           - Character set to use
  149     *     name_encoding     - Encoding for 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 for attachment filename (Content-Disposition)
  154     *                         See 'name_encoding'
  155     *     eol               - End of line sequence. Default: "\r\n"
  156     *     body_file         - Location of file with part's body (instead of $body)
  157     *
  158     * @access public
  159     */
  160     function Mail_mimePart($body = '', $params = array())
  161     {
  162         if (!empty($params['eol'])) {
  163             $this->_eol = $params['eol'];
  164         } else if (defined('MAIL_MIMEPART_CRLF')) { // backward-copat.
  165             $this->_eol = MAIL_MIMEPART_CRLF;
  166         }
  167 
  168         $c_type = array();
  169         $c_disp = array();
  170         foreach ($params as $key => $value) {
  171             switch ($key) {
  172             case 'content_type':
  173                 $c_type['type'] = $value;
  174                 break;
  175 
  176             case 'encoding':
  177                 $this->_encoding = $value;
  178                 $headers['Content-Transfer-Encoding'] = $value;
  179                 break;
  180 
  181             case 'cid':
  182                 $headers['Content-ID'] = '<' . $value . '>';
  183                 break;
  184 
  185             case 'disposition':
  186                 $c_disp['disp'] = $value;
  187                 break;
  188 
  189             case 'dfilename':
  190                 $c_disp['filename'] = $value;
  191                 $c_type['name'] = $value;
  192                 break;
  193 
  194             case 'description':
  195                 $headers['Content-Description'] = $value;
  196                 break;
  197 
  198             case 'charset':
  199                 $c_type['charset'] = $value;
  200                 $c_disp['charset'] = $value;
  201                 break;
  202 
  203             case 'language':
  204                 $c_type['language'] = $value;
  205                 $c_disp['language'] = $value;
  206                 break;
  207 
  208             case 'location':
  209                 $headers['Content-Location'] = $value;
  210                 break;
  211 
  212             case 'body_file':
  213                 $this->_body_file = $value;
  214                 break;
  215             }
  216         }
  217 
  218         // Content-Type
  219         if (isset($c_type['type'])) {
  220             $headers['Content-Type'] = $c_type['type'];
  221             if (isset($c_type['name'])) {
  222                 $headers['Content-Type'] .= ';' . $this->_eol;
  223                 $headers['Content-Type'] .= $this->_buildHeaderParam(
  224                     'name', $c_type['name'], 
  225                     isset($c_type['charset']) ? $c_type['charset'] : 'US-ASCII', 
  226                     isset($c_type['language']) ? $c_type['language'] : null,
  227                     isset($params['name_encoding']) ?  $params['name_encoding'] : null
  228                 );
  229             }
  230             if (isset($c_type['charset'])) {
  231                 $headers['Content-Type']
  232                     .= ';' . $this->_eol . " charset={$c_type['charset']}";
  233             }
  234         }
  235 
  236         // Content-Disposition
  237         if (isset($c_disp['disp'])) {
  238             $headers['Content-Disposition'] = $c_disp['disp'];
  239             if (isset($c_disp['filename'])) {
  240                 $headers['Content-Disposition'] .= ';' . $this->_eol;
  241                 $headers['Content-Disposition'] .= $this->_buildHeaderParam(
  242                     'filename', $c_disp['filename'], 
  243                     isset($c_disp['charset']) ? $c_disp['charset'] : 'US-ASCII', 
  244                     isset($c_disp['language']) ? $c_disp['language'] : null,
  245                     isset($params['filename_encoding']) ?  $params['filename_encoding'] : null
  246                 );
  247             }
  248         }
  249 
  250         if (!empty($headers['Content-Description'])) {
  251             $headers['Content-Description'] = Mail_mimePart::encodeHeader(
  252                 'Content-Description', $headers['Content-Description'],
  253                 isset($c_type['charset']) ? $c_type['charset'] : 'US-ASCII',
  254                 isset($params['name_encoding']) ?  $params['name_encoding'] : 'quoted-printable',
  255                 $this->_eol
  256             );
  257         }
  258 
  259         // Default content-type
  260         if (!isset($headers['Content-Type'])) {
  261             $headers['Content-Type'] = 'text/plain';
  262         }
  263 
  264         // Default encoding
  265         if (!isset($this->_encoding)) {
  266             $this->_encoding = '7bit';
  267         }
  268 
  269         // Assign stuff to member variables
  270         $this->_encoded  = array();
  271         $this->_headers  = $headers;
  272         $this->_body     = $body;
  273     }
  274 
  275     /**
  276      * Encodes and returns the email. Also stores
  277      * it in the encoded member variable
  278      *
  279      * @param string $boundary Pre-defined boundary string
  280      *
  281      * @return An associative array containing two elements,
  282      *         body and headers. The headers element is itself
  283      *         an indexed array. On error returns PEAR error object.
  284      * @access public
  285      */
  286     function encode($boundary=null)
  287     {
  288         $encoded =& $this->_encoded;
  289 
  290         if (count($this->_subparts)) {
  291             $boundary = $boundary ? $boundary : '=_' . md5(rand() . microtime());
  292             $eol = $this->_eol;
  293 
  294             $this->_headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
  295 
  296             $encoded['body'] = ''; 
  297 
  298             for ($i = 0; $i < count($this->_subparts); $i++) {
  299                 $encoded['body'] .= '--' . $boundary . $eol;
  300                 $tmp = $this->_subparts[$i]->encode();
  301                 if ($tmp === false) {
  302                     return $tmp;
  303                 }
  304                 foreach ($tmp['headers'] as $key => $value) {
  305                     $encoded['body'] .= $key . ': ' . $value . $eol;
  306                 }
  307                 $encoded['body'] .= $eol . $tmp['body'] . $eol;
  308             }
  309 
  310             $encoded['body'] .= '--' . $boundary . '--' . $eol;
  311 
  312         } else if ($this->_body) {
  313             $encoded['body'] = $this->_getEncodedData($this->_body, $this->_encoding);
  314         } else if ($this->_body_file) {
  315             // Temporarily reset magic_quotes_runtime for file reads and writes
  316             if ($magic_quote_setting = get_magic_quotes_runtime()) {
  317                 @ini_set('magic_quotes_runtime', 0);
  318             }
  319             $body = $this->_getEncodedDataFromFile($this->_body_file, $this->_encoding);
  320             if ($magic_quote_setting) {
  321                 @ini_set('magic_quotes_runtime', $magic_quote_setting);
  322             }
  323 
  324             if ($body === false) {
  325                 return $body;
  326             }
  327             $encoded['body'] = $body;
  328         } else {
  329             $encoded['body'] = '';
  330         }
  331 
  332         // Add headers to $encoded
  333         $encoded['headers'] =& $this->_headers;
  334 
  335         return $encoded;
  336     }
  337 
  338     /**
  339      * Encodes and saves the email into file. File must exist.
  340      * Data will be appended to the file.
  341      *
  342      * @param string  $filename  Output file location
  343      * @param string  $boundary  Pre-defined boundary string
  344      * @param boolean $skip_head True if you don't want to save headers
  345      *
  346      * @return array An associative array containing message headers
  347      *               or PEAR error object
  348      * @access public
  349      * @since 1.6.0
  350      */
  351     function encodeToFile($filename, $boundary=null, $skip_head=false)
  352     {
  353         if (file_exists($filename) && !is_writable($filename)) {
  354             debugLog('File is not writeable: ' . $filename);
  355             return false;
  356         }
  357 
  358         if (!($fh = fopen($filename, 'ab'))) {
  359             $err = debugLog('Unable to open file: ' . $filename);
  360             return false;
  361         }
  362 
  363         // Temporarily reset magic_quotes_runtime for file reads and writes
  364         if ($magic_quote_setting = get_magic_quotes_runtime()) {
  365             @ini_set('magic_quotes_runtime', 0);
  366         }
  367 
  368         $res = $this->_encodePartToFile($fh, $boundary, $skip_head);
  369 
  370         fclose($fh);
  371 
  372         if ($magic_quote_setting) {
  373             @ini_set('magic_quotes_runtime', $magic_quote_setting);
  374         }
  375 
  376         return $res === false ? $res : $this->_headers;
  377     }
  378 
  379     /**
  380      * Encodes given email part into file
  381      *
  382      * @param string  $fh        Output file handle
  383      * @param string  $boundary  Pre-defined boundary string
  384      * @param boolean $skip_head True if you don't want to save headers
  385      *
  386      * @return array True on sucess or PEAR error object
  387      * @access private
  388      */
  389     function _encodePartToFile($fh, $boundary=null, $skip_head=false)
  390     {
  391         $eol = $this->_eol;
  392 
  393         if (count($this->_subparts)) {
  394             $boundary = $boundary ? $boundary : '=_' . md5(rand() . microtime());
  395             $this->_headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
  396         }
  397 
  398         if (!$skip_head) {
  399             foreach ($this->_headers as $key => $value) {
  400                 fwrite($fh, $key . ': ' . $value . $eol);
  401             }
  402             $f_eol = $eol;
  403         } else {
  404             $f_eol = '';
  405         }
  406 
  407         if (count($this->_subparts)) {
  408             for ($i = 0; $i < count($this->_subparts); $i++) {
  409                 fwrite($fh, $f_eol . '--' . $boundary . $eol);
  410                 $res = $this->_subparts[$i]->_encodePartToFile($fh);
  411                 if ($res === false) {
  412                     return $res;
  413                 }
  414                 $f_eol = $eol;
  415             }
  416 
  417             fwrite($fh, $eol . '--' . $boundary . '--' . $eol);
  418 
  419         } else if ($this->_body) {
  420             fwrite($fh, $f_eol . $this->_getEncodedData($this->_body, $this->_encoding));
  421         } else if ($this->_body_file) {
  422             fwrite($fh, $f_eol);
  423             $res = $this->_getEncodedDataFromFile(
  424                 $this->_body_file, $this->_encoding, $fh
  425             );
  426             if ($res === false) {
  427                 return $res;
  428             }
  429         }
  430 
  431         return true;
  432     }
  433 
  434     /**
  435      * Adds a subpart to current mime part and returns
  436      * a reference to it
  437      *
  438      * @param string $body   The body of the subpart, if any.
  439      * @param array  $params The parameters for the subpart, same
  440      *                       as the $params argument for constructor.
  441      *
  442      * @return Mail_mimePart A reference to the part you just added. It is
  443      *                       crucial if using multipart/* in your subparts that
  444      *                       you use =& in your script when calling this function,
  445      *                       otherwise you will not be able to add further subparts.
  446      * @access public
  447      */
  448     function &addSubpart($body, $params)
  449     {
  450         $this->_subparts[] = new Mail_mimePart($body, $params);
  451         return $this->_subparts[count($this->_subparts) - 1];
  452     }
  453 
  454     /**
  455      * Returns encoded data based upon encoding passed to it
  456      *
  457      * @param string $data     The data to encode.
  458      * @param string $encoding The encoding type to use, 7bit, base64,
  459      *                         or quoted-printable.
  460      *
  461      * @return string
  462      * @access private
  463      */
  464     function _getEncodedData($data, $encoding)
  465     {
  466         switch ($encoding) {
  467         case 'quoted-printable':
  468             return $this->_quotedPrintableEncode($data);
  469             break;
  470 
  471         case 'base64':
  472             return rtrim(chunk_split(base64_encode($data), 76, $this->_eol));
  473             break;
  474 
  475         case '8bit':
  476         case '7bit':
  477         default:
  478             return $data;
  479         }
  480     }
  481 
  482     /**
  483      * Returns encoded data based upon encoding passed to it
  484      *
  485      * @param string   $filename Data file location
  486      * @param string   $encoding The encoding type to use, 7bit, base64,
  487      *                           or quoted-printable.
  488      * @param resource $fh       Output file handle. If set, data will be
  489      *                           stored into it instead of returning it
  490      *
  491      * @return string Encoded data or PEAR error object
  492      * @access private
  493      */
  494     function _getEncodedDataFromFile($filename, $encoding, $fh=null)
  495     {
  496         if (!is_readable($filename)) {
  497             debugLog('Unable to read file: ' . $filename);
  498             return false;
  499         }
  500 
  501         if (!($fd = fopen($filename, 'rb'))) {
  502             debugLog('Could not open file: ' . $filename);
  503             return false;
  504         }
  505 
  506         $data = '';
  507 
  508         switch ($encoding) {
  509         case 'quoted-printable':
  510             while (!feof($fd)) {
  511                 $buffer = $this->_quotedPrintableEncode(fgets($fd));
  512                 if ($fh) {
  513                     fwrite($fh, $buffer);
  514                 } else {
  515                     $data .= $buffer;
  516                 }
  517             }
  518             break;
  519 
  520         case 'base64':
  521             while (!feof($fd)) {
  522                 // Should read in a multiple of 57 bytes so that
  523                 // the output is 76 bytes per line. Don't use big chunks
  524                 // because base64 encoding is memory expensive
  525                 $buffer = fread($fd, 57 * 9198); // ca. 0.5 MB
  526                 $buffer = base64_encode($buffer);
  527                 $buffer = chunk_split($buffer, 76, $this->_eol);
  528                 if (feof($fd)) {
  529                     $buffer = rtrim($buffer);
  530                 }
  531 
  532                 if ($fh) {
  533                     fwrite($fh, $buffer);
  534                 } else {
  535                     $data .= $buffer;
  536                 }
  537             }
  538             break;
  539 
  540         case '8bit':
  541         case '7bit':
  542         default:
  543             while (!feof($fd)) {
  544                 $buffer = fread($fd, 1048576); // 1 MB
  545                 if ($fh) {
  546                     fwrite($fh, $buffer);
  547                 } else {
  548                     $data .= $buffer;
  549                 }
  550             }
  551         }
  552 
  553         fclose($fd);
  554 
  555         if (!$fh) {
  556             return $data;
  557         }
  558     }
  559 
  560     /**
  561      * Encodes data to quoted-printable standard.
  562      *
  563      * @param string $input    The data to encode
  564      * @param int    $line_max Optional max line length. Should
  565      *                         not be more than 76 chars
  566      *
  567      * @return string Encoded data
  568      *
  569      * @access private
  570      */
  571     function _quotedPrintableEncode($input , $line_max = 76)
  572     {
  573         $eol = $this->_eol;
  574         /*
  575         // imap_8bit() is extremely fast, but doesn't handle properly some characters
  576         if (function_exists('imap_8bit') && $line_max == 76) {
  577             $input = preg_replace('/\r?\n/', "\r\n", $input);
  578             $input = imap_8bit($input);
  579             if ($eol != "\r\n") {
  580                 $input = str_replace("\r\n", $eol, $input);
  581             }
  582             return $input;
  583         }
  584         */
  585         $lines  = preg_split("/\r?\n/", $input);
  586         $escape = '=';
  587         $output = '';
  588 
  589         while (list($idx, $line) = each($lines)) {
  590             $newline = '';
  591             $i = 0;
  592 
  593             while (isset($line[$i])) {
  594                 $char = $line[$i];
  595                 $dec  = ord($char);
  596                 $i++;
  597 
  598                 if (($dec == 32) && (!isset($line[$i]))) {
  599                     // convert space at eol only
  600                     $char = '=20';
  601                 } elseif ($dec == 9 && isset($line[$i])) {
  602                     ; // Do nothing if a TAB is not on eol
  603                 } elseif (($dec == 61) || ($dec < 32) || ($dec > 126)) {
  604                     $char = $escape . sprintf('%02X', $dec);
  605                 } elseif (($dec == 46) && (($newline == '')
  606                     || ((strlen($newline) + strlen("=2E")) >= $line_max))
  607                 ) {
  608                     // Bug #9722: convert full-stop at bol,
  609                     // some Windows servers need this, won't break anything (cipri)
  610                     // Bug #11731: full-stop at bol also needs to be encoded
  611                     // if this line would push us over the line_max limit.
  612                     $char = '=2E';
  613                 }
  614 
  615                 // Note, when changing this line, also change the ($dec == 46)
  616                 // check line, as it mimics this line due to Bug #11731
  617                 // EOL is not counted
  618                 if ((strlen($newline) + strlen($char)) >= $line_max) {
  619                     // soft line break; " =\r\n" is okay
  620                     $output  .= $newline . $escape . $eol;
  621                     $newline  = '';
  622                 }
  623                 $newline .= $char;
  624             } // end of for
  625             $output .= $newline . $eol;
  626             unset($lines[$idx]);
  627         }
  628         // Don't want last crlf
  629         $output = substr($output, 0, -1 * strlen($eol));
  630         return $output;
  631     }
  632 
  633     /**
  634      * Encodes the paramater of a header.
  635      *
  636      * @param string $name      The name of the header-parameter
  637      * @param string $value     The value of the paramter
  638      * @param string $charset   The characterset of $value
  639      * @param string $language  The language used in $value
  640      * @param string $encoding  Parameter encoding. If not set, parameter value
  641      *                          is encoded according to RFC2231
  642      * @param int    $maxLength The maximum length of a line. Defauls to 75
  643      *
  644      * @return string
  645      *
  646      * @access private
  647      */
  648     function _buildHeaderParam($name, $value, $charset=null, $language=null,
  649         $encoding=null, $maxLength=75
  650     ) {
  651         // RFC 2045:
  652         // value needs encoding if contains non-ASCII chars or is longer than 78 chars
  653         if (!preg_match('#[^\x20-\x7E]#', $value)) {
  654             $token_regexp = '#([^\x21,\x23-\x27,\x2A,\x2B,\x2D'
  655                 . ',\x2E,\x30-\x39,\x41-\x5A,\x5E-\x7E])#';
  656             if (!preg_match($token_regexp, $value)) {
  657                 // token
  658                 if (strlen($name) + strlen($value) + 3 <= $maxLength) {
  659                     return " {$name}={$value}";
  660                 }
  661             } else {
  662                 // quoted-string
  663                 $quoted = addcslashes($value, '\\"');
  664                 if (strlen($name) + strlen($quoted) + 5 <= $maxLength) {
  665                     return " {$name}=\"{$quoted}\"";
  666                 }
  667             }
  668         }
  669 
  670         // RFC2047: use quoted-printable/base64 encoding
  671         if ($encoding == 'quoted-printable' || $encoding == 'base64') {
  672             return $this->_buildRFC2047Param($name, $value, $charset, $encoding);
  673         }
  674 
  675         // RFC2231:
  676         $encValue = preg_replace_callback(
  677             '/([^\x21,\x23,\x24,\x26,\x2B,\x2D,\x2E,\x30-\x39,\x41-\x5A,\x5E-\x7E])/',
  678             array($this, '_encodeReplaceCallback'), $value
  679         );
  680         $value = "$charset'$language'$encValue";
  681 
  682         $header = " {$name}*={$value}";
  683         if (strlen($header) <= $maxLength) {
  684             return $header;
  685         }
  686 
  687         $preLength = strlen(" {$name}*0*=");
  688         $maxLength = max(16, $maxLength - $preLength - 3);
  689         $maxLengthReg = "|(.{0,$maxLength}[^\%][^\%])|";
  690 
  691         $headers = array();
  692         $headCount = 0;
  693         while ($value) {
  694             $matches = array();
  695             $found = preg_match($maxLengthReg, $value, $matches);
  696             if ($found) {
  697                 $headers[] = " {$name}*{$headCount}*={$matches[0]}";
  698                 $value = substr($value, strlen($matches[0]));
  699             } else {
  700                 $headers[] = " {$name}*{$headCount}*={$value}";
  701                 $value = '';
  702             }
  703             $headCount++;
  704         }
  705 
  706         $headers = implode(';' . $this->_eol, $headers);
  707         return $headers;
  708     }
  709 
  710     /**
  711      * Encodes header parameter as per RFC2047 if needed
  712      *
  713      * @param string $name      The parameter name
  714      * @param string $value     The parameter value
  715      * @param string $charset   The parameter charset
  716      * @param string $encoding  Encoding type (quoted-printable or base64)
  717      * @param int    $maxLength Encoded parameter max length. Default: 76
  718      *
  719      * @return string Parameter line
  720      * @access private
  721      */
  722     function _buildRFC2047Param($name, $value, $charset,
  723         $encoding='quoted-printable', $maxLength=76
  724     ) {
  725         // WARNING: RFC 2047 says: "An 'encoded-word' MUST NOT be used in
  726         // parameter of a MIME Content-Type or Content-Disposition field",
  727         // but... it's supported by many clients/servers
  728         $quoted = '';
  729 
  730         if ($encoding == 'base64') {
  731             $value = base64_encode($value);
  732             $prefix = '=?' . $charset . '?B?';
  733             $suffix = '?=';
  734 
  735             // 2 x SPACE, 2 x '"', '=', ';'
  736             $add_len = strlen($prefix . $suffix) + strlen($name) + 6;
  737             $len = $add_len + strlen($value);
  738 
  739             while ($len > $maxLength) { 
  740                 // We can cut base64-encoded string every 4 characters
  741                 $real_len = floor(($maxLength - $add_len) / 4) * 4;
  742                 $_quote = substr($value, 0, $real_len);
  743                 $value = substr($value, $real_len);
  744 
  745                 $quoted .= $prefix . $_quote . $suffix . $this->_eol . ' ';
  746                 $add_len = strlen($prefix . $suffix) + 4; // 2 x SPACE, '"', ';'
  747                 $len = strlen($value) + $add_len;
  748             }
  749             $quoted .= $prefix . $value . $suffix;
  750 
  751         } else {
  752             // quoted-printable
  753             $value = Mail_mimePart::encodeQP($value);
  754             $prefix = '=?' . $charset . '?Q?';
  755             $suffix = '?=';
  756 
  757             // 2 x SPACE, 2 x '"', '=', ';'
  758             $add_len = strlen($prefix . $suffix) + strlen($name) + 6;
  759             $len = $add_len + strlen($value);
  760 
  761             while ($len > $maxLength) {
  762                 $length = $maxLength - $add_len;
  763                 // don't break any encoded letters
  764                 if (preg_match("/^(.{0,$length}[^\=][^\=])/", $value, $matches)) {
  765                     $_quote = $matches[1];
  766                 }
  767 
  768                 $quoted .= $prefix . $_quote . $suffix . $this->_eol . ' ';
  769                 $value = substr($value, strlen($_quote));
  770                 $add_len = strlen($prefix . $suffix) + 4; // 2 x SPACE, '"', ';'
  771                 $len = strlen($value) + $add_len;
  772             }
  773 
  774             $quoted .= $prefix . $value . $suffix;
  775         }
  776 
  777         return " {$name}=\"{$quoted}\"";
  778     }
  779 
  780     /**
  781      * Encodes a header as per RFC2047
  782      *
  783      * @param string $name     The header name
  784      * @param string $value    The header data to encode
  785      * @param string $charset  Character set name
  786      * @param string $encoding Encoding name (base64 or quoted-printable)
  787      * @param string $eol      End-of-line sequence. Default: "\r\n"
  788      *
  789      * @return string          Encoded header data (without a name)
  790      * @access public
  791      * @since 1.6.1
  792      */
  793     static function encodeHeader($name, $value, $charset='ISO-8859-1',
  794         $encoding='quoted-printable', $eol="\r\n"
  795     ) {
  796         // Structured headers
  797         $comma_headers = array(
  798             'from', 'to', 'cc', 'bcc', 'sender', 'reply-to',
  799             'resent-from', 'resent-to', 'resent-cc', 'resent-bcc',
  800             'resent-sender', 'resent-reply-to',
  801             'return-receipt-to', 'disposition-notification-to',
  802         );
  803         $other_headers = array(
  804             'references', 'in-reply-to', 'message-id', 'resent-message-id',
  805         );
  806 
  807         $name = strtolower($name);
  808 
  809         if (in_array($name, $comma_headers)) {
  810             $separator = ',';
  811         } else if (in_array($name, $other_headers)) {
  812             $separator = ' ';
  813         }
  814 
  815         if (!$charset) {
  816             $charset = 'ISO-8859-1';
  817         }
  818 
  819         // Structured header (make sure addr-spec inside is not encoded)
  820         if (!empty($separator)) {
  821             $parts = Mail_mimePart::_explodeQuotedString($separator, $value);
  822             $value = '';
  823 
  824             foreach ($parts as $part) {
  825                 $part = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $part);
  826                 $part = trim($part);
  827 
  828                 if (!$part) {
  829                     continue;
  830                 }
  831                 if ($value) {
  832                     $value .= $separator==',' ? $separator.' ' : ' ';
  833                 } else {
  834                     $value = $name . ': ';
  835                 }
  836 
  837                 // let's find phrase (name) and/or addr-spec
  838                 if (preg_match('/^<\S+@\S+>$/', $part)) {
  839                     $value .= $part;
  840                 } else if (preg_match('/^\S+@\S+$/', $part)) {
  841                     // address without brackets and without name
  842                     $value .= $part;
  843                 } else if (preg_match('/<*\S+@\S+>*$/', $part, $matches)) {
  844                     // address with name (handle name)
  845                     $address = $matches[0];
  846                     $word = str_replace($address, '', $part);
  847                     $word = trim($word);
  848                     // check if phrase requires quoting
  849                     if ($word) {
  850                         // non-ASCII: require encoding
  851                         if (preg_match('#([\x80-\xFF]){1}#', $word)) {
  852                             if ($word[0] == '"' && $word[strlen($word)-1] == '"') {
  853                                 // de-quote quoted-string, encoding changes
  854                                 // string to atom
  855                                 $search = array("\\\"", "\\\\");
  856                                 $replace = array("\"", "\\");
  857                                 $word = str_replace($search, $replace, $word);
  858                                 $word = substr($word, 1, -1);
  859                             }
  860                             // find length of last line
  861                             if (($pos = strrpos($value, $eol)) !== false) {
  862                                 $last_len = strlen($value) - $pos;
  863                             } else {
  864                                 $last_len = strlen($value);
  865                             }
  866                             $word = Mail_mimePart::encodeHeaderValue(
  867                                 $word, $charset, $encoding, $last_len, $eol
  868                             );
  869                         } else if (($word[0] != '"' || $word[strlen($word)-1] != '"')
  870                             && preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $word)
  871                         ) {
  872                             // ASCII: quote string if needed
  873                             $word = '"'.addcslashes($word, '\\"').'"';
  874                         }
  875                     }
  876                     $value .= $word.' '.$address;
  877                 } else {
  878                     // addr-spec not found, don't encode (?)
  879                     $value .= $part;
  880                 }
  881 
  882                 // RFC2822 recommends 78 characters limit, use 76 from RFC2047
  883                 $value = wordwrap($value, 76, $eol . ' ');
  884             }
  885 
  886             // remove header name prefix (there could be EOL too)
  887             $value = preg_replace(
  888                 '/^'.$name.':('.preg_quote($eol, '/').')* /', '', $value
  889             );
  890 
  891         } else {
  892             // Unstructured header
  893             // non-ASCII: require encoding
  894             if (preg_match('#([\x80-\xFF]){1}#', $value)) {
  895                 if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
  896                     // de-quote quoted-string, encoding changes
  897                     // string to atom
  898                     $search = array("\\\"", "\\\\");
  899                     $replace = array("\"", "\\");
  900                     $value = str_replace($search, $replace, $value);
  901                     $value = substr($value, 1, -1);
  902                 }
  903                 $value = Mail_mimePart::encodeHeaderValue(
  904                     $value, $charset, $encoding, strlen($name) + 2, $eol
  905                 );
  906             } else if (strlen($name.': '.$value) > 78) {
  907                 // ASCII: check if header line isn't too long and use folding
  908                 $value = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $value);
  909                 $tmp = wordwrap($name.': '.$value, 78, $eol . ' ');
  910                 $value = preg_replace('/^'.$name.':\s*/', '', $tmp);
  911                 // hard limit 998 (RFC2822)
  912                 $value = wordwrap($value, 998, $eol . ' ', true);
  913             }
  914         }
  915 
  916         return $value;
  917     }
  918 
  919     /**
  920      * Explode quoted string
  921      *
  922      * @param string $delimiter Delimiter expression string for preg_match()
  923      * @param string $string    Input string
  924      *
  925      * @return array            String tokens array
  926      * @access private
  927      */
  928     static function _explodeQuotedString($delimiter, $string)
  929     {
  930         $result = array();
  931         $strlen = strlen($string);
  932 
  933         for ($q=$p=$i=0; $i < $strlen; $i++) {
  934             if ($string[$i] == "\""
  935                 && (empty($string[$i-1]) || $string[$i-1] != "\\")
  936             ) {
  937                 $q = $q ? false : true;
  938             } else if (!$q && preg_match("/$delimiter/", $string[$i])) {
  939                 $result[] = substr($string, $p, $i - $p);
  940                 $p = $i + 1;
  941             }
  942         }
  943 
  944         $result[] = substr($string, $p);
  945         return $result;
  946     }
  947 
  948     /**
  949      * Encodes a header value as per RFC2047
  950      *
  951      * @param string $value      The header data to encode
  952      * @param string $charset    Character set name
  953      * @param string $encoding   Encoding name (base64 or quoted-printable)
  954      * @param int    $prefix_len Prefix length. Default: 0
  955      * @param string $eol        End-of-line sequence. Default: "\r\n"
  956      *
  957      * @return string            Encoded header data
  958      * @access public
  959      * @since 1.6.1
  960      */
  961     static function encodeHeaderValue($value, $charset, $encoding, $prefix_len=0, $eol="\r\n")
  962     {
  963         if ($encoding == 'base64') {
  964             // Base64 encode the entire string
  965             $value = base64_encode($value);
  966 
  967             // Generate the header using the specified params and dynamicly 
  968             // determine the maximum length of such strings.
  969             // 75 is the value specified in the RFC.
  970             $prefix = '=?' . $charset . '?B?';
  971             $suffix = '?=';
  972             $maxLength = 75 - strlen($prefix . $suffix) - 2;
  973             $maxLength1stLine = $maxLength - $prefix_len;
  974 
  975             // We can cut base4 every 4 characters, so the real max
  976             // we can get must be rounded down.
  977             $maxLength = $maxLength - ($maxLength % 4);
  978             $maxLength1stLine = $maxLength1stLine - ($maxLength1stLine % 4);
  979 
  980             $cutpoint = $maxLength1stLine;
  981             $value_out = $value;
  982             $output = '';
  983             while ($value_out) {
  984                 // Split translated string at every $maxLength
  985                 $part = substr($value_out, 0, $cutpoint);
  986                 $value_out = substr($value_out, $cutpoint);
  987                 $cutpoint = $maxLength;
  988                 // RFC 2047 specifies that any split header should
  989                 // be seperated by a CRLF SPACE. 
  990                 if ($output) {
  991                     $output .= $eol . ' ';
  992                 }
  993                 $output .= $prefix . $part . $suffix;
  994             }
  995             $value = $output;
  996         } else {
  997             // quoted-printable encoding has been selected
  998             $value = Mail_mimePart::encodeQP($value);
  999 
 1000             // Generate the header using the specified params and dynamicly 
 1001             // determine the maximum length of such strings.
 1002             // 75 is the value specified in the RFC.
 1003             $prefix = '=?' . $charset . '?Q?';
 1004             $suffix = '?=';
 1005             $maxLength = 75 - strlen($prefix . $suffix) - 3;
 1006             $maxLength1stLine = $maxLength - $prefix_len;
 1007             $maxLength = $maxLength - 1;
 1008 
 1009             // This regexp will break QP-encoded text at every $maxLength
 1010             // but will not break any encoded letters.
 1011             $reg1st = "|(.{0,$maxLength1stLine}[^\=][^\=])|";
 1012             $reg2nd = "|(.{0,$maxLength}[^\=][^\=])|";
 1013 
 1014             $value_out = $value;
 1015             $realMax = $maxLength1stLine + strlen($prefix . $suffix);
 1016             if (strlen($value_out) >= $realMax) {
 1017                 // Begin with the regexp for the first line.
 1018                 $reg = $reg1st;
 1019                 $output = '';
 1020                 while ($value_out) {
 1021                     // Split translated string at every $maxLength
 1022                     // But make sure not to break any translated chars.
 1023                     $found = preg_match($reg, $value_out, $matches);
 1024 
 1025                     // After this first line, we need to use a different
 1026                     // regexp for the first line.
 1027                     $reg = $reg2nd;
 1028 
 1029                     // Save the found part and encapsulate it in the
 1030                     // prefix & suffix. Then remove the part from the
 1031                     // $value_out variable.
 1032                     if ($found) {
 1033                         $part = $matches[0];
 1034                         $len = strlen($matches[0]);
 1035                         $value_out = substr($value_out, $len);
 1036                     } else {
 1037                         $part = $value_out;
 1038                         $value_out = "";
 1039                     }
 1040 
 1041                     // RFC 2047 specifies that any split header should 
 1042                     // be seperated by a CRLF SPACE
 1043                     if ($output) {
 1044                         $output .= $eol . ' ';
 1045                     }
 1046                     $output .= $prefix . $part . $suffix;
 1047                 }
 1048                 $value_out = $output;
 1049             } else {
 1050                 $value_out = $prefix . $value_out . $suffix;
 1051             }
 1052             $value = $value_out;
 1053         }
 1054 
 1055         return $value;
 1056     }
 1057 
 1058     /**
 1059      * Encodes the given string using quoted-printable
 1060      *
 1061      * @param string $str String to encode
 1062      *
 1063      * @return string     Encoded string
 1064      * @access public
 1065      * @since 1.6.0
 1066      */
 1067     static function encodeQP($str)
 1068     {
 1069         // Bug #17226 RFC 2047 restricts some characters
 1070         // if the word is inside a phrase, permit chars are only:
 1071         // ASCII letters, decimal digits, "!", "*", "+", "-", "/", "=", and "_"
 1072 
 1073         // "=",  "_",  "?" must be encoded
 1074         $regexp = '/([\x22-\x29\x2C\x2E\x3A-\x40\x5B-\x60\x7B-\x7E\x80-\xFF])/';
 1075         $str = preg_replace_callback(
 1076             $regexp, array('Mail_mimePart', '_qpReplaceCallback'), $str
 1077         );
 1078 
 1079         return str_replace(' ', '_', $str);
 1080     }
 1081 
 1082     /**
 1083      * Callback function to replace extended characters (\x80-xFF) with their
 1084      * ASCII values (RFC2047: quoted-printable)
 1085      *
 1086      * @param array $matches Preg_replace's matches array
 1087      *
 1088      * @return string        Encoded character string
 1089      * @access private
 1090      */
 1091     static function _qpReplaceCallback($matches)
 1092     {
 1093         return sprintf('=%02X', ord($matches[1]));
 1094     }
 1095 
 1096     /**
 1097      * Callback function to replace extended characters (\x80-xFF) with their
 1098      * ASCII values (RFC2231)
 1099      *
 1100      * @param array $matches Preg_replace's matches array
 1101      *
 1102      * @return string        Encoded character string
 1103      * @access private
 1104      */
 1105     function _encodeReplaceCallback($matches)
 1106     {
 1107         return sprintf('%%%02X', ord($matches[1]));
 1108     }
 1109 
 1110 } // End of class