PageRenderTime 58ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/library/Zend/Mime/Decode.php

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