Mercurial > hg > cmmr2012-drupal-site
comparison vendor/zendframework/zend-diactoros/src/ServerRequestFactory.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | 5311817fb629 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 <?php | |
2 /** | |
3 * Zend Framework (http://framework.zend.com/) | |
4 * | |
5 * @see http://github.com/zendframework/zend-diactoros for the canonical source repository | |
6 * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com) | |
7 * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License | |
8 */ | |
9 | |
10 namespace Zend\Diactoros; | |
11 | |
12 use InvalidArgumentException; | |
13 use Psr\Http\Message\UploadedFileInterface; | |
14 use stdClass; | |
15 use UnexpectedValueException; | |
16 | |
17 /** | |
18 * Class for marshaling a request object from the current PHP environment. | |
19 * | |
20 * Logic largely refactored from the ZF2 Zend\Http\PhpEnvironment\Request class. | |
21 * | |
22 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) | |
23 * @license http://framework.zend.com/license/new-bsd New BSD License | |
24 */ | |
25 abstract class ServerRequestFactory | |
26 { | |
27 /** | |
28 * Function to use to get apache request headers; present only to simplify mocking. | |
29 * | |
30 * @var callable | |
31 */ | |
32 private static $apacheRequestHeaders = 'apache_request_headers'; | |
33 | |
34 /** | |
35 * Create a request from the supplied superglobal values. | |
36 * | |
37 * If any argument is not supplied, the corresponding superglobal value will | |
38 * be used. | |
39 * | |
40 * The ServerRequest created is then passed to the fromServer() method in | |
41 * order to marshal the request URI and headers. | |
42 * | |
43 * @see fromServer() | |
44 * @param array $server $_SERVER superglobal | |
45 * @param array $query $_GET superglobal | |
46 * @param array $body $_POST superglobal | |
47 * @param array $cookies $_COOKIE superglobal | |
48 * @param array $files $_FILES superglobal | |
49 * @return ServerRequest | |
50 * @throws InvalidArgumentException for invalid file values | |
51 */ | |
52 public static function fromGlobals( | |
53 array $server = null, | |
54 array $query = null, | |
55 array $body = null, | |
56 array $cookies = null, | |
57 array $files = null | |
58 ) { | |
59 $server = static::normalizeServer($server ?: $_SERVER); | |
60 $files = static::normalizeFiles($files ?: $_FILES); | |
61 $headers = static::marshalHeaders($server); | |
62 | |
63 if (null === $cookies && array_key_exists('cookie', $headers)) { | |
64 $cookies = self::parseCookieHeader($headers['cookie']); | |
65 } | |
66 | |
67 return new ServerRequest( | |
68 $server, | |
69 $files, | |
70 static::marshalUriFromServer($server, $headers), | |
71 static::get('REQUEST_METHOD', $server, 'GET'), | |
72 'php://input', | |
73 $headers, | |
74 $cookies ?: $_COOKIE, | |
75 $query ?: $_GET, | |
76 $body ?: $_POST, | |
77 static::marshalProtocolVersion($server) | |
78 ); | |
79 } | |
80 | |
81 /** | |
82 * Access a value in an array, returning a default value if not found | |
83 * | |
84 * Will also do a case-insensitive search if a case sensitive search fails. | |
85 * | |
86 * @param string $key | |
87 * @param array $values | |
88 * @param mixed $default | |
89 * @return mixed | |
90 */ | |
91 public static function get($key, array $values, $default = null) | |
92 { | |
93 if (array_key_exists($key, $values)) { | |
94 return $values[$key]; | |
95 } | |
96 | |
97 return $default; | |
98 } | |
99 | |
100 /** | |
101 * Search for a header value. | |
102 * | |
103 * Does a case-insensitive search for a matching header. | |
104 * | |
105 * If found, it is returned as a string, using comma concatenation. | |
106 * | |
107 * If not, the $default is returned. | |
108 * | |
109 * @param string $header | |
110 * @param array $headers | |
111 * @param mixed $default | |
112 * @return string | |
113 */ | |
114 public static function getHeader($header, array $headers, $default = null) | |
115 { | |
116 $header = strtolower($header); | |
117 $headers = array_change_key_case($headers, CASE_LOWER); | |
118 if (array_key_exists($header, $headers)) { | |
119 $value = is_array($headers[$header]) ? implode(', ', $headers[$header]) : $headers[$header]; | |
120 return $value; | |
121 } | |
122 | |
123 return $default; | |
124 } | |
125 | |
126 /** | |
127 * Marshal the $_SERVER array | |
128 * | |
129 * Pre-processes and returns the $_SERVER superglobal. | |
130 * | |
131 * @param array $server | |
132 * @return array | |
133 */ | |
134 public static function normalizeServer(array $server) | |
135 { | |
136 // This seems to be the only way to get the Authorization header on Apache | |
137 $apacheRequestHeaders = self::$apacheRequestHeaders; | |
138 if (isset($server['HTTP_AUTHORIZATION']) | |
139 || ! is_callable($apacheRequestHeaders) | |
140 ) { | |
141 return $server; | |
142 } | |
143 | |
144 $apacheRequestHeaders = $apacheRequestHeaders(); | |
145 if (isset($apacheRequestHeaders['Authorization'])) { | |
146 $server['HTTP_AUTHORIZATION'] = $apacheRequestHeaders['Authorization']; | |
147 return $server; | |
148 } | |
149 | |
150 if (isset($apacheRequestHeaders['authorization'])) { | |
151 $server['HTTP_AUTHORIZATION'] = $apacheRequestHeaders['authorization']; | |
152 return $server; | |
153 } | |
154 | |
155 return $server; | |
156 } | |
157 | |
158 /** | |
159 * Normalize uploaded files | |
160 * | |
161 * Transforms each value into an UploadedFileInterface instance, and ensures | |
162 * that nested arrays are normalized. | |
163 * | |
164 * @param array $files | |
165 * @return array | |
166 * @throws InvalidArgumentException for unrecognized values | |
167 */ | |
168 public static function normalizeFiles(array $files) | |
169 { | |
170 $normalized = []; | |
171 foreach ($files as $key => $value) { | |
172 if ($value instanceof UploadedFileInterface) { | |
173 $normalized[$key] = $value; | |
174 continue; | |
175 } | |
176 | |
177 if (is_array($value) && isset($value['tmp_name'])) { | |
178 $normalized[$key] = self::createUploadedFileFromSpec($value); | |
179 continue; | |
180 } | |
181 | |
182 if (is_array($value)) { | |
183 $normalized[$key] = self::normalizeFiles($value); | |
184 continue; | |
185 } | |
186 | |
187 throw new InvalidArgumentException('Invalid value in files specification'); | |
188 } | |
189 return $normalized; | |
190 } | |
191 | |
192 /** | |
193 * Marshal headers from $_SERVER | |
194 * | |
195 * @param array $server | |
196 * @return array | |
197 */ | |
198 public static function marshalHeaders(array $server) | |
199 { | |
200 $headers = []; | |
201 foreach ($server as $key => $value) { | |
202 // Apache prefixes environment variables with REDIRECT_ | |
203 // if they are added by rewrite rules | |
204 if (strpos($key, 'REDIRECT_') === 0) { | |
205 $key = substr($key, 9); | |
206 | |
207 // We will not overwrite existing variables with the | |
208 // prefixed versions, though | |
209 if (array_key_exists($key, $server)) { | |
210 continue; | |
211 } | |
212 } | |
213 | |
214 if ($value && strpos($key, 'HTTP_') === 0) { | |
215 $name = strtr(strtolower(substr($key, 5)), '_', '-'); | |
216 $headers[$name] = $value; | |
217 continue; | |
218 } | |
219 | |
220 if ($value && strpos($key, 'CONTENT_') === 0) { | |
221 $name = 'content-' . strtolower(substr($key, 8)); | |
222 $headers[$name] = $value; | |
223 continue; | |
224 } | |
225 } | |
226 | |
227 return $headers; | |
228 } | |
229 | |
230 /** | |
231 * Marshal the URI from the $_SERVER array and headers | |
232 * | |
233 * @param array $server | |
234 * @param array $headers | |
235 * @return Uri | |
236 */ | |
237 public static function marshalUriFromServer(array $server, array $headers) | |
238 { | |
239 $uri = new Uri(''); | |
240 | |
241 // URI scheme | |
242 $scheme = 'http'; | |
243 $https = self::get('HTTPS', $server); | |
244 if (($https && 'off' !== $https) | |
245 || self::getHeader('x-forwarded-proto', $headers, false) === 'https' | |
246 ) { | |
247 $scheme = 'https'; | |
248 } | |
249 if (! empty($scheme)) { | |
250 $uri = $uri->withScheme($scheme); | |
251 } | |
252 | |
253 // Set the host | |
254 $accumulator = (object) ['host' => '', 'port' => null]; | |
255 self::marshalHostAndPortFromHeaders($accumulator, $server, $headers); | |
256 $host = $accumulator->host; | |
257 $port = $accumulator->port; | |
258 if (! empty($host)) { | |
259 $uri = $uri->withHost($host); | |
260 if (! empty($port)) { | |
261 $uri = $uri->withPort($port); | |
262 } | |
263 } | |
264 | |
265 // URI path | |
266 $path = self::marshalRequestUri($server); | |
267 $path = self::stripQueryString($path); | |
268 | |
269 // URI query | |
270 $query = ''; | |
271 if (isset($server['QUERY_STRING'])) { | |
272 $query = ltrim($server['QUERY_STRING'], '?'); | |
273 } | |
274 | |
275 // URI fragment | |
276 $fragment = ''; | |
277 if (strpos($path, '#') !== false) { | |
278 list($path, $fragment) = explode('#', $path, 2); | |
279 } | |
280 | |
281 return $uri | |
282 ->withPath($path) | |
283 ->withFragment($fragment) | |
284 ->withQuery($query); | |
285 } | |
286 | |
287 /** | |
288 * Marshal the host and port from HTTP headers and/or the PHP environment | |
289 * | |
290 * @param stdClass $accumulator | |
291 * @param array $server | |
292 * @param array $headers | |
293 */ | |
294 public static function marshalHostAndPortFromHeaders(stdClass $accumulator, array $server, array $headers) | |
295 { | |
296 if (self::getHeader('host', $headers, false)) { | |
297 self::marshalHostAndPortFromHeader($accumulator, self::getHeader('host', $headers)); | |
298 return; | |
299 } | |
300 | |
301 if (! isset($server['SERVER_NAME'])) { | |
302 return; | |
303 } | |
304 | |
305 $accumulator->host = $server['SERVER_NAME']; | |
306 if (isset($server['SERVER_PORT'])) { | |
307 $accumulator->port = (int) $server['SERVER_PORT']; | |
308 } | |
309 | |
310 if (! isset($server['SERVER_ADDR']) || ! preg_match('/^\[[0-9a-fA-F\:]+\]$/', $accumulator->host)) { | |
311 return; | |
312 } | |
313 | |
314 // Misinterpreted IPv6-Address | |
315 // Reported for Safari on Windows | |
316 self::marshalIpv6HostAndPort($accumulator, $server); | |
317 } | |
318 | |
319 /** | |
320 * Detect the base URI for the request | |
321 * | |
322 * Looks at a variety of criteria in order to attempt to autodetect a base | |
323 * URI, including rewrite URIs, proxy URIs, etc. | |
324 * | |
325 * From ZF2's Zend\Http\PhpEnvironment\Request class | |
326 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) | |
327 * @license http://framework.zend.com/license/new-bsd New BSD License | |
328 * | |
329 * @param array $server | |
330 * @return string | |
331 */ | |
332 public static function marshalRequestUri(array $server) | |
333 { | |
334 // IIS7 with URL Rewrite: make sure we get the unencoded url | |
335 // (double slash problem). | |
336 $iisUrlRewritten = self::get('IIS_WasUrlRewritten', $server); | |
337 $unencodedUrl = self::get('UNENCODED_URL', $server, ''); | |
338 if ('1' == $iisUrlRewritten && ! empty($unencodedUrl)) { | |
339 return $unencodedUrl; | |
340 } | |
341 | |
342 $requestUri = self::get('REQUEST_URI', $server); | |
343 | |
344 // Check this first so IIS will catch. | |
345 $httpXRewriteUrl = self::get('HTTP_X_REWRITE_URL', $server); | |
346 if ($httpXRewriteUrl !== null) { | |
347 $requestUri = $httpXRewriteUrl; | |
348 } | |
349 | |
350 // Check for IIS 7.0 or later with ISAPI_Rewrite | |
351 $httpXOriginalUrl = self::get('HTTP_X_ORIGINAL_URL', $server); | |
352 if ($httpXOriginalUrl !== null) { | |
353 $requestUri = $httpXOriginalUrl; | |
354 } | |
355 | |
356 if ($requestUri !== null) { | |
357 return preg_replace('#^[^/:]+://[^/]+#', '', $requestUri); | |
358 } | |
359 | |
360 $origPathInfo = self::get('ORIG_PATH_INFO', $server); | |
361 if (empty($origPathInfo)) { | |
362 return '/'; | |
363 } | |
364 | |
365 return $origPathInfo; | |
366 } | |
367 | |
368 /** | |
369 * Strip the query string from a path | |
370 * | |
371 * @param mixed $path | |
372 * @return string | |
373 */ | |
374 public static function stripQueryString($path) | |
375 { | |
376 if (($qpos = strpos($path, '?')) !== false) { | |
377 return substr($path, 0, $qpos); | |
378 } | |
379 return $path; | |
380 } | |
381 | |
382 /** | |
383 * Marshal the host and port from the request header | |
384 * | |
385 * @param stdClass $accumulator | |
386 * @param string|array $host | |
387 * @return void | |
388 */ | |
389 private static function marshalHostAndPortFromHeader(stdClass $accumulator, $host) | |
390 { | |
391 if (is_array($host)) { | |
392 $host = implode(', ', $host); | |
393 } | |
394 | |
395 $accumulator->host = $host; | |
396 $accumulator->port = null; | |
397 | |
398 // works for regname, IPv4 & IPv6 | |
399 if (preg_match('|\:(\d+)$|', $accumulator->host, $matches)) { | |
400 $accumulator->host = substr($accumulator->host, 0, -1 * (strlen($matches[1]) + 1)); | |
401 $accumulator->port = (int) $matches[1]; | |
402 } | |
403 } | |
404 | |
405 /** | |
406 * Marshal host/port from misinterpreted IPv6 address | |
407 * | |
408 * @param stdClass $accumulator | |
409 * @param array $server | |
410 */ | |
411 private static function marshalIpv6HostAndPort(stdClass $accumulator, array $server) | |
412 { | |
413 $accumulator->host = '[' . $server['SERVER_ADDR'] . ']'; | |
414 $accumulator->port = $accumulator->port ?: 80; | |
415 if ($accumulator->port . ']' === substr($accumulator->host, strrpos($accumulator->host, ':') + 1)) { | |
416 // The last digit of the IPv6-Address has been taken as port | |
417 // Unset the port so the default port can be used | |
418 $accumulator->port = null; | |
419 } | |
420 } | |
421 | |
422 /** | |
423 * Create and return an UploadedFile instance from a $_FILES specification. | |
424 * | |
425 * If the specification represents an array of values, this method will | |
426 * delegate to normalizeNestedFileSpec() and return that return value. | |
427 * | |
428 * @param array $value $_FILES struct | |
429 * @return array|UploadedFileInterface | |
430 */ | |
431 private static function createUploadedFileFromSpec(array $value) | |
432 { | |
433 if (is_array($value['tmp_name'])) { | |
434 return self::normalizeNestedFileSpec($value); | |
435 } | |
436 | |
437 return new UploadedFile( | |
438 $value['tmp_name'], | |
439 $value['size'], | |
440 $value['error'], | |
441 $value['name'], | |
442 $value['type'] | |
443 ); | |
444 } | |
445 | |
446 /** | |
447 * Normalize an array of file specifications. | |
448 * | |
449 * Loops through all nested files and returns a normalized array of | |
450 * UploadedFileInterface instances. | |
451 * | |
452 * @param array $files | |
453 * @return UploadedFileInterface[] | |
454 */ | |
455 private static function normalizeNestedFileSpec(array $files = []) | |
456 { | |
457 $normalizedFiles = []; | |
458 foreach (array_keys($files['tmp_name']) as $key) { | |
459 $spec = [ | |
460 'tmp_name' => $files['tmp_name'][$key], | |
461 'size' => $files['size'][$key], | |
462 'error' => $files['error'][$key], | |
463 'name' => $files['name'][$key], | |
464 'type' => $files['type'][$key], | |
465 ]; | |
466 $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); | |
467 } | |
468 return $normalizedFiles; | |
469 } | |
470 | |
471 /** | |
472 * Return HTTP protocol version (X.Y) | |
473 * | |
474 * @param array $server | |
475 * @return string | |
476 */ | |
477 private static function marshalProtocolVersion(array $server) | |
478 { | |
479 if (! isset($server['SERVER_PROTOCOL'])) { | |
480 return '1.1'; | |
481 } | |
482 | |
483 if (! preg_match('#^(HTTP/)?(?P<version>[1-9]\d*(?:\.\d)?)$#', $server['SERVER_PROTOCOL'], $matches)) { | |
484 throw new UnexpectedValueException(sprintf( | |
485 'Unrecognized protocol version (%s)', | |
486 $server['SERVER_PROTOCOL'] | |
487 )); | |
488 } | |
489 | |
490 return $matches['version']; | |
491 } | |
492 | |
493 /** | |
494 * Parse a cookie header according to RFC 6265. | |
495 * | |
496 * PHP will replace special characters in cookie names, which results in other cookies not being available due to | |
497 * overwriting. Thus, the server request should take the cookies from the request header instead. | |
498 * | |
499 * @param $cookieHeader | |
500 * @return array | |
501 */ | |
502 private static function parseCookieHeader($cookieHeader) | |
503 { | |
504 preg_match_all('( | |
505 (?:^\\n?[ \t]*|;[ ]) | |
506 (?P<name>[!#$%&\'*+-.0-9A-Z^_`a-z|~]+) | |
507 = | |
508 (?P<DQUOTE>"?) | |
509 (?P<value>[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*) | |
510 (?P=DQUOTE) | |
511 (?=\\n?[ \t]*$|;[ ]) | |
512 )x', $cookieHeader, $matches, PREG_SET_ORDER); | |
513 | |
514 $cookies = []; | |
515 | |
516 foreach ($matches as $match) { | |
517 $cookies[$match['name']] = urldecode($match['value']); | |
518 } | |
519 | |
520 return $cookies; | |
521 } | |
522 } |