TYPO3  7.6
typo3/sysext/core/Classes/Http/Message.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Http;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
19 
28 class Message implements MessageInterface
29 {
34  protected $protocolVersion = '1.1';
35 
41  protected $headers = array();
42 
48  protected $lowercasedHeaderNames = array();
49 
54  protected $body;
55 
63  public function getProtocolVersion()
64  {
66  }
67 
81  public function withProtocolVersion($version)
82  {
83  $clonedObject = clone $this;
84  $clonedObject->protocolVersion = $version;
85  return $clonedObject;
86  }
87 
113  public function getHeaders()
114  {
115  return $this->headers;
116  }
117 
126  public function hasHeader($name)
127  {
128  return isset($this->lowercasedHeaderNames[strtolower($name)]);
129  }
130 
145  public function getHeader($name)
146  {
147  if (!$this->hasHeader($name)) {
148  return array();
149  }
150  $header = $this->lowercasedHeaderNames[strtolower($name)];
151  $headerValue = $this->headers[$header];
152  if (is_array($headerValue)) {
153  return $headerValue;
154  } else {
155  return array($headerValue);
156  }
157  }
158 
178  public function getHeaderLine($name)
179  {
180  $headerValue = $this->getHeader($name);
181  if (empty($headerValue)) {
182  return '';
183  }
184  return implode(',', $headerValue);
185  }
186 
202  public function withHeader($name, $value)
203  {
204  if (is_string($value)) {
205  $value = array($value);
206  }
207 
208  if (!is_array($value) || !$this->arrayContainsOnlyStrings($value)) {
209  throw new \InvalidArgumentException('Invalid header value for header "' . $name . '"". The value must be a string or an array of strings.', 1436717266);
210  }
211 
212  $this->validateHeaderName($name);
213  $this->validateHeaderValues($value);
214  $lowercasedHeaderName = strtolower($name);
215 
216  $clonedObject = clone $this;
217  $clonedObject->headers[$name] = $value;
218  $clonedObject->lowercasedHeaderNames[$lowercasedHeaderName] = $name;
219  return $clonedObject;
220  }
221 
238  public function withAddedHeader($name, $value)
239  {
240  if (is_string($value)) {
241  $value = array($value);
242  }
243  if (!is_array($value) || !$this->arrayContainsOnlyStrings($value)) {
244  throw new \InvalidArgumentException('Invalid header value for header "' . $name . '". The header value must be a string or array of strings', 1436717267);
245  }
246  $this->validateHeaderName($name);
247  $this->validateHeaderValues($value);
248  if (!$this->hasHeader($name)) {
249  return $this->withHeader($name, $value);
250  }
251  $name = $this->lowercasedHeaderNames[strtolower($name)];
252  $clonedObject = clone $this;
253  $clonedObject->headers[$name] = array_merge($this->headers[$name], $value);
254  return $clonedObject;
255  }
256 
269  public function withoutHeader($name)
270  {
271  if (!$this->hasHeader($name)) {
272  return clone $this;
273  }
274  // fetch the original header from the lowercased version
275  $lowercasedHeader = strtolower($name);
276  $name = $this->lowercasedHeaderNames[$lowercasedHeader];
277  $clonedObject = clone $this;
278  unset($clonedObject->headers[$name], $clonedObject->lowercasedHeaderNames[$lowercasedHeader]);
279  return $clonedObject;
280  }
281 
287  public function getBody()
288  {
289  return $this->body;
290  }
291 
305  public function withBody(StreamInterface $body)
306  {
307  $clonedObject = clone $this;
308  $clonedObject->body = $body;
309  return $clonedObject;
310  }
311 
318  protected function assertHeaders(array $headers)
319  {
320  foreach ($headers as $name => $headerValues) {
321  $this->validateHeaderName($name);
322  // check if all values are correct
323  array_walk($headerValues, function ($value, $key, Message $messageObject) {
324  if (!$messageObject->isValidHeaderValue($value)) {
325  throw new \InvalidArgumentException('Invalid header value for header "' . $key . '"', 1436717268);
326  }
327  }, $this);
328  }
329  }
330 
339  protected function filterHeaders(array $originalHeaders)
340  {
341  $headerNames = $headers = array();
342  foreach ($originalHeaders as $header => $value) {
343  if (!is_string($header) || (!is_array($value) && !is_string($value))) {
344  continue;
345  }
346  if (!is_array($value)) {
347  $value = array($value);
348  }
349  $headerNames[strtolower($header)] = $header;
350  $headers[$header] = $value;
351  }
352  return array($headerNames, $headers);
353  }
354 
361  protected function arrayContainsOnlyStrings(array $data)
362  {
363  return array_reduce($data, function ($original, $item) {
364  return is_string($item) ? $original : false;
365  }, true);
366  }
367 
375  protected function validateHeaderValues(array $values)
376  {
377  array_walk($values, function ($value, $key, Message $messageObject) {
378  if (!$messageObject->isValidHeaderValue($value)) {
379  throw new \InvalidArgumentException('Invalid header value for header "' . $key . '"', 1436717269);
380  }
381  }, $this);
382  }
383 
400  public function filter($value)
401  {
402  $value = (string)$value;
403  $length = strlen($value);
404  $string = '';
405  for ($i = 0; $i < $length; $i += 1) {
406  $ascii = ord($value[$i]);
407 
408  // Detect continuation sequences
409  if ($ascii === 13) {
410  $lf = ord($value[$i + 1]);
411  $ws = ord($value[$i + 2]);
412  if ($lf === 10 && in_array($ws, [9, 32], true)) {
413  $string .= $value[$i] . $value[$i + 1];
414  $i += 1;
415  }
416  continue;
417  }
418 
419  // Non-visible, non-whitespace characters
420  // 9 === horizontal tab
421  // 32-126, 128-254 === visible
422  // 127 === DEL
423  // 255 === null byte
424  if (($ascii < 32 && $ascii !== 9) || $ascii === 127 || $ascii > 254) {
425  continue;
426  }
427 
428  $string .= $value[$i];
429  }
430 
431  return $string;
432  }
433 
441  public function validateHeaderName($name)
442  {
443  if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $name)) {
444  throw new \InvalidArgumentException('Invalid header name, given "' . $name . '"', 1436717270);
445  }
446  }
447 
459  public function isValidHeaderValue($value)
460  {
461  $value = (string)$value;
462 
463  // Look for:
464  // \n not preceded by \r, OR
465  // \r not followed by \n, OR
466  // \r\n not followed by space or horizontal tab; these are all CRLF attacks
467  if (preg_match("#(?:(?:(?<!\r)\n)|(?:\r(?!\n))|(?:\r\n(?![ \t])))#", $value)) {
468  return false;
469  }
470 
471  $length = strlen($value);
472  for ($i = 0; $i < $length; $i += 1) {
473  $ascii = ord($value[$i]);
474 
475  // Non-visible, non-whitespace characters
476  // 9 === horizontal tab
477  // 10 === line feed
478  // 13 === carriage return
479  // 32-126, 128-254 === visible
480  // 127 === DEL
481  // 255 === null byte
482  if (($ascii < 32 && ! in_array($ascii, [9, 10, 13], true)) || $ascii === 127 || $ascii > 254) {
483  return false;
484  }
485  }
486 
487  return true;
488  }
489 }