Decode.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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. * @see Zend_Mime
  23. */
  24. #require_once 'Zend/Mime.php';
  25. /**
  26. * @category Zend
  27. * @package Zend_Mime
  28. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  29. * @license http://framework.zend.com/license/new-bsd New BSD License
  30. */
  31. class Zend_Mime_Decode
  32. {
  33. /**
  34. * Explode MIME multipart string into seperate parts
  35. *
  36. * Parts consist of the header and the body of each MIME part.
  37. *
  38. * @param string $body raw body of message
  39. * @param string $boundary boundary as found in content-type
  40. * @return array parts with content of each part, empty if no parts found
  41. * @throws Zend_Exception
  42. */
  43. public static function splitMime($body, $boundary)
  44. {
  45. // TODO: we're ignoring \r for now - is this function fast enough and is it safe to asume noone needs \r?
  46. $body = str_replace("\r", '', $body);
  47. $start = 0;
  48. $res = array();
  49. // find every mime part limiter and cut out the
  50. // string before it.
  51. // the part before the first boundary string is discarded:
  52. $p = strpos($body, '--' . $boundary . "\n", $start);
  53. if ($p === false) {
  54. // no parts found!
  55. return array();
  56. }
  57. // position after first boundary line
  58. $start = $p + 3 + strlen($boundary);
  59. while (($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) {
  60. $res[] = substr($body, $start, $p-$start);
  61. $start = $p + 3 + strlen($boundary);
  62. }
  63. // no more parts, find end boundary
  64. $p = strpos($body, '--' . $boundary . '--', $start);
  65. if ($p === false) {
  66. throw new Zend_Exception('Not a valid Mime Message: End Missing');
  67. }
  68. // the remaining part also needs to be parsed:
  69. $res[] = substr($body, $start, $p - $start);
  70. return $res;
  71. }
  72. /**
  73. * decodes a mime encoded String and returns a
  74. * struct of parts with header and body
  75. *
  76. * @param string $message raw message content
  77. * @param string $boundary boundary as found in content-type
  78. * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND}
  79. * @return array|null parts as array('header' => array(name => value), 'body' => content), null if no parts found
  80. * @throws Zend_Exception
  81. */
  82. public static function splitMessageStruct(
  83. $message, $boundary, $EOL = Zend_Mime::LINEEND
  84. )
  85. {
  86. $parts = self::splitMime($message, $boundary);
  87. if (count($parts) <= 0) {
  88. return null;
  89. }
  90. $result = array();
  91. foreach ($parts as $part) {
  92. self::splitMessage($part, $headers, $body, $EOL);
  93. $result[] = array(
  94. 'header' => $headers,
  95. 'body' => $body
  96. );
  97. }
  98. return $result;
  99. }
  100. /**
  101. * split a message in header and body part, if no header or an
  102. * invalid header is found $headers is empty
  103. *
  104. * The charset of the returned headers depend on your iconv settings.
  105. *
  106. * @param string $message raw message with header and optional content
  107. * @param array $headers output param, array with headers as array(name => value)
  108. * @param string $body output param, content of message
  109. * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND}
  110. * @return null
  111. */
  112. public static function splitMessage(
  113. $message, &$headers, &$body, $EOL = Zend_Mime::LINEEND
  114. )
  115. {
  116. // check for valid header at first line
  117. $firstline = strtok($message, "\n");
  118. if (!preg_match('%^[^\s]+[^:]*:%', $firstline)) {
  119. $headers = array();
  120. // TODO: we're ignoring \r for now - is this function fast enough and is it safe to asume noone needs \r?
  121. $body = str_replace(
  122. array(
  123. "\r",
  124. "\n"
  125. ), array(
  126. '',
  127. $EOL
  128. ), $message
  129. );
  130. return;
  131. }
  132. // find an empty line between headers and body
  133. // default is set new line
  134. if (strpos($message, $EOL . $EOL)) {
  135. list($headers, $body) = explode($EOL . $EOL, $message, 2);
  136. // next is the standard new line
  137. } else {
  138. if ($EOL != "\r\n" && strpos($message, "\r\n\r\n")) {
  139. list($headers, $body) = explode("\r\n\r\n", $message, 2);
  140. // next is the other "standard" new line
  141. } else {
  142. if ($EOL != "\n" && strpos($message, "\n\n")) {
  143. list($headers, $body) = explode("\n\n", $message, 2);
  144. // at last resort find anything that looks like a new line
  145. } else {
  146. @list($headers, $body) =
  147. @preg_split("%([\r\n]+)\\1%U", $message, 2);
  148. }
  149. }
  150. }
  151. $headers = iconv_mime_decode_headers(
  152. $headers, ICONV_MIME_DECODE_CONTINUE_ON_ERROR
  153. );
  154. if ($headers === false) {
  155. // an error occurs during the decoding
  156. return;
  157. }
  158. // normalize header names
  159. foreach ($headers as $name => $header) {
  160. $lower = strtolower($name);
  161. if ($lower == $name) {
  162. continue;
  163. }
  164. unset($headers[$name]);
  165. if (!isset($headers[$lower])) {
  166. $headers[$lower] = $header;
  167. continue;
  168. }
  169. if (is_array($headers[$lower])) {
  170. $headers[$lower][] = $header;
  171. continue;
  172. }
  173. $headers[$lower] = array(
  174. $headers[$lower],
  175. $header
  176. );
  177. }
  178. }
  179. /**
  180. * split a content type in its different parts
  181. *
  182. * @param string $type content-type
  183. * @param string $wantedPart the wanted part, else an array with all parts is returned
  184. * @return string|array wanted part or all parts as array('type' => content-type, partname => value)
  185. */
  186. public static function splitContentType($type, $wantedPart = null)
  187. {
  188. return self::splitHeaderField($type, $wantedPart, 'type');
  189. }
  190. /**
  191. * split a header field like content type in its different parts
  192. *
  193. * @param string $field
  194. * @param string $wantedPart the wanted part, else an array with all parts is returned
  195. * @param int|string $firstName key name for the first part
  196. * @throws Zend_Exception
  197. * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
  198. */
  199. public static function splitHeaderField(
  200. $field, $wantedPart = null, $firstName = 0
  201. )
  202. {
  203. $wantedPart = strtolower($wantedPart);
  204. $firstName = strtolower($firstName);
  205. // special case - a bit optimized
  206. if ($firstName === $wantedPart) {
  207. $field = strtok($field, ';');
  208. return $field[0] == '"' ? substr($field, 1, -1) : $field;
  209. }
  210. $field = $firstName . '=' . $field;
  211. if (!preg_match_all('%([^=\s]+)\s*=\s*("[^"]+"|[^;]+)(;\s*|$)%', $field, $matches)) {
  212. throw new Zend_Exception('not a valid header field');
  213. }
  214. if ($wantedPart) {
  215. foreach ($matches[1] as $key => $name) {
  216. if (strcasecmp($name, $wantedPart)) {
  217. continue;
  218. }
  219. if ($matches[2][$key][0] != '"') {
  220. return $matches[2][$key];
  221. }
  222. return substr($matches[2][$key], 1, -1);
  223. }
  224. return null;
  225. }
  226. $split = array();
  227. foreach ($matches[1] as $key => $name) {
  228. $name = strtolower($name);
  229. if ($matches[2][$key][0] == '"') {
  230. $split[$name] = substr($matches[2][$key], 1, -1);
  231. } else {
  232. $split[$name] = $matches[2][$key];
  233. }
  234. }
  235. return $split;
  236. }
  237. /**
  238. * decode a quoted printable encoded string
  239. *
  240. * The charset of the returned string depends on your iconv settings.
  241. *
  242. * @param string $string Encoded string
  243. * @return string Decoded string
  244. */
  245. public static function decodeQuotedPrintable($string)
  246. {
  247. return quoted_printable_decode($string);
  248. }
  249. }