PageRenderTime 27ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/zendframework/zend-http/src/Headers.php

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