1<?php 2 3namespace Mpdf\PsrHttpMessageShim; 4 5use Psr\Http\Message\StreamInterface; 6use Psr\Http\Message\ResponseInterface; 7use Psr\Http\Message\MessageInterface; 8 9/** 10 * PSR-7 URI implementation ported from nyholm/psr7 and adapted for PHP 5.6 11 * 12 * @link https://github.com/Nyholm/psr7/blob/master/src/Uri.php 13 */ 14class Response implements \Psr\Http\Message\ResponseInterface 15{ 16 17 /** @var array Map of standard HTTP status code/reason phrases */ 18 private static $phrases = [ 19 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 20 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', 21 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect', 22 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', 23 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required', 24 ]; 25 26 /** @var string */ 27 private $reasonPhrase; 28 29 /** @var int */ 30 private $statusCode; 31 32 /** @var array Map of all registered headers, as original name => array of values */ 33 private $headers = []; 34 35 /** @var array Map of lowercase header name => original name at registration */ 36 private $headerNames = []; 37 38 /** @var string */ 39 private $protocol; 40 41 /** @var \Psr\Http\Message\StreamInterface */ 42 private $stream; 43 44 /** 45 * @param int $status Status code 46 * @param array $headers Response headers 47 * @param string|resource|StreamInterface|null $body Response body 48 * @param string $version Protocol version 49 * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) 50 */ 51 public function __construct($status = 200, array $headers = [], $body = null, $version = '1.1', $reason = null) 52 { 53 // If we got no body, defer initialization of the stream until Response::getBody() 54 if ('' !== $body && null !== $body) { 55 $this->stream = Stream::create($body); 56 } 57 58 $this->statusCode = $status; 59 $this->setHeaders($headers); 60 if (null === $reason && isset(self::$phrases[$this->statusCode])) { 61 $this->reasonPhrase = self::$phrases[$status]; 62 } else { 63 $this->reasonPhrase = isset($reason) ? $reason : ''; 64 } 65 66 $this->protocol = $version; 67 } 68 69 public function getStatusCode(): int 70 { 71 return $this->statusCode; 72 } 73 74 public function getReasonPhrase(): string 75 { 76 return $this->reasonPhrase; 77 } 78 79 public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface 80 { 81 if (!\is_int($code) && !\is_string($code)) { 82 throw new \InvalidArgumentException('Status code has to be an integer'); 83 } 84 85 $code = (int) $code; 86 if ($code < 100 || $code > 599) { 87 throw new \InvalidArgumentException(\sprintf('Status code has to be an integer between 100 and 599. A status code of %d was given', $code)); 88 } 89 90 $new = clone $this; 91 $new->statusCode = $code; 92 if ((null === $reasonPhrase || '' === $reasonPhrase) && isset(self::$phrases[$new->statusCode])) { 93 $reasonPhrase = self::$phrases[$new->statusCode]; 94 } 95 $new->reasonPhrase = $reasonPhrase; 96 97 return $new; 98 } 99 100 public function getProtocolVersion(): string 101 { 102 return $this->protocol; 103 } 104 105 public function withProtocolVersion(string $version): MessageInterface 106 { 107 if ($this->protocol === $version) { 108 return $this; 109 } 110 111 $new = clone $this; 112 $new->protocol = $version; 113 114 return $new; 115 } 116 117 public function getHeaders(): array 118 { 119 return $this->headers; 120 } 121 122 public function hasHeader(string $header): bool 123 { 124 return isset($this->headerNames[strtolower($header)]); 125 } 126 127 public function getHeader(string $header): array 128 { 129 $header = strtolower($header); 130 131 if (!isset($this->headerNames[$header])) { 132 return []; 133 } 134 135 $header = $this->headerNames[$header]; 136 137 return $this->headers[$header]; 138 } 139 140 public function getHeaderLine(string $header): string 141 { 142 return implode(', ', $this->getHeader($header)); 143 } 144 145 public function withHeader(string $header, $value): MessageInterface 146 { 147 if (!is_array($value)) { 148 $value = [$value]; 149 } 150 151 $value = $this->trimHeaderValues($value); 152 $normalized = strtolower($header); 153 154 $new = clone $this; 155 if (isset($new->headerNames[$normalized])) { 156 unset($new->headers[$new->headerNames[$normalized]]); 157 } 158 $new->headerNames[$normalized] = $header; 159 $new->headers[$header] = $value; 160 161 return $new; 162 } 163 164 public function withAddedHeader(string $header, $value): MessageInterface 165 { 166 if (!is_array($value)) { 167 $value = [$value]; 168 } 169 170 $value = $this->trimHeaderValues($value); 171 $normalized = strtolower($header); 172 173 $new = clone $this; 174 if (isset($new->headerNames[$normalized])) { 175 $header = $this->headerNames[$normalized]; 176 $new->headers[$header] = array_merge($this->headers[$header], $value); 177 } else { 178 $new->headerNames[$normalized] = $header; 179 $new->headers[$header] = $value; 180 } 181 182 return $new; 183 } 184 185 public function withoutHeader(string $header): MessageInterface 186 { 187 $normalized = strtolower($header); 188 189 if (!isset($this->headerNames[$normalized])) { 190 return $this; 191 } 192 193 $header = $this->headerNames[$normalized]; 194 195 $new = clone $this; 196 unset($new->headers[$header], $new->headerNames[$normalized]); 197 198 return $new; 199 } 200 201 public function getBody(): StreamInterface 202 { 203 if (!$this->stream) { 204 $this->stream = Stream::create(''); 205 } 206 207 return $this->stream; 208 } 209 210 public function withBody(StreamInterface $body): MessageInterface 211 { 212 if ($body === $this->stream) { 213 return $this; 214 } 215 216 $new = clone $this; 217 $new->stream = $body; 218 219 return $new; 220 } 221 222 private function setHeaders(array $headers) 223 { 224 $this->headerNames = $this->headers = []; 225 foreach ($headers as $header => $value) { 226 if (!is_array($value)) { 227 $value = [$value]; 228 } 229 230 $value = $this->trimHeaderValues($value); 231 $normalized = strtolower($header); 232 if (isset($this->headerNames[$normalized])) { 233 $header = $this->headerNames[$normalized]; 234 $this->headers[$header] = array_merge($this->headers[$header], $value); 235 } else { 236 $this->headerNames[$normalized] = $header; 237 $this->headers[$header] = $value; 238 } 239 } 240 } 241 242 /** 243 * Trims whitespace from the header values. 244 * 245 * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. 246 * 247 * header-field = field-name ":" OWS field-value OWS 248 * OWS = *( SP / HTAB ) 249 * 250 * @param string[] $values Header values 251 * 252 * @return string[] Trimmed header values 253 * 254 * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 255 */ 256 private function trimHeaderValues(array $values) 257 { 258 return array_map(function ($value) { 259 return trim($value, " \t"); 260 }, $values); 261 } 262 263} 264