PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/Classes/TYPO3/FLOW3/Http/Headers.php

https://github.com/christianjul/FLOW3-Composer
PHP | 435 lines | 230 code | 32 blank | 173 comment | 31 complexity | a4d929fb37e557769df5c554f8d08685 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. namespace TYPO3\FLOW3\Http;
  3. /* *
  4. * This script belongs to the FLOW3 framework. *
  5. * *
  6. * It is free software; you can redistribute it and/or modify it under *
  7. * the terms of the GNU Lesser General Public License, either version 3 *
  8. * of the License, or (at your option) any later version. *
  9. * *
  10. * The TYPO3 project - inspiring people to share! *
  11. * */
  12. use TYPO3\FLOW3\Annotations as FLOW3;
  13. /**
  14. * Container for HTTP header fields
  15. *
  16. * @api
  17. * @FLOW3\Proxy(false)
  18. */
  19. class Headers {
  20. /**
  21. * @var array
  22. */
  23. protected $fields = array();
  24. /**
  25. * @var array
  26. */
  27. protected $cookies = array();
  28. /**
  29. * @var array
  30. */
  31. protected $cacheDirectives = array(
  32. 'visibility' => '',
  33. 'max-age' => '',
  34. 's-maxage' => '',
  35. 'must-revalidate' => '',
  36. 'proxy-revalidate' => '',
  37. 'no-store' => '',
  38. 'no-transform' => ''
  39. );
  40. /**
  41. * Constructs a new Headers object.
  42. *
  43. * @param array $fields Field names and their values (either as single value or array of values)
  44. */
  45. public function __construct(array $fields = array()) {
  46. foreach ($fields as $name => $values) {
  47. $this->set($name, $values);
  48. }
  49. }
  50. /**
  51. * Creates a new Headers instance from the given $_SERVER-superglobal-like array.
  52. *
  53. * @param array $server An array similar or equal to $_SERVER, containing headers in the form of "HTTP_FOO_BAR"
  54. * @return \TYPO3\FLOW3\Http\Headers
  55. */
  56. static public function createFromServer(array $server) {
  57. $headerFields = array();
  58. if (isset($server['PHP_AUTH_USER']) && isset($server['PHP_AUTH_PW'])) {
  59. $headerFields['Authorization'] = 'Basic ' . base64_encode($server['PHP_AUTH_USER'] . ':' . $server['PHP_AUTH_PW']);
  60. }
  61. foreach ($server as $name => $value) {
  62. if (strpos($name, 'HTTP_') === 0) {
  63. $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
  64. $headerFields[$name] = $value;
  65. } elseif ($name == 'REDIRECT_REMOTE_AUTHORIZATION' && !isset($headerFields['Authorization'])) {
  66. $headerFields['Authorization'] = $value;
  67. } elseif (in_array($name, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) {
  68. $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $name))));
  69. $headerFields[$name] = $value;
  70. }
  71. }
  72. return new self($headerFields);
  73. }
  74. /**
  75. * Sets the specified HTTP header
  76. *
  77. * DateTime objects will be converted to a string representation internally but
  78. * will be returned as DateTime objects on calling get().
  79. *
  80. * Please note that dates are normalized to GMT internally, so that get() will return
  81. * the same point in time, but not necessarily in the same timezone, if it was not
  82. * GMT previously. GMT is used synonymously with UTC as per RFC 2616 3.3.1.
  83. *
  84. * @param string $name Name of the header, for example "Location", "Content-Description" etc.
  85. * @param array|string|\DateTime $values An array of values or a single value for the specified header field
  86. * @param boolean $replaceExistingHeader If a header with the same name should be replaced. Default is TRUE.
  87. * @return void
  88. * @throws \InvalidArgumentException
  89. * @api
  90. */
  91. public function set($name, $values, $replaceExistingHeader = TRUE) {
  92. if (strtoupper(substr($name, 0, 4)) === 'HTTP') {
  93. throw new \InvalidArgumentException('The "HTTP" status header must be set via setStatus().', 1220541963);
  94. }
  95. if (strtoupper(substr($name, 0, 10)) === 'SET-COOKIE') {
  96. throw new \InvalidArgumentException('The "Set-Cookie" headers must be set via setCookie().', 1345128153);
  97. }
  98. if ($values instanceof \DateTime) {
  99. $date = clone $values;
  100. $date->setTimezone(new \DateTimeZone('GMT'));
  101. $values = array($date->format('D, d M Y H:i:s') . ' GMT');
  102. } else {
  103. $values = (array) $values;
  104. }
  105. switch ($name) {
  106. case 'Cache-Control':
  107. if (count($values) !== 1) {
  108. throw new \InvalidArgumentException('The "Cache-Control" header must be unique and thus only one field value may be specified.', 1337849415);
  109. }
  110. $this->setCacheControlDirectivesFromRawHeader(array_pop($values));
  111. break;
  112. case 'Cookie':
  113. if (count($values) !== 1) {
  114. throw new \InvalidArgumentException('The "Cookie" header must be unique and thus only one field value may be specified.', 1345127727);
  115. }
  116. $this->setCookiesFromRawHeader(array_pop($values));
  117. break;
  118. default:
  119. if ($replaceExistingHeader === TRUE || !isset($this->fields[$name])) {
  120. $this->fields[$name] = $values;
  121. } else {
  122. $this->fields[$name] = array_merge($this->fields[$name], $values);
  123. }
  124. }
  125. }
  126. /**
  127. * Returns the specified HTTP header
  128. *
  129. * Dates are returned as DateTime objects with the timezone set to GMT.
  130. *
  131. * @param string $name Name of the header, for example "Location", "Content-Description" etc.
  132. * @return array|string An array of field values if multiple headers of that name exist, a string value if only one value exists and NULL if there is no such header.
  133. * @api
  134. */
  135. public function get($name) {
  136. if ($name === 'Cache-Control') {
  137. return $this->getCacheControlHeader();
  138. }
  139. if (!isset($this->fields[$name])) {
  140. return NULL;
  141. }
  142. $convertedValues = array();
  143. foreach ($this->fields[$name] as $index => $value) {
  144. $convertedValues[$index] = \DateTime::createFromFormat(DATE_RFC2822, $value);
  145. if ($convertedValues[$index] === FALSE) {
  146. $convertedValues[$index] = $value;
  147. }
  148. }
  149. return (count($convertedValues) > 1) ? $convertedValues : reset($convertedValues);
  150. }
  151. /**
  152. * Returns all header fields
  153. *
  154. * Note that even for those header fields which exist only one time, the value is
  155. * returned as an array (with a single item).
  156. *
  157. * @return array
  158. * @api
  159. */
  160. public function getAll() {
  161. $fields = $this->fields;
  162. $cacheControlHeader = $this->getCacheControlHeader();
  163. if (!empty($cacheControlHeader)) {
  164. $fields['Cache-Control'] = array($cacheControlHeader);
  165. }
  166. return $fields;
  167. }
  168. /**
  169. * Checks if the specified HTTP header exists
  170. *
  171. * @param string $name Name of the header
  172. * @return boolean
  173. * @api
  174. */
  175. public function has($name) {
  176. if ($name === 'Cache-Control') {
  177. return ($this->getCacheControlHeader() !== NULL);
  178. }
  179. return isset($this->fields[$name]);
  180. }
  181. /**
  182. * Removes the specified header field
  183. *
  184. * @param string $name Name of the field
  185. * @return void
  186. * @api
  187. */
  188. public function remove($name) {
  189. unset($this->fields[$name]);
  190. }
  191. /**
  192. * Sets a cookie
  193. *
  194. * @param \TYPO3\FLOW3\Http\Cookie $cookie
  195. * @return void
  196. * @api
  197. */
  198. public function setCookie(Cookie $cookie) {
  199. $this->cookies[$cookie->getName()] = $cookie;
  200. }
  201. /**
  202. * Returns a cookie specified by the given name
  203. *
  204. * @param string $name Name of the cookie
  205. * @return \TYPO3\FLOW3\Http\Cookie The cookie or NULL if no such cookie exists
  206. * @api
  207. */
  208. public function getCookie($name) {
  209. return isset($this->cookies[$name]) ? $this->cookies[$name] : NULL;
  210. }
  211. /**
  212. * Returns all cookies
  213. *
  214. * @return array
  215. * @api
  216. */
  217. public function getCookies() {
  218. return $this->cookies;
  219. }
  220. /**
  221. * Checks if the specified cookie exists
  222. *
  223. * @param string $name Name of the cookie
  224. * @return boolean
  225. * @api
  226. */
  227. public function hasCookie($name) {
  228. return isset($this->cookies[$name]);
  229. }
  230. /**
  231. * Removes the specified cookie if it exists
  232. *
  233. * @param string $name Name of the cookie to remove
  234. * @return void
  235. * @api
  236. */
  237. public function removeCookie($name) {
  238. unset ($this->cookies[$name]);
  239. }
  240. /**
  241. * Although not 100% semantically correct, an alias for removeCookie()
  242. *
  243. * @param string $name Name of the cookie to eat
  244. * @return void
  245. * @api
  246. */
  247. public function eatCookie($name) {
  248. $this->removeCookie($name);
  249. }
  250. /**
  251. * Sets a special directive for use in the Cache-Control header, according to
  252. * RFC 2616 / 14.9
  253. *
  254. * @param string $name Name of the directive, for example "max-age"
  255. * @param string $value An optional value
  256. * @return void
  257. * @api
  258. */
  259. public function setCacheControlDirective($name, $value = NULL) {
  260. switch ($name) {
  261. case 'public':
  262. $this->cacheDirectives['visibility'] = 'public';
  263. break;
  264. case 'private':
  265. case 'no-cache':
  266. $this->cacheDirectives['visibility'] = $name . (!empty($value) ? '="' . $value . '"' : '');
  267. break;
  268. case 'no-store':
  269. case 'no-transform':
  270. case 'must-revalidate':
  271. case 'proxy-revalidate':
  272. $this->cacheDirectives[$name] = $name;
  273. break;
  274. case 'max-age':
  275. case 's-maxage':
  276. $this->cacheDirectives[$name] = $name . '=' . $value;
  277. break;
  278. }
  279. }
  280. /**
  281. * Removes a special directive previously set for the Cache-Control header.
  282. *
  283. * @param string $name Name of the directive, for example "public"
  284. * @return void
  285. */
  286. public function removeCacheControlDirective($name) {
  287. switch ($name) {
  288. case 'public':
  289. case 'private':
  290. case 'no-cache':
  291. $this->cacheDirectives['visibility'] = '';
  292. break;
  293. case 'no-store':
  294. case 'max-age':
  295. case 's-maxage':
  296. case 'no-transform':
  297. case 'must-revalidate':
  298. case 'proxy-revalidate':
  299. $this->cacheDirectives[$name] = '';
  300. break;
  301. }
  302. }
  303. /**
  304. * Returns the value of the specified Cache-Control directive.
  305. *
  306. * If the cache directive is not present, NULL is returned. If the specified
  307. * directive is present but contains no value, this method returns TRUE. Finally,
  308. * if the directive is present and does contain a value, the value is returned.
  309. *
  310. * @param string $name Name of the cache directive, for example "max-age"
  311. * @return mixed
  312. * @api
  313. */
  314. public function getCacheControlDirective($name) {
  315. $value = NULL;
  316. switch ($name) {
  317. case 'public':
  318. $value = ($this->cacheDirectives['visibility'] === 'public' ? TRUE : NULL);
  319. break;
  320. case 'private':
  321. case 'no-cache':
  322. preg_match('/^(' . $name . ')(?:="([^"]+)")?$/', $this->cacheDirectives['visibility'], $matches);
  323. if (!isset($matches[1])) {
  324. $value = NULL;
  325. } else {
  326. $value = (isset($matches[2]) ? $matches[2] : TRUE);
  327. }
  328. break;
  329. case 'no-store':
  330. case 'no-transform':
  331. case 'must-revalidate':
  332. case 'proxy-revalidate':
  333. $value = ($this->cacheDirectives[$name] !== '' ? TRUE : NULL);
  334. break;
  335. case 'max-age':
  336. case 's-maxage':
  337. preg_match('/^(' . $name . ')=(.+)$/', $this->cacheDirectives[$name], $matches);
  338. if (!isset($matches[1])) {
  339. $value = NULL;
  340. } else {
  341. $value = (isset($matches[2]) ? intval($matches[2]) : TRUE);
  342. }
  343. break;
  344. }
  345. return $value;
  346. }
  347. /**
  348. * Internally sets the cache directives correctly by parsing the given
  349. * Cache-Control field value.
  350. *
  351. * @param string $rawFieldValue The value of a specification compliant Cache-Control header
  352. * @return void
  353. * @see set()
  354. */
  355. protected function setCacheControlDirectivesFromRawHeader($rawFieldValue) {
  356. foreach (array_keys($this->cacheDirectives) as $key) {
  357. $this->cacheDirectives[$key] = '';
  358. }
  359. preg_match_all('/([a-zA-Z][a-zA-Z_-]*)\s*(?:=\s*(?:"([^"]*)"|([^,;\s"]*)))?/', $rawFieldValue, $matches, PREG_SET_ORDER);
  360. foreach ($matches as $match) {
  361. if (isset($match[2]) && $match[2] !== '') {
  362. $value = $match[2];
  363. } elseif (isset($match[3]) && $match[3] !== '') {
  364. $value = $match[3];
  365. } else {
  366. $value = NULL;
  367. }
  368. $this->setCacheControlDirective(strtolower($match[1]), $value);
  369. }
  370. }
  371. /**
  372. * Renders and returns a Cache-Control header, based on the previously set
  373. * cache control directives.
  374. *
  375. * @return string Either the value of the header or NULL if it shall be omitted
  376. * @see get()
  377. */
  378. protected function getCacheControlHeader() {
  379. $cacheControl = '';
  380. foreach ($this->cacheDirectives as $cacheDirective) {
  381. $cacheControl .= ($cacheDirective !== '' ? $cacheDirective . ', ' : '');
  382. }
  383. $cacheControl = trim($cacheControl, ' ,');
  384. return ($cacheControl === '' ? NULL : $cacheControl);
  385. }
  386. /**
  387. * Internally sets cookie objects based on the Cookie header field value.
  388. *
  389. * @param string $rawFieldValue The value of a specification compliant Cookie header
  390. * @return void
  391. * @see set()
  392. */
  393. protected function setCookiesFromRawHeader($rawFieldValue) {
  394. $cookiePairs = explode(';', $rawFieldValue);
  395. foreach($cookiePairs as $cookiePair) {
  396. list($name, $value) = explode('=', $cookiePair, 2);
  397. $this->setCookie(new Cookie(trim($name), urldecode(trim($value, "\t ;\""))));
  398. }
  399. }
  400. }
  401. ?>