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

/library/Zend/Mail/Headers.php

https://github.com/Thinkscape/zf2
PHP | 485 lines | 258 code | 45 blank | 182 comment | 31 complexity | acdb0568a1a90c83296f48b073c84dec MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\Mail;
  10. use ArrayIterator;
  11. use Countable;
  12. use Iterator;
  13. use Traversable;
  14. use Zend\Loader\PluginClassLocator;
  15. /**
  16. * Basic mail headers collection functionality
  17. *
  18. * Handles aggregation of headers
  19. */
  20. class Headers implements Countable, Iterator
  21. {
  22. /** @var string End of Line for fields */
  23. const EOL = "\r\n";
  24. /** @var string Start of Line when folding */
  25. const FOLDING = "\r\n ";
  26. /**
  27. * @var \Zend\Loader\PluginClassLoader
  28. */
  29. protected $pluginClassLoader = null;
  30. /**
  31. * @var array key names for $headers array
  32. */
  33. protected $headersKeys = array();
  34. /**
  35. * @var Header\HeaderInterface[] instances
  36. */
  37. protected $headers = array();
  38. /**
  39. * Header encoding; defaults to ASCII
  40. *
  41. * @var string
  42. */
  43. protected $encoding = 'ASCII';
  44. /**
  45. * Populates headers from string representation
  46. *
  47. * Parses a string for headers, and aggregates them, in order, in the
  48. * current instance, primarily as strings until they are needed (they
  49. * will be lazy loaded)
  50. *
  51. * @param string $string
  52. * @param string $EOL EOL string; defaults to {@link EOL}
  53. * @throws Exception\RuntimeException
  54. * @return Headers
  55. */
  56. public static function fromString($string, $EOL = self::EOL)
  57. {
  58. $headers = new static();
  59. $currentLine = '';
  60. // iterate the header lines, some might be continuations
  61. foreach (explode($EOL, $string) as $line) {
  62. // check if a header name is present
  63. if (preg_match('/^(?P<name>[\x21-\x39\x3B-\x7E]+):.*$/', $line, $matches)) {
  64. if ($currentLine) {
  65. // a header name was present, then store the current complete line
  66. $headers->addHeaderLine($currentLine);
  67. }
  68. $currentLine = trim($line);
  69. } elseif (preg_match('/^\s+.*$/', $line, $matches)) {
  70. // continuation: append to current line
  71. $currentLine .= trim($line);
  72. } elseif (preg_match('/^\s*$/', $line)) {
  73. // empty line indicates end of headers
  74. break;
  75. } else {
  76. // Line does not match header format!
  77. throw new Exception\RuntimeException(sprintf(
  78. 'Line "%s"does not match header format!',
  79. $line
  80. ));
  81. }
  82. }
  83. if ($currentLine) {
  84. $headers->addHeaderLine($currentLine);
  85. }
  86. return $headers;
  87. }
  88. /**
  89. * Set an alternate implementation for the PluginClassLoader
  90. *
  91. * @param PluginClassLocator $pluginClassLoader
  92. * @return Headers
  93. */
  94. public function setPluginClassLoader(PluginClassLocator $pluginClassLoader)
  95. {
  96. $this->pluginClassLoader = $pluginClassLoader;
  97. return $this;
  98. }
  99. /**
  100. * Return an instance of a PluginClassLocator, lazyload and inject map if necessary
  101. *
  102. * @return PluginClassLocator
  103. */
  104. public function getPluginClassLoader()
  105. {
  106. if ($this->pluginClassLoader === null) {
  107. $this->pluginClassLoader = new Header\HeaderLoader();
  108. }
  109. return $this->pluginClassLoader;
  110. }
  111. /**
  112. * Set the header encoding
  113. *
  114. * @param string $encoding
  115. * @return Headers
  116. */
  117. public function setEncoding($encoding)
  118. {
  119. $this->encoding = $encoding;
  120. foreach ($this as $header) {
  121. $header->setEncoding($encoding);
  122. }
  123. return $this;
  124. }
  125. /**
  126. * Get the header encoding
  127. *
  128. * @return string
  129. */
  130. public function getEncoding()
  131. {
  132. return $this->encoding;
  133. }
  134. /**
  135. * Add many headers at once
  136. *
  137. * Expects an array (or Traversable object) of type/value pairs.
  138. *
  139. * @param array|Traversable $headers
  140. * @throws Exception\InvalidArgumentException
  141. * @return Headers
  142. */
  143. public function addHeaders($headers)
  144. {
  145. if (!is_array($headers) && !$headers instanceof Traversable) {
  146. throw new Exception\InvalidArgumentException(sprintf(
  147. 'Expected array or Traversable; received "%s"',
  148. (is_object($headers) ? get_class($headers) : gettype($headers))
  149. ));
  150. }
  151. foreach ($headers as $name => $value) {
  152. if (is_int($name)) {
  153. if (is_string($value)) {
  154. $this->addHeaderLine($value);
  155. } elseif (is_array($value) && count($value) == 1) {
  156. $this->addHeaderLine(key($value), current($value));
  157. } elseif (is_array($value) && count($value) == 2) {
  158. $this->addHeaderLine($value[0], $value[1]);
  159. } elseif ($value instanceof Header\HeaderInterface) {
  160. $this->addHeader($value);
  161. }
  162. } elseif (is_string($name)) {
  163. $this->addHeaderLine($name, $value);
  164. }
  165. }
  166. return $this;
  167. }
  168. /**
  169. * Add a raw header line, either in name => value, or as a single string 'name: value'
  170. *
  171. * This method allows for lazy-loading in that the parsing and instantiation of HeaderInterface object
  172. * will be delayed until they are retrieved by either get() or current()
  173. *
  174. * @throws Exception\InvalidArgumentException
  175. * @param string $headerFieldNameOrLine
  176. * @param string $fieldValue optional
  177. * @return Headers
  178. */
  179. public function addHeaderLine($headerFieldNameOrLine, $fieldValue = null)
  180. {
  181. if (!is_string($headerFieldNameOrLine)) {
  182. throw new Exception\InvalidArgumentException(sprintf(
  183. '%s expects its first argument to be a string; received "%s"',
  184. (is_object($headerFieldNameOrLine) ? get_class($headerFieldNameOrLine) : gettype($headerFieldNameOrLine))
  185. ));
  186. }
  187. if ($fieldValue === null) {
  188. $this->addHeader(Header\GenericHeader::fromString($headerFieldNameOrLine));
  189. } elseif (is_array($fieldValue)) {
  190. foreach ($fieldValue as $i) {
  191. $this->addHeader(new Header\GenericMultiHeader($headerFieldNameOrLine, $i));
  192. }
  193. } else {
  194. $this->addHeader(new Header\GenericHeader($headerFieldNameOrLine, $fieldValue));
  195. }
  196. return $this;
  197. }
  198. /**
  199. * Add a Header\Interface to this container, for raw values see {@link addHeaderLine()} and {@link addHeaders()}
  200. *
  201. * @param Header\HeaderInterface $header
  202. * @return Headers
  203. */
  204. public function addHeader(Header\HeaderInterface $header)
  205. {
  206. $key = $this->normalizeFieldName($header->getFieldName());
  207. $this->headersKeys[] = $key;
  208. $this->headers[] = $header;
  209. if ($this->getEncoding() !== 'ASCII') {
  210. $header->setEncoding($this->getEncoding());
  211. }
  212. return $this;
  213. }
  214. /**
  215. * Remove a Header from the container
  216. *
  217. * @param string|Header\HeaderInterface field name or specific header instance to remove
  218. * @return bool
  219. */
  220. public function removeHeader($instanceOrFieldName)
  221. {
  222. if ($instanceOrFieldName instanceof Header\HeaderInterface) {
  223. $indexes = array_keys($this->headers, $instanceOrFieldName, true);
  224. } else {
  225. $key = $this->normalizeFieldName($instanceOrFieldName);
  226. $indexes = array_keys($this->headersKeys, $key, true);
  227. }
  228. if (!empty($indexes)) {
  229. foreach ($indexes as $index) {
  230. unset ($this->headersKeys[$index]);
  231. unset ($this->headers[$index]);
  232. }
  233. return true;
  234. }
  235. return false;
  236. }
  237. /**
  238. * Clear all headers
  239. *
  240. * Removes all headers from queue
  241. *
  242. * @return Headers
  243. */
  244. public function clearHeaders()
  245. {
  246. $this->headers = $this->headersKeys = array();
  247. return $this;
  248. }
  249. /**
  250. * Get all headers of a certain name/type
  251. *
  252. * @param string $name
  253. * @return bool|ArrayIterator|Header\HeaderInterface Returns false if there is no headers with $name in this
  254. * contain, an ArrayIterator if the header is a MultipleHeadersInterface instance and finally returns
  255. * HeaderInterface for the rest of cases.
  256. */
  257. public function get($name)
  258. {
  259. $key = $this->normalizeFieldName($name);
  260. $results = array();
  261. foreach (array_keys($this->headersKeys, $key) as $index) {
  262. if ($this->headers[$index] instanceof Header\GenericHeader) {
  263. $results[] = $this->lazyLoadHeader($index);
  264. } else {
  265. $results[] = $this->headers[$index];
  266. }
  267. }
  268. switch (count($results)) {
  269. case 0:
  270. return false;
  271. case 1:
  272. if ($results[0] instanceof Header\MultipleHeadersInterface) {
  273. return new ArrayIterator($results);
  274. } else {
  275. return $results[0];
  276. }
  277. default:
  278. return new ArrayIterator($results);
  279. }
  280. }
  281. /**
  282. * Test for existence of a type of header
  283. *
  284. * @param string $name
  285. * @return bool
  286. */
  287. public function has($name)
  288. {
  289. $name = $this->normalizeFieldName($name);
  290. return in_array($name, $this->headersKeys);
  291. }
  292. /**
  293. * Advance the pointer for this object as an iterator
  294. *
  295. */
  296. public function next()
  297. {
  298. next($this->headers);
  299. }
  300. /**
  301. * Return the current key for this object as an iterator
  302. *
  303. * @return mixed
  304. */
  305. public function key()
  306. {
  307. return key($this->headers);
  308. }
  309. /**
  310. * Is this iterator still valid?
  311. *
  312. * @return bool
  313. */
  314. public function valid()
  315. {
  316. return (current($this->headers) !== false);
  317. }
  318. /**
  319. * Reset the internal pointer for this object as an iterator
  320. *
  321. */
  322. public function rewind()
  323. {
  324. reset($this->headers);
  325. }
  326. /**
  327. * Return the current value for this iterator, lazy loading it if need be
  328. *
  329. * @return Header\HeaderInterface
  330. */
  331. public function current()
  332. {
  333. $current = current($this->headers);
  334. if ($current instanceof Header\GenericHeader) {
  335. $current = $this->lazyLoadHeader(key($this->headers));
  336. }
  337. return $current;
  338. }
  339. /**
  340. * Return the number of headers in this contain, if all headers have not been parsed, actual count could
  341. * increase if MultipleHeader objects exist in the Request/Response. If you need an exact count, iterate
  342. *
  343. * @return int count of currently known headers
  344. */
  345. public function count()
  346. {
  347. return count($this->headers);
  348. }
  349. /**
  350. * Render all headers at once
  351. *
  352. * This method handles the normal iteration of headers; it is up to the
  353. * concrete classes to prepend with the appropriate status/request line.
  354. *
  355. * @return string
  356. */
  357. public function toString()
  358. {
  359. $headers = '';
  360. foreach ($this as $header) {
  361. if ($str = $header->toString()) {
  362. $headers .= $str . self::EOL;
  363. }
  364. }
  365. return $headers;
  366. }
  367. /**
  368. * Return the headers container as an array
  369. *
  370. * @todo determine how to produce single line headers, if they are supported
  371. * @return array
  372. */
  373. public function toArray()
  374. {
  375. $headers = array();
  376. /* @var $header Header\HeaderInterface */
  377. foreach ($this->headers as $header) {
  378. if ($header instanceof Header\MultipleHeadersInterface) {
  379. $name = $header->getFieldName();
  380. if (!isset($headers[$name])) {
  381. $headers[$name] = array();
  382. }
  383. $headers[$name][] = $header->getFieldValue();
  384. } else {
  385. $headers[$header->getFieldName()] = $header->getFieldValue();
  386. }
  387. }
  388. return $headers;
  389. }
  390. /**
  391. * By calling this, it will force parsing and loading of all headers, after this count() will be accurate
  392. *
  393. * @return bool
  394. */
  395. public function forceLoading()
  396. {
  397. foreach ($this as $item) {
  398. // $item should now be loaded
  399. }
  400. return true;
  401. }
  402. /**
  403. * @param $index
  404. * @return mixed
  405. */
  406. protected function lazyLoadHeader($index)
  407. {
  408. $current = $this->headers[$index];
  409. $key = $this->headersKeys[$index];
  410. /* @var $class Header\HeaderInterface */
  411. $class = ($this->getPluginClassLoader()->load($key)) ?: 'Zend\Mail\Header\GenericHeader';
  412. $encoding = $current->getEncoding();
  413. $headers = $class::fromString($current->toString());
  414. if (is_array($headers)) {
  415. $current = array_shift($headers);
  416. $current->setEncoding($encoding);
  417. $this->headers[$index] = $current;
  418. foreach ($headers as $header) {
  419. $header->setEncoding($encoding);
  420. $this->headersKeys[] = $key;
  421. $this->headers[] = $header;
  422. }
  423. return $current;
  424. }
  425. $current = $headers;
  426. $current->setEncoding($encoding);
  427. $this->headers[$index] = $current;
  428. return $current;
  429. }
  430. /**
  431. * Normalize a field name
  432. *
  433. * @param string $fieldName
  434. * @return string
  435. */
  436. protected function normalizeFieldName($fieldName)
  437. {
  438. return str_replace(array('-', '_', ' ', '.'), '', strtolower($fieldName));
  439. }
  440. }