a1bdc3de0af4454495207e5a5feba6f471a5c659
[yaffs-website] / src / RequestTrait.php
1 <?php
2 /**
3  * @see       https://github.com/zendframework/zend-diactoros for the canonical source repository
4  * @copyright Copyright (c) 2015-2017 Zend Technologies USA Inc. (http://www.zend.com)
5  * @license   https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
6  */
7
8 namespace Zend\Diactoros;
9
10 use InvalidArgumentException;
11 use Psr\Http\Message\StreamInterface;
12 use Psr\Http\Message\UriInterface;
13
14 use function array_keys;
15 use function get_class;
16 use function gettype;
17 use function is_object;
18 use function is_string;
19 use function preg_match;
20 use function sprintf;
21 use function strtolower;
22
23 /**
24  * Trait with common request behaviors.
25  *
26  * Server and client-side requests differ slightly in how the Host header is
27  * handled; on client-side, it should be calculated on-the-fly from the
28  * composed URI (if present), while on server-side, it will be calculated from
29  * the environment. As such, this trait exists to provide the common code
30  * between both client-side and server-side requests, and each can then
31  * use the headers functionality required by their implementations.
32  */
33 trait RequestTrait
34 {
35     use MessageTrait;
36
37     /**
38      * @var string
39      */
40     private $method = '';
41
42     /**
43      * The request-target, if it has been provided or calculated.
44      *
45      * @var null|string
46      */
47     private $requestTarget;
48
49     /**
50      * @var UriInterface
51      */
52     private $uri;
53
54     /**
55      * Initialize request state.
56      *
57      * Used by constructors.
58      *
59      * @param null|string|UriInterface $uri URI for the request, if any.
60      * @param null|string $method HTTP method for the request, if any.
61      * @param string|resource|StreamInterface $body Message body, if any.
62      * @param array $headers Headers for the message, if any.
63      * @throws InvalidArgumentException for any invalid value.
64      */
65     private function initialize($uri = null, $method = null, $body = 'php://memory', array $headers = [])
66     {
67         $this->validateMethod($method);
68
69         $this->method = $method ?: '';
70         $this->uri    = $this->createUri($uri);
71         $this->stream = $this->getStream($body, 'wb+');
72
73         $this->setHeaders($headers);
74
75         // per PSR-7: attempt to set the Host header from a provided URI if no
76         // Host header is provided
77         if (! $this->hasHeader('Host') && $this->uri->getHost()) {
78             $this->headerNames['host'] = 'Host';
79             $this->headers['Host'] = [$this->getHostFromUri()];
80         }
81     }
82
83     /**
84      * Create and return a URI instance.
85      *
86      * If `$uri` is a already a `UriInterface` instance, returns it.
87      *
88      * If `$uri` is a string, passes it to the `Uri` constructor to return an
89      * instance.
90      *
91      * If `$uri is null, creates and returns an empty `Uri` instance.
92      *
93      * Otherwise, it raises an exception.
94      *
95      * @param null|string|UriInterface $uri
96      * @return UriInterface
97      * @throws InvalidArgumentException
98      */
99     private function createUri($uri)
100     {
101         if ($uri instanceof UriInterface) {
102             return $uri;
103         }
104         if (is_string($uri)) {
105             return new Uri($uri);
106         }
107         if ($uri === null) {
108             return new Uri();
109         }
110         throw new InvalidArgumentException(
111             'Invalid URI provided; must be null, a string, or a Psr\Http\Message\UriInterface instance'
112         );
113     }
114
115     /**
116      * Retrieves the message's request target.
117      *
118      * Retrieves the message's request-target either as it will appear (for
119      * clients), as it appeared at request (for servers), or as it was
120      * specified for the instance (see withRequestTarget()).
121      *
122      * In most cases, this will be the origin-form of the composed URI,
123      * unless a value was provided to the concrete implementation (see
124      * withRequestTarget() below).
125      *
126      * If no URI is available, and no request-target has been specifically
127      * provided, this method MUST return the string "/".
128      *
129      * @return string
130      */
131     public function getRequestTarget()
132     {
133         if (null !== $this->requestTarget) {
134             return $this->requestTarget;
135         }
136
137         $target = $this->uri->getPath();
138         if ($this->uri->getQuery()) {
139             $target .= '?' . $this->uri->getQuery();
140         }
141
142         if (empty($target)) {
143             $target = '/';
144         }
145
146         return $target;
147     }
148
149     /**
150      * Create a new instance with a specific request-target.
151      *
152      * If the request needs a non-origin-form request-target — e.g., for
153      * specifying an absolute-form, authority-form, or asterisk-form —
154      * this method may be used to create an instance with the specified
155      * request-target, verbatim.
156      *
157      * This method MUST be implemented in such a way as to retain the
158      * immutability of the message, and MUST return a new instance that has the
159      * changed request target.
160      *
161      * @link http://tools.ietf.org/html/rfc7230#section-2.7 (for the various
162      *     request-target forms allowed in request messages)
163      * @param mixed $requestTarget
164      * @return static
165      * @throws InvalidArgumentException if the request target is invalid.
166      */
167     public function withRequestTarget($requestTarget)
168     {
169         if (preg_match('#\s#', $requestTarget)) {
170             throw new InvalidArgumentException(
171                 'Invalid request target provided; cannot contain whitespace'
172             );
173         }
174
175         $new = clone $this;
176         $new->requestTarget = $requestTarget;
177         return $new;
178     }
179
180     /**
181      * Retrieves the HTTP method of the request.
182      *
183      * @return string Returns the request method.
184      */
185     public function getMethod()
186     {
187         return $this->method;
188     }
189
190     /**
191      * Return an instance with the provided HTTP method.
192      *
193      * While HTTP method names are typically all uppercase characters, HTTP
194      * method names are case-sensitive and thus implementations SHOULD NOT
195      * modify the given string.
196      *
197      * This method MUST be implemented in such a way as to retain the
198      * immutability of the message, and MUST return an instance that has the
199      * changed request method.
200      *
201      * @param string $method Case-insensitive method.
202      * @return static
203      * @throws InvalidArgumentException for invalid HTTP methods.
204      */
205     public function withMethod($method)
206     {
207         $this->validateMethod($method);
208         $new = clone $this;
209         $new->method = $method;
210         return $new;
211     }
212
213     /**
214      * Retrieves the URI instance.
215      *
216      * This method MUST return a UriInterface instance.
217      *
218      * @link http://tools.ietf.org/html/rfc3986#section-4.3
219      * @return UriInterface Returns a UriInterface instance
220      *     representing the URI of the request, if any.
221      */
222     public function getUri()
223     {
224         return $this->uri;
225     }
226
227     /**
228      * Returns an instance with the provided URI.
229      *
230      * This method will update the Host header of the returned request by
231      * default if the URI contains a host component. If the URI does not
232      * contain a host component, any pre-existing Host header will be carried
233      * over to the returned request.
234      *
235      * You can opt-in to preserving the original state of the Host header by
236      * setting `$preserveHost` to `true`. When `$preserveHost` is set to
237      * `true`, the returned request will not update the Host header of the
238      * returned message -- even if the message contains no Host header. This
239      * means that a call to `getHeader('Host')` on the original request MUST
240      * equal the return value of a call to `getHeader('Host')` on the returned
241      * request.
242      *
243      * This method MUST be implemented in such a way as to retain the
244      * immutability of the message, and MUST return an instance that has the
245      * new UriInterface instance.
246      *
247      * @link http://tools.ietf.org/html/rfc3986#section-4.3
248      * @param UriInterface $uri New request URI to use.
249      * @param bool $preserveHost Preserve the original state of the Host header.
250      * @return static
251      */
252     public function withUri(UriInterface $uri, $preserveHost = false)
253     {
254         $new = clone $this;
255         $new->uri = $uri;
256
257         if ($preserveHost && $this->hasHeader('Host')) {
258             return $new;
259         }
260
261         if (! $uri->getHost()) {
262             return $new;
263         }
264
265         $host = $uri->getHost();
266         if ($uri->getPort()) {
267             $host .= ':' . $uri->getPort();
268         }
269
270         $new->headerNames['host'] = 'Host';
271
272         // Remove an existing host header if present, regardless of current
273         // de-normalization of the header name.
274         // @see https://github.com/zendframework/zend-diactoros/issues/91
275         foreach (array_keys($new->headers) as $header) {
276             if (strtolower($header) === 'host') {
277                 unset($new->headers[$header]);
278             }
279         }
280
281         $new->headers['Host'] = [$host];
282
283         return $new;
284     }
285
286     /**
287      * Validate the HTTP method
288      *
289      * @param null|string $method
290      * @throws InvalidArgumentException on invalid HTTP method.
291      */
292     private function validateMethod($method)
293     {
294         if (null === $method) {
295             return;
296         }
297
298         if (! is_string($method)) {
299             throw new InvalidArgumentException(sprintf(
300                 'Unsupported HTTP method; must be a string, received %s',
301                 (is_object($method) ? get_class($method) : gettype($method))
302             ));
303         }
304
305         if (! preg_match('/^[!#$%&\'*+.^_`\|~0-9a-z-]+$/i', $method)) {
306             throw new InvalidArgumentException(sprintf(
307                 'Unsupported HTTP method "%s" provided',
308                 $method
309             ));
310         }
311     }
312
313     /**
314      * Retrieve the host from the URI instance
315      *
316      * @return string
317      */
318     private function getHostFromUri()
319     {
320         $host  = $this->uri->getHost();
321         $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : '';
322         return $host;
323     }
324 }