1<?php 2 3namespace Mpdf\PsrHttpMessageShim; 4 5use Psr\Http\Message\MessageInterface; 6use Psr\Http\Message\RequestInterface; 7use Psr\Http\Message\StreamInterface; 8use Psr\Http\Message\UriInterface; 9 10/** 11 * PSR-7 URI implementation ported from nyholm/psr7 and adapted for PHP 5.6 12 * 13 * @link https://github.com/Nyholm/psr7/blob/master/src/Uri.php 14 */ 15class Request implements \Psr\Http\Message\RequestInterface 16{ 17 18 /** @var string */ 19 private $method; 20 21 /** @var null|string */ 22 private $requestTarget; 23 24 /** @var null|UriInterface */ 25 private $uri; 26 27 /** @var array Map of all registered headers, as original name => array of values */ 28 private $headers = []; 29 30 /** @var array Map of lowercase header name => original name at registration */ 31 private $headerNames = []; 32 33 /** @var string */ 34 private $protocol; 35 36 /** @var StreamInterface */ 37 private $stream; 38 39 /** 40 * @param string $method HTTP method 41 * @param string|UriInterface $uri URI 42 * @param array $headers Request headers 43 * @param string|null|resource|StreamInterface $body Request body 44 * @param string $version Protocol version 45 */ 46 public function __construct( 47 $method, 48 $uri, 49 array $headers = [], 50 $body = null, 51 $version = '1.1' 52 ) { 53 if (!($uri instanceof UriInterface)) { 54 $uri = new Uri($uri); 55 } 56 57 $this->method = $method; 58 $this->uri = $uri; 59 $this->setHeaders($headers); 60 $this->protocol = $version; 61 62 if (!$this->hasHeader('Host')) { 63 $this->updateHostFromUri(); 64 } 65 66 if ($body !== '' && $body !== null) { 67 $this->stream = Stream::create($body); 68 } 69 } 70 71 public function getRequestTarget(): string 72 { 73 if ($this->requestTarget !== null) { 74 return $this->requestTarget; 75 } 76 77 $target = $this->uri->getPath(); 78 if ($target == '') { 79 $target = '/'; 80 } 81 if ($this->uri->getQuery() != '') { 82 $target .= '?'.$this->uri->getQuery(); 83 } 84 85 return $target; 86 } 87 88 public function withRequestTarget(string $requestTarget): RequestInterface 89 { 90 if (preg_match('#\s#', $requestTarget)) { 91 throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace'); 92 } 93 94 $new = clone $this; 95 $new->requestTarget = $requestTarget; 96 97 return $new; 98 } 99 100 public function getMethod(): string 101 { 102 return $this->method; 103 } 104 105 public function withMethod(string $method): RequestInterface 106 { 107 $new = clone $this; 108 $new->method = $method; 109 110 return $new; 111 } 112 113 public function getUri(): UriInterface 114 { 115 return $this->uri; 116 } 117 118 public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface 119 { 120 if ($uri === $this->uri) { 121 return $this; 122 } 123 124 $new = clone $this; 125 $new->uri = $uri; 126 127 if (!$preserveHost || !$this->hasHeader('Host')) { 128 $new->updateHostFromUri(); 129 } 130 131 return $new; 132 } 133 134 private function updateHostFromUri() 135 { 136 $host = $this->uri->getHost(); 137 138 if ($host == '') { 139 return; 140 } 141 142 if (($port = $this->uri->getPort()) !== null) { 143 $host .= ':'.$port; 144 } 145 146 if (isset($this->headerNames['host'])) { 147 $header = $this->headerNames['host']; 148 } else { 149 $header = 'Host'; 150 $this->headerNames['host'] = 'Host'; 151 } 152 // Ensure Host is the first header. 153 // See: http://tools.ietf.org/html/rfc7230#section-5.4 154 $this->headers = [$header => [$host]] + $this->headers; 155 } 156 157 public function getProtocolVersion(): string 158 { 159 return $this->protocol; 160 } 161 162 public function withProtocolVersion(string $version): MessageInterface 163 { 164 if ($this->protocol === $version) { 165 return $this; 166 } 167 168 $new = clone $this; 169 $new->protocol = $version; 170 171 return $new; 172 } 173 174 public function getHeaders(): array 175 { 176 return $this->headers; 177 } 178 179 public function hasHeader(string $header): bool 180 { 181 return isset($this->headerNames[strtolower($header)]); 182 } 183 184 public function getHeader(string $header): array 185 { 186 $header = strtolower($header); 187 188 if (!isset($this->headerNames[$header])) { 189 return []; 190 } 191 192 $header = $this->headerNames[$header]; 193 194 return $this->headers[$header]; 195 } 196 197 public function getHeaderLine(string $header):string 198 { 199 return implode(', ', $this->getHeader($header)); 200 } 201 202 public function withHeader(string $header, $value): MessageInterface 203 { 204 if (!is_array($value)) { 205 $value = [$value]; 206 } 207 208 $value = $this->trimHeaderValues($value); 209 $normalized = strtolower($header); 210 211 $new = clone $this; 212 if (isset($new->headerNames[$normalized])) { 213 unset($new->headers[$new->headerNames[$normalized]]); 214 } 215 $new->headerNames[$normalized] = $header; 216 $new->headers[$header] = $value; 217 218 return $new; 219 } 220 221 public function withAddedHeader(string $header, $value): MessageInterface 222 { 223 if (!is_array($value)) { 224 $value = [$value]; 225 } 226 227 $value = $this->trimHeaderValues($value); 228 $normalized = strtolower($header); 229 230 $new = clone $this; 231 if (isset($new->headerNames[$normalized])) { 232 $header = $this->headerNames[$normalized]; 233 $new->headers[$header] = array_merge($this->headers[$header], $value); 234 } else { 235 $new->headerNames[$normalized] = $header; 236 $new->headers[$header] = $value; 237 } 238 239 return $new; 240 } 241 242 public function withoutHeader(string $header): MessageInterface 243 { 244 $normalized = strtolower($header); 245 246 if (!isset($this->headerNames[$normalized])) { 247 return $this; 248 } 249 250 $header = $this->headerNames[$normalized]; 251 252 $new = clone $this; 253 unset($new->headers[$header], $new->headerNames[$normalized]); 254 255 return $new; 256 } 257 258 public function getBody(): StreamInterface 259 { 260 if (!$this->stream) { 261 $this->stream = Stream::create(''); 262 $this->stream->rewind(); 263 } 264 265 return $this->stream; 266 } 267 268 public function withBody(StreamInterface $body): MessageInterface 269 { 270 if ($body === $this->stream) { 271 return $this; 272 } 273 274 $new = clone $this; 275 $new->stream = $body; 276 277 return $new; 278 } 279 280 private function setHeaders(array $headers) 281 { 282 $this->headerNames = $this->headers = []; 283 foreach ($headers as $header => $value) { 284 if (!is_array($value)) { 285 $value = [$value]; 286 } 287 288 $value = $this->trimHeaderValues($value); 289 $normalized = strtolower($header); 290 if (isset($this->headerNames[$normalized])) { 291 $header = $this->headerNames[$normalized]; 292 $this->headers[$header] = array_merge($this->headers[$header], $value); 293 } else { 294 $this->headerNames[$normalized] = $header; 295 $this->headers[$header] = $value; 296 } 297 } 298 } 299 300 /** 301 * Trims whitespace from the header values. 302 * 303 * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. 304 * 305 * header-field = field-name ":" OWS field-value OWS 306 * OWS = *( SP / HTAB ) 307 * 308 * @param string[] $values Header values 309 * 310 * @return string[] Trimmed header values 311 * 312 * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 313 */ 314 private function trimHeaderValues(array $values) 315 { 316 return array_map(function ($value) { 317 return trim($value, " \t"); 318 }, $values); 319 } 320 321} 322