HeaderBag.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. <?php
  2. namespace Symfony\Component\HttpFoundation;
  3. /*
  4. * This file is part of the Symfony package.
  5. *
  6. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. /**
  12. * HeaderBag is a container for HTTP headers.
  13. *
  14. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  15. */
  16. class HeaderBag
  17. {
  18. protected $headers;
  19. protected $cacheControl;
  20. /**
  21. * Constructor.
  22. *
  23. * @param array $headers An array of HTTP headers
  24. */
  25. public function __construct(array $headers = array())
  26. {
  27. $this->cacheControl = array();
  28. $this->replace($headers);
  29. }
  30. /**
  31. * Returns the headers.
  32. *
  33. * @return array An array of headers
  34. */
  35. public function all()
  36. {
  37. return $this->headers;
  38. }
  39. /**
  40. * Returns the parameter keys.
  41. *
  42. * @return array An array of parameter keys
  43. */
  44. public function keys()
  45. {
  46. return array_keys($this->headers);
  47. }
  48. /**
  49. * Replaces the current HTTP headers by a new set.
  50. *
  51. * @param array $headers An array of HTTP headers
  52. */
  53. public function replace(array $headers = array())
  54. {
  55. $this->headers = array();
  56. foreach ($headers as $key => $values) {
  57. $this->set($key, $values);
  58. }
  59. }
  60. /**
  61. * Returns a header value by name.
  62. *
  63. * @param string $key The header name
  64. * @param mixed $default The default value
  65. * @param Boolean $first Whether to return the first value or all header values
  66. *
  67. * @return string|array The first header value if $first is true, an array of values otherwise
  68. */
  69. public function get($key, $default = null, $first = true)
  70. {
  71. $key = strtr(strtolower($key), '_', '-');
  72. if (!array_key_exists($key, $this->headers)) {
  73. if (null === $default) {
  74. return $first ? null : array();
  75. } else {
  76. return $first ? $default : array($default);
  77. }
  78. }
  79. if ($first) {
  80. return count($this->headers[$key]) ? $this->headers[$key][0] : $default;
  81. } else {
  82. return $this->headers[$key];
  83. }
  84. }
  85. /**
  86. * Sets a header by name.
  87. *
  88. * @param string $key The key
  89. * @param string|array $values The value or an array of values
  90. * @param Boolean $replace Whether to replace the actual value of not (true by default)
  91. */
  92. public function set($key, $values, $replace = true)
  93. {
  94. $key = strtr(strtolower($key), '_', '-');
  95. if (!is_array($values)) {
  96. $values = array($values);
  97. }
  98. if (true === $replace || !isset($this->headers[$key])) {
  99. $this->headers[$key] = $values;
  100. } else {
  101. $this->headers[$key] = array_merge($this->headers[$key], $values);
  102. }
  103. if ('cache-control' === $key) {
  104. $this->cacheControl = $this->parseCacheControl($values[0]);
  105. }
  106. }
  107. /**
  108. * Returns true if the HTTP header is defined.
  109. *
  110. * @param string $key The HTTP header
  111. *
  112. * @return Boolean true if the parameter exists, false otherwise
  113. */
  114. public function has($key)
  115. {
  116. return array_key_exists(strtr(strtolower($key), '_', '-'), $this->headers);
  117. }
  118. /**
  119. * Returns true if the given HTTP header contains the given value.
  120. *
  121. * @param string $key The HTTP header name
  122. * @param string $value The HTTP value
  123. *
  124. * @return Boolean true if the value is contained in the header, false otherwise
  125. */
  126. public function contains($key, $value)
  127. {
  128. return in_array($value, $this->get($key, null, false));
  129. }
  130. /**
  131. * Deletes a header.
  132. *
  133. * @param string $key The HTTP header name
  134. */
  135. public function delete($key)
  136. {
  137. $key = strtr(strtolower($key), '_', '-');
  138. unset($this->headers[$key]);
  139. if ('cache-control' === $key) {
  140. $this->cacheControl = array();
  141. }
  142. }
  143. /**
  144. * Sets a cookie.
  145. *
  146. * @param string $name The cookie name
  147. * @param string $value The value of the cookie
  148. * @param string $domain The domain that the cookie is available
  149. * @param string $expire The time the cookie expires
  150. * @param string $path The path on the server in which the cookie will be available on
  151. * @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client
  152. * @param bool $httponly When TRUE the cookie will not be made accessible to JavaScript, preventing XSS attacks from stealing cookies
  153. *
  154. * @throws \InvalidArgumentException When the cookie expire parameter is not valid
  155. */
  156. public function setCookie($name, $value, $domain = null, $expires = null, $path = '/', $secure = false, $httponly = true)
  157. {
  158. $this->validateCookie($name, $value);
  159. return $this->set('Cookie', sprintf('%s=%s', $name, urlencode($value)));
  160. }
  161. /**
  162. * Returns the HTTP header value converted to a date.
  163. *
  164. * @param string $key The parameter key
  165. * @param \DateTime $default The default value
  166. *
  167. * @return \DateTime The filtered value
  168. */
  169. public function getDate($key, \DateTime $default = null)
  170. {
  171. if (null === $value = $this->get($key)) {
  172. return $default;
  173. }
  174. if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) {
  175. throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value));
  176. }
  177. return $date;
  178. }
  179. public function addCacheControlDirective($key, $value = true)
  180. {
  181. $this->cacheControl[$key] = $value;
  182. $this->set('Cache-Control', $this->getCacheControlHeader());
  183. }
  184. public function hasCacheControlDirective($key)
  185. {
  186. return array_key_exists($key, $this->cacheControl);
  187. }
  188. public function getCacheControlDirective($key)
  189. {
  190. return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null;
  191. }
  192. public function removeCacheControlDirective($key)
  193. {
  194. unset($this->cacheControl[$key]);
  195. $this->set('Cache-Control', $this->getCacheControlHeader());
  196. }
  197. protected function getCacheControlHeader()
  198. {
  199. $parts = array();
  200. ksort($this->cacheControl);
  201. foreach ($this->cacheControl as $key => $value) {
  202. if (true === $value) {
  203. $parts[] = $key;
  204. } else {
  205. if (preg_match('#[^a-zA-Z0-9._-]#', $value)) {
  206. $value = '"'.$value.'"';
  207. }
  208. $parts[] = "$key=$value";
  209. }
  210. }
  211. return implode(', ', $parts);
  212. }
  213. /**
  214. * Parses a Cache-Control HTTP header.
  215. *
  216. * @param string $header The value of the Cache-Control HTTP header
  217. *
  218. * @return array An array representing the attribute values
  219. */
  220. protected function parseCacheControl($header)
  221. {
  222. $cacheControl = array();
  223. preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER);
  224. foreach ($matches as $match) {
  225. $cacheControl[strtolower($match[1])] = isset($match[2]) && $match[2] ? $match[2] : (isset($match[3]) ? $match[3] : true);
  226. }
  227. return $cacheControl;
  228. }
  229. protected function validateCookie($name, $value)
  230. {
  231. // from PHP source code
  232. if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
  233. throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
  234. }
  235. if (preg_match("/[,; \t\r\n\013\014]/", $value)) {
  236. throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $name));
  237. }
  238. if (!$name) {
  239. throw new \InvalidArgumentException('The cookie name cannot be empty');
  240. }
  241. }
  242. }