[TASK] Order use statements alphabetically
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Http / Request.php
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
17 use Psr\Http\Message\RequestInterface;
18 use Psr\Http\Message\StreamInterface;
19 use Psr\Http\Message\UriInterface;
20
21 /**
22 * Default implementation for the RequestInterface of the PSR-7 standard
23 * It is the base for any request sent BY PHP.
24 *
25 * Please see ServerRequest for the typical use cases in the framework.
26 *
27 * Highly inspired by https://github.com/phly/http/
28 *
29 * @internal Note that this is not public API yet.
30 */
31 class Request extends Message implements RequestInterface
32 {
33 /**
34 * The request-target, if it has been provided or calculated.
35 * @var NULL|string
36 */
37 protected $requestTarget;
38
39 /**
40 * The HTTP method, defaults to GET
41 *
42 * @var string
43 */
44 protected $method;
45
46 /**
47 * Supported HTTP methods
48 *
49 * @var array
50 */
51 protected $supportedMethods = array(
52 'CONNECT',
53 'DELETE',
54 'GET',
55 'HEAD',
56 'OPTIONS',
57 'PATCH',
58 'POST',
59 'PUT',
60 'TRACE',
61 // WebDAV methods
62 'COPY',
63 'LOCK',
64 'MKCOL',
65 'MOVE',
66 'PROPFIND',
67 'PROPPATCH',
68 'UNLOCK'
69 );
70
71 /**
72 * An instance of the Uri object
73 * @var UriInterface
74 */
75 protected $uri;
76
77 /**
78 * Constructor, the only place to set all parameters of this Request
79 *
80 * @param NULL|string $uri URI for the request, if any.
81 * @param NULL|string $method HTTP method for the request, if any.
82 * @param string|resource|StreamInterface $body Message body, if any.
83 * @param array $headers Headers for the message, if any.
84 * @throws \InvalidArgumentException for any invalid value.
85 */
86 public function __construct($uri = null, $method = null, $body = 'php://input', array $headers = array())
87 {
88
89 // Build a streamable object for the body
90 if (!is_string($body) && !is_resource($body) && !$body instanceof StreamInterface) {
91 throw new \InvalidArgumentException('Body must be a string stream resource identifier, a stream resource, or a StreamInterface instance', 1436717271);
92 }
93
94 if (!$body instanceof StreamInterface) {
95 $body = new Stream($body);
96 }
97
98 if (is_string($uri)) {
99 $uri = new Uri($uri);
100 }
101
102 if (!$uri instanceof UriInterface && $uri !== null) {
103 throw new \InvalidArgumentException('Invalid URI provided; must be null, a string, or a UriInterface instance', 1436717272);
104 }
105
106 $this->validateMethod($method);
107
108 $this->method = $method;
109 $this->uri = $uri;
110 $this->body = $body;
111 list($this->headerNames, $headers) = $this->filterHeaders($headers);
112 $this->assertHeaders($headers);
113 $this->headers = $headers;
114 }
115
116 /**
117 * Retrieves all message header values.
118 *
119 * The keys represent the header name as it will be sent over the wire, and
120 * each value is an array of strings associated with the header.
121 *
122 * // Represent the headers as a string
123 * foreach ($message->getHeaders() as $name => $values) {
124 * echo $name . ": " . implode(", ", $values);
125 * }
126 *
127 * // Emit headers iteratively:
128 * foreach ($message->getHeaders() as $name => $values) {
129 * foreach ($values as $value) {
130 * header(sprintf('%s: %s', $name, $value), false);
131 * }
132 * }
133 *
134 * While header names are not case-sensitive, getHeaders() will preserve the
135 * exact case in which headers were originally specified.
136 *
137 * @return array Returns an associative array of the message's headers. Each
138 * key MUST be a header name, and each value MUST be an array of strings
139 * for that header.
140 */
141 public function getHeaders()
142 {
143 $headers = parent::getHeaders();
144 if (!$this->hasHeader('host') && ($this->uri && $this->uri->getHost())) {
145 $headers['host'] = [$this->getHostFromUri()];
146 }
147 return $headers;
148 }
149
150 /**
151 * Retrieves a message header value by the given case-insensitive name.
152 *
153 * This method returns an array of all the header values of the given
154 * case-insensitive header name.
155 *
156 * If the header does not appear in the message, this method MUST return an
157 * empty array.
158 *
159 * @param string $name Case-insensitive header field name.
160 * @return string[] An array of string values as provided for the given
161 * header. If the header does not appear in the message, this method MUST
162 * return an empty array.
163 */
164 public function getHeader($header)
165 {
166 if (!$this->hasHeader($header) && strtolower($header) === 'host' && ($this->uri && $this->uri->getHost())) {
167 return array($this->getHostFromUri());
168 }
169 return parent::getHeader($header);
170 }
171
172 /**
173 * Retrieve the host from the URI instance
174 *
175 * @return string
176 */
177 protected function getHostFromUri()
178 {
179 $host = $this->uri->getHost();
180 $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : '';
181 return $host;
182 }
183
184 /**
185 * Retrieves the message's request target.
186 *
187 * Retrieves the message's request-target either as it will appear (for
188 * clients), as it appeared at request (for servers), or as it was
189 * specified for the instance (see withRequestTarget()).
190 *
191 * In most cases, this will be the origin-form of the composed URI,
192 * unless a value was provided to the concrete implementation (see
193 * withRequestTarget() below).
194 *
195 * If no URI is available, and no request-target has been specifically
196 * provided, this method MUST return the string "/".
197 *
198 * @return string
199 */
200 public function getRequestTarget()
201 {
202 if ($this->requestTarget !== null) {
203 return $this->requestTarget;
204 }
205 if (!$this->uri) {
206 return '/';
207 }
208 $target = $this->uri->getPath();
209
210 if ($this->uri->getQuery()) {
211 $target .= '?' . $this->uri->getQuery();
212 }
213
214 if (empty($target)) {
215 $target = '/';
216 }
217 return $target;
218 }
219
220 /**
221 * Return an instance with the specific request-target.
222 *
223 * If the request needs a non-origin-form request-target — e.g., for
224 * specifying an absolute-form, authority-form, or asterisk-form —
225 * this method may be used to create an instance with the specified
226 * request-target, verbatim.
227 *
228 * This method MUST be implemented in such a way as to retain the
229 * immutability of the message, and MUST return an instance that has the
230 * changed request target.
231 *
232 * @link http://tools.ietf.org/html/rfc7230#section-2.7 (for the various
233 * request-target forms allowed in request messages)
234 *
235 * @param mixed $requestTarget
236 * @return Request
237 */
238 public function withRequestTarget($requestTarget)
239 {
240 if (preg_match('#\s#', $requestTarget)) {
241 throw new \InvalidArgumentException('Invalid request target provided which contains whitespaces.', 1436717273);
242 }
243 $clonedObject = clone $this;
244 $clonedObject->requestTarget = $requestTarget;
245 return $clonedObject;
246 }
247
248 /**
249 * Retrieves the HTTP method of the request, defaults to GET
250 *
251 * @return string Returns the request method.
252 */
253 public function getMethod()
254 {
255 return !empty($this->method) ? $this->method : 'GET';
256 }
257
258 /**
259 * Return an instance with the provided HTTP method.
260 *
261 * While HTTP method names are typically all uppercase characters, HTTP
262 * method names are case-sensitive and thus implementations SHOULD NOT
263 * modify the given string.
264 *
265 * This method MUST be implemented in such a way as to retain the
266 * immutability of the message, and MUST return an instance that has the
267 * changed request method.
268 *
269 * @param string $method Case-sensitive method.
270 * @return Request
271 * @throws \InvalidArgumentException for invalid HTTP methods.
272 */
273 public function withMethod($method)
274 {
275 $clonedObject = clone $this;
276 $clonedObject->method = $method;
277 return $clonedObject;
278 }
279
280 /**
281 * Retrieves the URI instance.
282 *
283 * This method MUST return a UriInterface instance.
284 *
285 * @link http://tools.ietf.org/html/rfc3986#section-4.3
286 * @return \Psr\Http\Message\UriInterface Returns a UriInterface instance
287 * representing the URI of the request.
288 */
289 public function getUri()
290 {
291 return $this->uri;
292 }
293
294 /**
295 * Returns an instance with the provided URI.
296 *
297 * This method MUST update the Host header of the returned request by
298 * default if the URI contains a host component. If the URI does not
299 * contain a host component, any pre-existing Host header MUST be carried
300 * over to the returned request.
301 *
302 * You can opt-in to preserving the original state of the Host header by
303 * setting `$preserveHost` to `true`. When `$preserveHost` is set to
304 * `true`, this method interacts with the Host header in the following ways:
305 *
306 * - If the the Host header is missing or empty, and the new URI contains
307 * a host component, this method MUST update the Host header in the returned
308 * request.
309 * - If the Host header is missing or empty, and the new URI does not contain a
310 * host component, this method MUST NOT update the Host header in the returned
311 * request.
312 * - If a Host header is present and non-empty, this method MUST NOT update
313 * the Host header in the returned request.
314 *
315 * This method MUST be implemented in such a way as to retain the
316 * immutability of the message, and MUST return an instance that has the
317 * new UriInterface instance.
318 *
319 * @link http://tools.ietf.org/html/rfc3986#section-4.3
320 *
321 * @param \Psr\Http\Message\UriInterface $uri New request URI to use.
322 * @param bool $preserveHost Preserve the original state of the Host header.
323 * @return Request
324 */
325 public function withUri(UriInterface $uri, $preserveHost = false)
326 {
327 $clonedObject = clone $this;
328 $clonedObject->uri = $uri;
329
330 if ($preserveHost) {
331 return $clonedObject;
332 }
333
334 if (!$uri->getHost()) {
335 return $clonedObject;
336 }
337
338 $host = $uri->getHost();
339
340 if ($uri->getPort()) {
341 $host .= ':' . $uri->getPort();
342 }
343
344 $clonedObject->headerNames['host'] = 'Host';
345 $clonedObject->headers['Host'] = array($host);
346 return $clonedObject;
347 }
348
349 /**
350 * Validate the HTTP method, helper function.
351 *
352 * @param NULL|string $method
353 * @throws \InvalidArgumentException on invalid HTTP method.
354 */
355 protected function validateMethod($method)
356 {
357 if ($method !== null) {
358 if (!is_string($method)) {
359 $methodAsString = is_object($method) ? get_class($method) : gettype($method);
360 throw new \InvalidArgumentException('Unsupported HTTP method "' . $methodAsString . '".', 1436717274);
361 }
362 $method = strtoupper($method);
363 if (!in_array($method, $this->supportedMethods, true)) {
364 throw new \InvalidArgumentException('Unsupported HTTP method "' . $method . '".', 1436717275);
365 }
366 }
367 }
368 }