Mime.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Mime
  17. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id$
  20. */
  21. /**
  22. * Support class for MultiPart Mime Messages
  23. *
  24. * @category Zend
  25. * @package Zend_Mime
  26. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  27. * @license http://framework.zend.com/license/new-bsd New BSD License
  28. */
  29. class Zend_Mime
  30. {
  31. const TYPE_OCTETSTREAM = 'application/octet-stream';
  32. const TYPE_TEXT = 'text/plain';
  33. const TYPE_HTML = 'text/html';
  34. const ENCODING_7BIT = '7bit';
  35. const ENCODING_8BIT = '8bit';
  36. const ENCODING_QUOTEDPRINTABLE = 'quoted-printable';
  37. const ENCODING_BASE64 = 'base64';
  38. const DISPOSITION_ATTACHMENT = 'attachment';
  39. const DISPOSITION_INLINE = 'inline';
  40. const LINELENGTH = 72;
  41. const LINEEND = "\n";
  42. const MULTIPART_ALTERNATIVE = 'multipart/alternative';
  43. const MULTIPART_MIXED = 'multipart/mixed';
  44. const MULTIPART_RELATED = 'multipart/related';
  45. /**
  46. * Boundary
  47. *
  48. * @var null|string
  49. */
  50. protected $_boundary;
  51. /**
  52. * @var int
  53. */
  54. protected static $makeUnique = 0;
  55. /**
  56. * Lookup-Tables for QuotedPrintable
  57. *
  58. * @var array
  59. */
  60. public static $qpKeys = array(
  61. "\x00",
  62. "\x01",
  63. "\x02",
  64. "\x03",
  65. "\x04",
  66. "\x05",
  67. "\x06",
  68. "\x07",
  69. "\x08",
  70. "\x09",
  71. "\x0A",
  72. "\x0B",
  73. "\x0C",
  74. "\x0D",
  75. "\x0E",
  76. "\x0F",
  77. "\x10",
  78. "\x11",
  79. "\x12",
  80. "\x13",
  81. "\x14",
  82. "\x15",
  83. "\x16",
  84. "\x17",
  85. "\x18",
  86. "\x19",
  87. "\x1A",
  88. "\x1B",
  89. "\x1C",
  90. "\x1D",
  91. "\x1E",
  92. "\x1F",
  93. "\x7F",
  94. "\x80",
  95. "\x81",
  96. "\x82",
  97. "\x83",
  98. "\x84",
  99. "\x85",
  100. "\x86",
  101. "\x87",
  102. "\x88",
  103. "\x89",
  104. "\x8A",
  105. "\x8B",
  106. "\x8C",
  107. "\x8D",
  108. "\x8E",
  109. "\x8F",
  110. "\x90",
  111. "\x91",
  112. "\x92",
  113. "\x93",
  114. "\x94",
  115. "\x95",
  116. "\x96",
  117. "\x97",
  118. "\x98",
  119. "\x99",
  120. "\x9A",
  121. "\x9B",
  122. "\x9C",
  123. "\x9D",
  124. "\x9E",
  125. "\x9F",
  126. "\xA0",
  127. "\xA1",
  128. "\xA2",
  129. "\xA3",
  130. "\xA4",
  131. "\xA5",
  132. "\xA6",
  133. "\xA7",
  134. "\xA8",
  135. "\xA9",
  136. "\xAA",
  137. "\xAB",
  138. "\xAC",
  139. "\xAD",
  140. "\xAE",
  141. "\xAF",
  142. "\xB0",
  143. "\xB1",
  144. "\xB2",
  145. "\xB3",
  146. "\xB4",
  147. "\xB5",
  148. "\xB6",
  149. "\xB7",
  150. "\xB8",
  151. "\xB9",
  152. "\xBA",
  153. "\xBB",
  154. "\xBC",
  155. "\xBD",
  156. "\xBE",
  157. "\xBF",
  158. "\xC0",
  159. "\xC1",
  160. "\xC2",
  161. "\xC3",
  162. "\xC4",
  163. "\xC5",
  164. "\xC6",
  165. "\xC7",
  166. "\xC8",
  167. "\xC9",
  168. "\xCA",
  169. "\xCB",
  170. "\xCC",
  171. "\xCD",
  172. "\xCE",
  173. "\xCF",
  174. "\xD0",
  175. "\xD1",
  176. "\xD2",
  177. "\xD3",
  178. "\xD4",
  179. "\xD5",
  180. "\xD6",
  181. "\xD7",
  182. "\xD8",
  183. "\xD9",
  184. "\xDA",
  185. "\xDB",
  186. "\xDC",
  187. "\xDD",
  188. "\xDE",
  189. "\xDF",
  190. "\xE0",
  191. "\xE1",
  192. "\xE2",
  193. "\xE3",
  194. "\xE4",
  195. "\xE5",
  196. "\xE6",
  197. "\xE7",
  198. "\xE8",
  199. "\xE9",
  200. "\xEA",
  201. "\xEB",
  202. "\xEC",
  203. "\xED",
  204. "\xEE",
  205. "\xEF",
  206. "\xF0",
  207. "\xF1",
  208. "\xF2",
  209. "\xF3",
  210. "\xF4",
  211. "\xF5",
  212. "\xF6",
  213. "\xF7",
  214. "\xF8",
  215. "\xF9",
  216. "\xFA",
  217. "\xFB",
  218. "\xFC",
  219. "\xFD",
  220. "\xFE",
  221. "\xFF"
  222. );
  223. /**
  224. * @var array
  225. */
  226. public static $qpReplaceValues = array(
  227. "=00",
  228. "=01",
  229. "=02",
  230. "=03",
  231. "=04",
  232. "=05",
  233. "=06",
  234. "=07",
  235. "=08",
  236. "=09",
  237. "=0A",
  238. "=0B",
  239. "=0C",
  240. "=0D",
  241. "=0E",
  242. "=0F",
  243. "=10",
  244. "=11",
  245. "=12",
  246. "=13",
  247. "=14",
  248. "=15",
  249. "=16",
  250. "=17",
  251. "=18",
  252. "=19",
  253. "=1A",
  254. "=1B",
  255. "=1C",
  256. "=1D",
  257. "=1E",
  258. "=1F",
  259. "=7F",
  260. "=80",
  261. "=81",
  262. "=82",
  263. "=83",
  264. "=84",
  265. "=85",
  266. "=86",
  267. "=87",
  268. "=88",
  269. "=89",
  270. "=8A",
  271. "=8B",
  272. "=8C",
  273. "=8D",
  274. "=8E",
  275. "=8F",
  276. "=90",
  277. "=91",
  278. "=92",
  279. "=93",
  280. "=94",
  281. "=95",
  282. "=96",
  283. "=97",
  284. "=98",
  285. "=99",
  286. "=9A",
  287. "=9B",
  288. "=9C",
  289. "=9D",
  290. "=9E",
  291. "=9F",
  292. "=A0",
  293. "=A1",
  294. "=A2",
  295. "=A3",
  296. "=A4",
  297. "=A5",
  298. "=A6",
  299. "=A7",
  300. "=A8",
  301. "=A9",
  302. "=AA",
  303. "=AB",
  304. "=AC",
  305. "=AD",
  306. "=AE",
  307. "=AF",
  308. "=B0",
  309. "=B1",
  310. "=B2",
  311. "=B3",
  312. "=B4",
  313. "=B5",
  314. "=B6",
  315. "=B7",
  316. "=B8",
  317. "=B9",
  318. "=BA",
  319. "=BB",
  320. "=BC",
  321. "=BD",
  322. "=BE",
  323. "=BF",
  324. "=C0",
  325. "=C1",
  326. "=C2",
  327. "=C3",
  328. "=C4",
  329. "=C5",
  330. "=C6",
  331. "=C7",
  332. "=C8",
  333. "=C9",
  334. "=CA",
  335. "=CB",
  336. "=CC",
  337. "=CD",
  338. "=CE",
  339. "=CF",
  340. "=D0",
  341. "=D1",
  342. "=D2",
  343. "=D3",
  344. "=D4",
  345. "=D5",
  346. "=D6",
  347. "=D7",
  348. "=D8",
  349. "=D9",
  350. "=DA",
  351. "=DB",
  352. "=DC",
  353. "=DD",
  354. "=DE",
  355. "=DF",
  356. "=E0",
  357. "=E1",
  358. "=E2",
  359. "=E3",
  360. "=E4",
  361. "=E5",
  362. "=E6",
  363. "=E7",
  364. "=E8",
  365. "=E9",
  366. "=EA",
  367. "=EB",
  368. "=EC",
  369. "=ED",
  370. "=EE",
  371. "=EF",
  372. "=F0",
  373. "=F1",
  374. "=F2",
  375. "=F3",
  376. "=F4",
  377. "=F5",
  378. "=F6",
  379. "=F7",
  380. "=F8",
  381. "=F9",
  382. "=FA",
  383. "=FB",
  384. "=FC",
  385. "=FD",
  386. "=FE",
  387. "=FF"
  388. );
  389. /**
  390. * @var string
  391. */
  392. public static $qpKeysString =
  393. "\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";
  394. /**
  395. * Check if the given string is "printable"
  396. *
  397. * Checks that a string contains no unprintable characters. If this returns
  398. * false, encode the string for secure delivery.
  399. *
  400. * @param string $str
  401. * @return boolean
  402. */
  403. public static function isPrintable($str)
  404. {
  405. return (strcspn($str, self::$qpKeysString) == strlen($str));
  406. }
  407. /**
  408. * Encode a given string with the QUOTED_PRINTABLE mechanism and wrap the lines.
  409. *
  410. * @param string $str
  411. * @param int $lineLength Line length; defaults to {@link LINELENGTH}
  412. * @param string $lineEnd Line end; defaults to {@link LINEEND}
  413. * @return string
  414. */
  415. public static function encodeQuotedPrintable(
  416. $str,
  417. $lineLength = self::LINELENGTH,
  418. $lineEnd = self::LINEEND
  419. )
  420. {
  421. $out = '';
  422. $str = self::_encodeQuotedPrintable($str);
  423. // Split encoded text into separate lines
  424. while (strlen($str) > 0) {
  425. $ptr = strlen($str);
  426. if ($ptr > $lineLength) {
  427. $ptr = $lineLength;
  428. }
  429. // Ensure we are not splitting across an encoded character
  430. $pos = strrpos(substr($str, 0, $ptr), '=');
  431. if ($pos !== false && $pos >= $ptr - 2) {
  432. $ptr = $pos;
  433. }
  434. // Check if there is a space at the end of the line and rewind
  435. if ($ptr > 0 && $str[$ptr - 1] == ' ') {
  436. --$ptr;
  437. }
  438. // Add string and continue
  439. $out .= substr($str, 0, $ptr) . '=' . $lineEnd;
  440. $str = substr($str, $ptr);
  441. }
  442. $out = rtrim($out, $lineEnd);
  443. $out = rtrim($out, '=');
  444. return $out;
  445. }
  446. /**
  447. * Converts a string into quoted printable format.
  448. *
  449. * @param string $str
  450. * @return string
  451. */
  452. private static function _encodeQuotedPrintable($str)
  453. {
  454. $str = str_replace('=', '=3D', $str);
  455. $str = str_replace(self::$qpKeys, self::$qpReplaceValues, $str);
  456. $str = rtrim($str);
  457. return $str;
  458. }
  459. /**
  460. * Encode a given string with the QUOTED_PRINTABLE mechanism for Mail Headers.
  461. *
  462. * Mail headers depend on an extended quoted printable algorithm otherwise
  463. * a range of bugs can occur.
  464. *
  465. * @param string $str
  466. * @param string $charset
  467. * @param int $lineLength Line length; defaults to {@link LINELENGTH}
  468. * @param string $lineEnd Line end; defaults to {@link LINEEND}
  469. * @return string
  470. */
  471. public static function encodeQuotedPrintableHeader(
  472. $str, $charset, $lineLength = self::LINELENGTH, $lineEnd = self::LINEEND
  473. )
  474. {
  475. // Reduce line-length by the length of the required delimiter, charsets and encoding
  476. $prefix = sprintf('=?%s?Q?', $charset);
  477. $lineLength = $lineLength - strlen($prefix) - 3;
  478. $str = self::_encodeQuotedPrintable($str);
  479. // Mail-Header required chars have to be encoded also:
  480. $str = str_replace(
  481. array('?', ' ', '_', ','), array('=3F', '=20', '=5F', '=2C'), $str
  482. );
  483. // initialize first line, we need it anyways
  484. $lines = array(0 => "");
  485. // Split encoded text into separate lines
  486. $tmp = "";
  487. while (strlen($str) > 0) {
  488. $currentLine = max(count($lines) - 1, 0);
  489. $token = self::getNextQuotedPrintableToken($str);
  490. $str = substr($str, strlen($token));
  491. $tmp .= $token;
  492. if ($token == '=20') {
  493. // only if we have a single char token or space, we can append the
  494. // tempstring it to the current line or start a new line if necessary.
  495. if (strlen($lines[$currentLine] . $tmp) > $lineLength) {
  496. $lines[$currentLine + 1] = $tmp;
  497. } else {
  498. $lines[$currentLine] .= $tmp;
  499. }
  500. $tmp = "";
  501. }
  502. // don't forget to append the rest to the last line
  503. if (strlen($str) == 0) {
  504. $lines[$currentLine] .= $tmp;
  505. }
  506. }
  507. // assemble the lines together by pre- and appending delimiters, charset, encoding.
  508. for ($i = 0; $i < count($lines); $i++) {
  509. $lines[$i] = " " . $prefix . $lines[$i] . "?=";
  510. }
  511. $str = trim(implode($lineEnd, $lines));
  512. return $str;
  513. }
  514. /**
  515. * Retrieves the first token from a quoted printable string.
  516. *
  517. * @param string $str
  518. * @return string
  519. */
  520. private static function getNextQuotedPrintableToken($str)
  521. {
  522. if (substr($str, 0, 1) == "=") {
  523. $token = substr($str, 0, 3);
  524. } else {
  525. $token = substr($str, 0, 1);
  526. }
  527. return $token;
  528. }
  529. /**
  530. * Encode a given string in mail header compatible base64 encoding.
  531. *
  532. * @param string $str
  533. * @param string $charset
  534. * @param int $lineLength Line length; defaults to {@link LINELENGTH}
  535. * @param string $lineEnd Line end; defaults to {@link LINEEND}
  536. * @return string
  537. */
  538. public static function encodeBase64Header(
  539. $str, $charset, $lineLength = self::LINELENGTH, $lineEnd = self::LINEEND
  540. )
  541. {
  542. $prefix = '=?' . $charset . '?B?';
  543. $suffix = '?=';
  544. $remainingLength = $lineLength - strlen($prefix) - strlen($suffix);
  545. $encodedValue = self::encodeBase64($str, $remainingLength, $lineEnd);
  546. $encodedValue = str_replace(
  547. $lineEnd, $suffix . $lineEnd . ' ' . $prefix, $encodedValue
  548. );
  549. $encodedValue = $prefix . $encodedValue . $suffix;
  550. return $encodedValue;
  551. }
  552. /**
  553. * Encode a given string in base64 encoding and break lines
  554. * according to the maximum linelength.
  555. *
  556. * @param string $str
  557. * @param int $lineLength Line length; defaults to {@link LINELENGTH}
  558. * @param string $lineEnd Line end; defaults to {@link LINEEND}
  559. * @return string
  560. */
  561. public static function encodeBase64(
  562. $str, $lineLength = self::LINELENGTH, $lineEnd = self::LINEEND
  563. )
  564. {
  565. return rtrim(chunk_split(base64_encode($str), $lineLength, $lineEnd));
  566. }
  567. /**
  568. * Constructor
  569. *
  570. * @param null|string $boundary
  571. */
  572. public function __construct($boundary = null)
  573. {
  574. // This string needs to be somewhat unique
  575. if ($boundary === null) {
  576. $this->_boundary = '=_' . md5(microtime(1) . self::$makeUnique++);
  577. } else {
  578. $this->_boundary = $boundary;
  579. }
  580. }
  581. /**
  582. * Encode the given string with the given encoding.
  583. *
  584. * @param string $str
  585. * @param string $encoding
  586. * @param string $EOL Line end; defaults to {@link Zend_Mime::LINEEND}
  587. * @return string
  588. */
  589. public static function encode($str, $encoding, $EOL = self::LINEEND)
  590. {
  591. switch ($encoding) {
  592. case self::ENCODING_BASE64:
  593. return self::encodeBase64($str, self::LINELENGTH, $EOL);
  594. case self::ENCODING_QUOTEDPRINTABLE:
  595. return self::encodeQuotedPrintable($str, self::LINELENGTH, $EOL);
  596. default:
  597. /**
  598. * @todo 7Bit and 8Bit is currently handled the same way.
  599. */
  600. return $str;
  601. }
  602. }
  603. /**
  604. * Return a MIME boundary
  605. *
  606. * @access public
  607. * @return string
  608. */
  609. public function boundary()
  610. {
  611. return $this->_boundary;
  612. }
  613. /**
  614. * Return a MIME boundary line
  615. *
  616. * @param string $EOL Line end; defaults to {@link LINEEND}
  617. * @return string
  618. */
  619. public function boundaryLine($EOL = self::LINEEND)
  620. {
  621. return $EOL . '--' . $this->_boundary . $EOL;
  622. }
  623. /**
  624. * Return MIME ending
  625. *
  626. * @param string $EOL Line end; defaults to {@link LINEEND}
  627. * @return string
  628. */
  629. public function mimeEnd($EOL = self::LINEEND)
  630. {
  631. return $EOL . '--' . $this->_boundary . '--' . $EOL;
  632. }
  633. }