PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Http/Headers.php

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