/library/Zf2/Http/Headers.php

https://github.com/GeeH/zf2-sandbox · PHP · 273 lines · 262 code · 3 blank · 8 comment · 0 complexity · 97622c0523cae924d7c64d888d62b070 MD5 · raw file

  1. <?php
  2. namespace Zf2\Http;
  3. use SplQueue;
  4. /**
  5. * Basic HTTP headers collection functionality
  6. *
  7. * Handles aggregation of headers and HTTP protocol version.
  8. */
  9. abstract class Headers extends SplQueue implements HttpHeaders
  10. {
  11. /**@+
  12. * Constants containing patterns for parsing HTTP headers from a string
  13. */
  14. const PATTERN_HEADER_DELIM = "/\r\n/";
  15. const PATTERN_TOKEN = "(?P<token>[^()><@,;:\"\\/\[\]?=}{ \t]+)";
  16. const PATTERN_FIELD_CONTENT = "(?P<content>.*)";
  17. const PATTERN_FIELD_CONTINUATION = '/^\s+(?P<content>.*)$/';
  18. /**@-*/
  19. protected $allowedProtocolVersions = '/^\d+\.\d+$/';
  20. /**
  21. * Set of headers sorted by type
  22. *
  23. * Used to allow fetching headers by type.
  24. *
  25. * @var array Array of SplQueue instances
  26. */
  27. protected $headers = array();
  28. protected $isSent = false;
  29. protected $protocolVersion = '1.1';
  30. /**
  31. * Retrieve HTTP protocol version
  32. *
  33. * @return string|float
  34. */
  35. public function getProtocolVersion()
  36. {
  37. return $this->protocolVersion;
  38. }
  39. /**
  40. * Set HTTP protocol version
  41. *
  42. * @param string|float $version
  43. * @return Headers
  44. */
  45. public function setProtocolVersion($version)
  46. {
  47. if (!is_scalar($version) || !preg_match($this->allowedProtocolVersions, $version)) {
  48. $version = is_scalar($version) ? (string) $version : gettype($version);
  49. throw new Exception\InvalidArgumentException(sprintf(
  50. 'Invalid protocol version: "%s"',
  51. (string) $version
  52. ));
  53. }
  54. $this->protocolVersion = (string) $version;
  55. return $this;
  56. }
  57. /**
  58. * Add a header onto the queue
  59. *
  60. * @param HttpHeader $header
  61. * @return Headers
  62. */
  63. public function addHeader($header, $content = null, $replace = false)
  64. {
  65. if (!$header instanceof HttpHeader) {
  66. if (is_array($header)) {
  67. $header = new Header($header);
  68. } else {
  69. $header = new Header($header, $content, $replace);
  70. }
  71. }
  72. $this->push($header);
  73. return $this;
  74. }
  75. /**
  76. * Add many headers at once
  77. *
  78. * Expects an array (or Traversable object) of type/value pairs.
  79. *
  80. * @param array|Traversable $headers
  81. */
  82. public function addHeaders($headers)
  83. {
  84. if (!is_array($headers) && !$headers instanceof \Traversable) {
  85. throw new Exception\InvalidArgumentException(sprintf(
  86. 'Expected array or Traversable; received "%s"',
  87. (is_object($headers) ? get_class($headers) : gettype($headers))
  88. ));
  89. }
  90. foreach ($headers as $type => $content) {
  91. $this->addHeader($type, $content);
  92. }
  93. return $this;
  94. }
  95. /**
  96. * Clear all headers
  97. *
  98. * Removes all headers from queue
  99. *
  100. * @return void
  101. */
  102. public function clearHeaders()
  103. {
  104. while (count($this)) {
  105. $this->dequeue();
  106. }
  107. $this->headers = array();
  108. }
  109. /**
  110. * Push a header onto the queue
  111. *
  112. * @param Header $value
  113. * @return void
  114. * @throws Exception\InvalidArgumentException when non-Header object provided
  115. */
  116. public function push($value)
  117. {
  118. if (!$value instanceof HttpHeader) {
  119. throw new Exception\InvalidArgumentException(sprintf(
  120. 'Headers may only aggregate Zf2\Http\HttpHeader objects; received %s',
  121. (is_object($value) ? get_class($value) : gettype($value))
  122. ));
  123. }
  124. $type = strtolower($value->getType());
  125. if (!array_key_exists($type, $this->headers)) {
  126. $this->headers[$type] = new SplQueue();
  127. }
  128. $this->headers[$type]->push($value);
  129. return parent::push($value);
  130. }
  131. /**
  132. * Unshift a header onto the queue
  133. *
  134. * @param Header $value
  135. * @return void
  136. * @throws Exception\InvalidArgumentException when non-Header object provided
  137. */
  138. public function unshift($value)
  139. {
  140. if (!$value instanceof HttpHeader) {
  141. throw new Exception\InvalidArgumentException(sprintf(
  142. 'Headers may only aggregate Zf2\Http\HttpHeader objects; received %s',
  143. (is_object($value) ? get_class($value) : gettype($value))
  144. ));
  145. }
  146. $type = strtolower($value->getType());
  147. if (!array_key_exists($type, $this->headers)) {
  148. $this->headers[$type] = new SplQueue();
  149. }
  150. $this->headers[$type]->unshift($value);
  151. return parent::unshift($value);
  152. }
  153. /**
  154. * Get all headers of a certain name/type
  155. *
  156. * @param string $type
  157. * @return false|SplQueue
  158. */
  159. public function get($type)
  160. {
  161. if ($this->has($type)) {
  162. return $this->headers[strtolower($type)];
  163. }
  164. return false;
  165. }
  166. /**
  167. * Test for existence of a type of header
  168. *
  169. * @param string $type
  170. * @return bool
  171. */
  172. public function has($type)
  173. {
  174. return array_key_exists(strtolower($type), $this->headers);
  175. }
  176. /**
  177. * Render all headers at once
  178. *
  179. * This method handles the normal iteration of headers; it is up to the
  180. * concrete classes to prepend with the appropriate status/request line.
  181. *
  182. * @return string
  183. */
  184. public function __toString()
  185. {
  186. $content = '';
  187. foreach ($this as $header) {
  188. $content .= (string) $header;
  189. }
  190. return $content;
  191. }
  192. /**
  193. * Populates headers from string representation
  194. *
  195. * Parses a string for headers, and aggregates them, in order, in the
  196. * current instance.
  197. *
  198. * On Request/Response variants, this should look for the first line
  199. * matching the appropriate regex, and then forward the remainder of the
  200. * string on to parent::fromString().
  201. *
  202. * @param string $string
  203. * @return Headers
  204. */
  205. public function fromString($string)
  206. {
  207. $this->clearHeaders();
  208. $headers = array();
  209. $type = false;
  210. foreach (preg_split(self::PATTERN_HEADER_DELIM, $string) as $line) {
  211. if (preg_match('/^' . self::PATTERN_TOKEN . ':' . self::PATTERN_FIELD_CONTENT . '$/', $line, $matches)) {
  212. $type = $matches['token'];
  213. $content = trim($matches['content']);
  214. if (isset($headers[$type]) && is_string($headers[$type])) {
  215. $headers[$type] = array($headers[$type], $content);
  216. } elseif (isset($headers[$type]) && is_array($headers[$type])) {
  217. $headers[$type][] = $content;
  218. } else {
  219. $headers[$type] = $content;
  220. }
  221. } elseif (preg_match(self::PATTERN_FIELD_CONTINUATION, $line, $matches)) {
  222. if ($type) {
  223. $headers[$type] .= trim($matches['content']);
  224. }
  225. } elseif (preg_match('/^\s*$/', $line)) {
  226. // empty line indicates end of headers
  227. break;
  228. } else {
  229. // Line does not match header format!
  230. throw new Exception\RuntimeException(sprintf(
  231. 'Line "%s"does not match header format!',
  232. $line
  233. ));
  234. }
  235. }
  236. foreach ($headers as $type => $value) {
  237. if (is_array($value)) {
  238. foreach ($value as $v) {
  239. $this->addHeader($type, $v);
  240. }
  241. } else {
  242. $this->addHeader($type, $value);
  243. }
  244. }
  245. return $this;
  246. }
  247. }