File indexing completed on 2024-12-22 05:37:17

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Mime
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 /**
0023  * Support class for MultiPart Mime Messages
0024  *
0025  * @category   Zend
0026  * @package    Zend_Mime
0027  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0028  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0029  */
0030 class Zend_Mime
0031 {
0032     const TYPE_OCTETSTREAM         = 'application/octet-stream';
0033     const TYPE_TEXT                = 'text/plain';
0034     const TYPE_HTML                = 'text/html';
0035     const ENCODING_7BIT            = '7bit';
0036     const ENCODING_8BIT            = '8bit';
0037     const ENCODING_QUOTEDPRINTABLE = 'quoted-printable';
0038     const ENCODING_BASE64          = 'base64';
0039     const DISPOSITION_ATTACHMENT   = 'attachment';
0040     const DISPOSITION_INLINE       = 'inline';
0041     const LINELENGTH               = 72;
0042     const LINEEND                  = "\n";
0043     const MULTIPART_ALTERNATIVE    = 'multipart/alternative';
0044     const MULTIPART_MIXED          = 'multipart/mixed';
0045     const MULTIPART_RELATED        = 'multipart/related';
0046 
0047     /**
0048      * Boundary
0049      *
0050      * @var null|string
0051      */
0052     protected $_boundary;
0053 
0054     /**
0055      * @var int
0056      */
0057     protected static $makeUnique = 0;
0058 
0059     /**
0060      * Lookup-Tables for QuotedPrintable
0061      *
0062      * @var array
0063      */
0064     public static $qpKeys = array(
0065         "\x00",
0066         "\x01",
0067         "\x02",
0068         "\x03",
0069         "\x04",
0070         "\x05",
0071         "\x06",
0072         "\x07",
0073         "\x08",
0074         "\x09",
0075         "\x0A",
0076         "\x0B",
0077         "\x0C",
0078         "\x0D",
0079         "\x0E",
0080         "\x0F",
0081         "\x10",
0082         "\x11",
0083         "\x12",
0084         "\x13",
0085         "\x14",
0086         "\x15",
0087         "\x16",
0088         "\x17",
0089         "\x18",
0090         "\x19",
0091         "\x1A",
0092         "\x1B",
0093         "\x1C",
0094         "\x1D",
0095         "\x1E",
0096         "\x1F",
0097         "\x7F",
0098         "\x80",
0099         "\x81",
0100         "\x82",
0101         "\x83",
0102         "\x84",
0103         "\x85",
0104         "\x86",
0105         "\x87",
0106         "\x88",
0107         "\x89",
0108         "\x8A",
0109         "\x8B",
0110         "\x8C",
0111         "\x8D",
0112         "\x8E",
0113         "\x8F",
0114         "\x90",
0115         "\x91",
0116         "\x92",
0117         "\x93",
0118         "\x94",
0119         "\x95",
0120         "\x96",
0121         "\x97",
0122         "\x98",
0123         "\x99",
0124         "\x9A",
0125         "\x9B",
0126         "\x9C",
0127         "\x9D",
0128         "\x9E",
0129         "\x9F",
0130         "\xA0",
0131         "\xA1",
0132         "\xA2",
0133         "\xA3",
0134         "\xA4",
0135         "\xA5",
0136         "\xA6",
0137         "\xA7",
0138         "\xA8",
0139         "\xA9",
0140         "\xAA",
0141         "\xAB",
0142         "\xAC",
0143         "\xAD",
0144         "\xAE",
0145         "\xAF",
0146         "\xB0",
0147         "\xB1",
0148         "\xB2",
0149         "\xB3",
0150         "\xB4",
0151         "\xB5",
0152         "\xB6",
0153         "\xB7",
0154         "\xB8",
0155         "\xB9",
0156         "\xBA",
0157         "\xBB",
0158         "\xBC",
0159         "\xBD",
0160         "\xBE",
0161         "\xBF",
0162         "\xC0",
0163         "\xC1",
0164         "\xC2",
0165         "\xC3",
0166         "\xC4",
0167         "\xC5",
0168         "\xC6",
0169         "\xC7",
0170         "\xC8",
0171         "\xC9",
0172         "\xCA",
0173         "\xCB",
0174         "\xCC",
0175         "\xCD",
0176         "\xCE",
0177         "\xCF",
0178         "\xD0",
0179         "\xD1",
0180         "\xD2",
0181         "\xD3",
0182         "\xD4",
0183         "\xD5",
0184         "\xD6",
0185         "\xD7",
0186         "\xD8",
0187         "\xD9",
0188         "\xDA",
0189         "\xDB",
0190         "\xDC",
0191         "\xDD",
0192         "\xDE",
0193         "\xDF",
0194         "\xE0",
0195         "\xE1",
0196         "\xE2",
0197         "\xE3",
0198         "\xE4",
0199         "\xE5",
0200         "\xE6",
0201         "\xE7",
0202         "\xE8",
0203         "\xE9",
0204         "\xEA",
0205         "\xEB",
0206         "\xEC",
0207         "\xED",
0208         "\xEE",
0209         "\xEF",
0210         "\xF0",
0211         "\xF1",
0212         "\xF2",
0213         "\xF3",
0214         "\xF4",
0215         "\xF5",
0216         "\xF6",
0217         "\xF7",
0218         "\xF8",
0219         "\xF9",
0220         "\xFA",
0221         "\xFB",
0222         "\xFC",
0223         "\xFD",
0224         "\xFE",
0225         "\xFF"
0226     );
0227 
0228     /**
0229      * @var array
0230      */
0231     public static $qpReplaceValues = array(
0232         "=00",
0233         "=01",
0234         "=02",
0235         "=03",
0236         "=04",
0237         "=05",
0238         "=06",
0239         "=07",
0240         "=08",
0241         "=09",
0242         "=0A",
0243         "=0B",
0244         "=0C",
0245         "=0D",
0246         "=0E",
0247         "=0F",
0248         "=10",
0249         "=11",
0250         "=12",
0251         "=13",
0252         "=14",
0253         "=15",
0254         "=16",
0255         "=17",
0256         "=18",
0257         "=19",
0258         "=1A",
0259         "=1B",
0260         "=1C",
0261         "=1D",
0262         "=1E",
0263         "=1F",
0264         "=7F",
0265         "=80",
0266         "=81",
0267         "=82",
0268         "=83",
0269         "=84",
0270         "=85",
0271         "=86",
0272         "=87",
0273         "=88",
0274         "=89",
0275         "=8A",
0276         "=8B",
0277         "=8C",
0278         "=8D",
0279         "=8E",
0280         "=8F",
0281         "=90",
0282         "=91",
0283         "=92",
0284         "=93",
0285         "=94",
0286         "=95",
0287         "=96",
0288         "=97",
0289         "=98",
0290         "=99",
0291         "=9A",
0292         "=9B",
0293         "=9C",
0294         "=9D",
0295         "=9E",
0296         "=9F",
0297         "=A0",
0298         "=A1",
0299         "=A2",
0300         "=A3",
0301         "=A4",
0302         "=A5",
0303         "=A6",
0304         "=A7",
0305         "=A8",
0306         "=A9",
0307         "=AA",
0308         "=AB",
0309         "=AC",
0310         "=AD",
0311         "=AE",
0312         "=AF",
0313         "=B0",
0314         "=B1",
0315         "=B2",
0316         "=B3",
0317         "=B4",
0318         "=B5",
0319         "=B6",
0320         "=B7",
0321         "=B8",
0322         "=B9",
0323         "=BA",
0324         "=BB",
0325         "=BC",
0326         "=BD",
0327         "=BE",
0328         "=BF",
0329         "=C0",
0330         "=C1",
0331         "=C2",
0332         "=C3",
0333         "=C4",
0334         "=C5",
0335         "=C6",
0336         "=C7",
0337         "=C8",
0338         "=C9",
0339         "=CA",
0340         "=CB",
0341         "=CC",
0342         "=CD",
0343         "=CE",
0344         "=CF",
0345         "=D0",
0346         "=D1",
0347         "=D2",
0348         "=D3",
0349         "=D4",
0350         "=D5",
0351         "=D6",
0352         "=D7",
0353         "=D8",
0354         "=D9",
0355         "=DA",
0356         "=DB",
0357         "=DC",
0358         "=DD",
0359         "=DE",
0360         "=DF",
0361         "=E0",
0362         "=E1",
0363         "=E2",
0364         "=E3",
0365         "=E4",
0366         "=E5",
0367         "=E6",
0368         "=E7",
0369         "=E8",
0370         "=E9",
0371         "=EA",
0372         "=EB",
0373         "=EC",
0374         "=ED",
0375         "=EE",
0376         "=EF",
0377         "=F0",
0378         "=F1",
0379         "=F2",
0380         "=F3",
0381         "=F4",
0382         "=F5",
0383         "=F6",
0384         "=F7",
0385         "=F8",
0386         "=F9",
0387         "=FA",
0388         "=FB",
0389         "=FC",
0390         "=FD",
0391         "=FE",
0392         "=FF"
0393     );
0394 
0395     /**
0396      * @var string
0397      */
0398     public static $qpKeysString =
0399         "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF";
0400 
0401     /**
0402      * Check if the given string is "printable"
0403      *
0404      * Checks that a string contains no unprintable characters. If this returns
0405      * false, encode the string for secure delivery.
0406      *
0407      * @param string $str
0408      * @return boolean
0409      */
0410     public static function isPrintable($str)
0411     {
0412         return (strcspn($str, self::$qpKeysString) == strlen($str));
0413     }
0414 
0415     /**
0416      * Encode a given string with the QUOTED_PRINTABLE mechanism and wrap the lines.
0417      *
0418      * @param  string $str
0419      * @param  int    $lineLength Line length; defaults to {@link LINELENGTH}
0420      * @param  string $lineEnd    Line end; defaults to {@link LINEEND}
0421      * @return string
0422      */
0423     public static function encodeQuotedPrintable(
0424         $str,
0425         $lineLength = self::LINELENGTH,
0426         $lineEnd = self::LINEEND
0427     )
0428     {
0429         $out = '';
0430         $str = self::_encodeQuotedPrintable($str);
0431 
0432         // Split encoded text into separate lines
0433         while (strlen($str) > 0) {
0434             $ptr = strlen($str);
0435             if ($ptr > $lineLength) {
0436                 $ptr = $lineLength;
0437             }
0438 
0439             // Ensure we are not splitting across an encoded character
0440             $pos = strrpos(substr($str, 0, $ptr), '=');
0441             if ($pos !== false && $pos >= $ptr - 2) {
0442                 $ptr = $pos;
0443             }
0444 
0445             // Check if there is a space at the end of the line and rewind
0446             if ($ptr > 0 && $str[$ptr - 1] == ' ') {
0447                 --$ptr;
0448             }
0449 
0450             // Add string and continue
0451             $out .= substr($str, 0, $ptr) . '=' . $lineEnd;
0452             $str = substr($str, $ptr);
0453         }
0454 
0455         $out = rtrim($out, $lineEnd);
0456         $out = rtrim($out, '=');
0457 
0458         return $out;
0459     }
0460 
0461     /**
0462      * Converts a string into quoted printable format.
0463      *
0464      * @param  string $str
0465      * @return string
0466      */
0467     private static function _encodeQuotedPrintable($str)
0468     {
0469         $str = str_replace('=', '=3D', $str);
0470         $str = str_replace(self::$qpKeys, self::$qpReplaceValues, $str);
0471         $str = rtrim($str);
0472 
0473         return $str;
0474     }
0475 
0476     /**
0477      * Encode a given string with the QUOTED_PRINTABLE mechanism for Mail Headers.
0478      *
0479      * Mail headers depend on an extended quoted printable algorithm otherwise
0480      * a range of bugs can occur.
0481      *
0482      * @param  string $str
0483      * @param  string $charset
0484      * @param  int    $lineLength Line length; defaults to {@link LINELENGTH}
0485      * @param  string $lineEnd    Line end; defaults to {@link LINEEND}
0486      * @return string
0487      */
0488     public static function encodeQuotedPrintableHeader(
0489         $str, $charset, $lineLength = self::LINELENGTH, $lineEnd = self::LINEEND
0490     )
0491     {
0492         // Reduce line-length by the length of the required delimiter, charsets and encoding
0493         $prefix     = sprintf('=?%s?Q?', $charset);
0494         $lineLength = $lineLength - strlen($prefix) - 3;
0495 
0496         $str = self::_encodeQuotedPrintable($str);
0497 
0498         // Mail-Header required chars have to be encoded also:
0499         $str = str_replace(
0500             array('?', ' ', '_', ','), array('=3F', '=20', '=5F', '=2C'), $str
0501         );
0502 
0503         // initialize first line, we need it anyways
0504         $lines = array(0 => "");
0505 
0506         // Split encoded text into separate lines
0507         $tmp = "";
0508         while (strlen($str) > 0) {
0509             $currentLine = max(count($lines) - 1, 0);
0510             $token       = self::getNextQuotedPrintableToken($str);
0511             $str         = substr($str, strlen($token));
0512 
0513             $tmp .= $token;
0514             if ($token == '=20') {
0515                 // only if we have a single char token or space, we can append the
0516                 // tempstring it to the current line or start a new line if necessary.
0517                 if (strlen($lines[$currentLine] . $tmp) > $lineLength) {
0518                     $lines[$currentLine + 1] = $tmp;
0519                 } else {
0520                     $lines[$currentLine] .= $tmp;
0521                 }
0522                 $tmp = "";
0523             }
0524             // don't forget to append the rest to the last line
0525             if (strlen($str) == 0) {
0526                 $lines[$currentLine] .= $tmp;
0527             }
0528         }
0529 
0530         // assemble the lines together by pre- and appending delimiters, charset, encoding.
0531         for ($i = 0; $i < count($lines); $i++) {
0532             $lines[$i] = " " . $prefix . $lines[$i] . "?=";
0533         }
0534         $str = trim(implode($lineEnd, $lines));
0535 
0536         return $str;
0537     }
0538 
0539     /**
0540      * Retrieves the first token from a quoted printable string.
0541      *
0542      * @param  string $str
0543      * @return string
0544      */
0545     private static function getNextQuotedPrintableToken($str)
0546     {
0547         if (substr($str, 0, 1) == "=") {
0548             $token = substr($str, 0, 3);
0549         } else {
0550             $token = substr($str, 0, 1);
0551         }
0552 
0553         return $token;
0554     }
0555 
0556     /**
0557      * Encode a given string in mail header compatible base64 encoding.
0558      *
0559      * @param  string $str
0560      * @param  string $charset
0561      * @param  int    $lineLength Line length; defaults to {@link LINELENGTH}
0562      * @param  string $lineEnd    Line end; defaults to {@link LINEEND}
0563      * @return string
0564      */
0565     public static function encodeBase64Header(
0566         $str, $charset, $lineLength = self::LINELENGTH, $lineEnd = self::LINEEND
0567     )
0568     {
0569         $prefix          = '=?' . $charset . '?B?';
0570         $suffix          = '?=';
0571         $remainingLength = $lineLength - strlen($prefix) - strlen($suffix);
0572 
0573         $encodedValue = self::encodeBase64($str, $remainingLength, $lineEnd);
0574         $encodedValue = str_replace(
0575             $lineEnd, $suffix . $lineEnd . ' ' . $prefix, $encodedValue
0576         );
0577         $encodedValue = $prefix . $encodedValue . $suffix;
0578 
0579         return $encodedValue;
0580     }
0581 
0582     /**
0583      * Encode a given string in base64 encoding and break lines
0584      * according to the maximum linelength.
0585      *
0586      * @param  string $str
0587      * @param  int    $lineLength Line length; defaults to {@link LINELENGTH}
0588      * @param  string $lineEnd    Line end; defaults to {@link LINEEND}
0589      * @return string
0590      */
0591     public static function encodeBase64(
0592         $str, $lineLength = self::LINELENGTH, $lineEnd = self::LINEEND
0593     )
0594     {
0595         return rtrim(chunk_split(base64_encode($str), $lineLength, $lineEnd));
0596     }
0597 
0598     /**
0599      * Constructor
0600      *
0601      * @param null|string $boundary
0602      */
0603     public function __construct($boundary = null)
0604     {
0605         // This string needs to be somewhat unique
0606         if ($boundary === null) {
0607             $this->_boundary = '=_' . md5(microtime(1) . self::$makeUnique++);
0608         } else {
0609             $this->_boundary = $boundary;
0610         }
0611     }
0612 
0613     /**
0614      * Encode the given string with the given encoding.
0615      *
0616      * @param string $str
0617      * @param string $encoding
0618      * @param string $EOL Line end; defaults to {@link Zend_Mime::LINEEND}
0619      * @return string
0620      */
0621     public static function encode($str, $encoding, $EOL = self::LINEEND)
0622     {
0623         switch ($encoding) {
0624             case self::ENCODING_BASE64:
0625                 return self::encodeBase64($str, self::LINELENGTH, $EOL);
0626 
0627             case self::ENCODING_QUOTEDPRINTABLE:
0628                 return self::encodeQuotedPrintable($str, self::LINELENGTH, $EOL);
0629 
0630             default:
0631                 /**
0632                  * @todo 7Bit and 8Bit is currently handled the same way.
0633                  */
0634                 return $str;
0635         }
0636     }
0637 
0638     /**
0639      * Return a MIME boundary
0640      *
0641      * @access public
0642      * @return string
0643      */
0644     public function boundary()
0645     {
0646         return $this->_boundary;
0647     }
0648 
0649     /**
0650      * Return a MIME boundary line
0651      *
0652      * @param  string $EOL Line end; defaults to {@link LINEEND}
0653      * @return string
0654      */
0655     public function boundaryLine($EOL = self::LINEEND)
0656     {
0657         return $EOL . '--' . $this->_boundary . $EOL;
0658     }
0659 
0660     /**
0661      * Return MIME ending
0662      *
0663      * @param  string $EOL Line end; defaults to {@link LINEEND}
0664      * @return string
0665      */
0666     public function mimeEnd($EOL = self::LINEEND)
0667     {
0668         return $EOL . '--' . $this->_boundary . '--' . $EOL;
0669     }
0670 }