comparison vendor/zendframework/zend-diactoros/src/ServerRequestFactory.php @ 2:5311817fb629

Theme updates
author Chris Cannam
date Tue, 10 Jul 2018 13:19:18 +0000
parents c75dbcec494b
children a9cd425dd02b
comparison
equal deleted inserted replaced
1:0b0e5f3b1e83 2:5311817fb629
1 <?php 1 <?php
2 /** 2 /**
3 * Zend Framework (http://framework.zend.com/) 3 * @see https://github.com/zendframework/zend-diactoros for the canonical source repository
4 * 4 * @copyright Copyright (c) 2015-2017 Zend Technologies USA Inc. (http://www.zend.com)
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 5 * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
8 */ 6 */
9 7
10 namespace Zend\Diactoros; 8 namespace Zend\Diactoros;
11 9
12 use InvalidArgumentException; 10 use InvalidArgumentException;
13 use Psr\Http\Message\UploadedFileInterface; 11 use Psr\Http\Message\UploadedFileInterface;
14 use stdClass; 12 use stdClass;
15 use UnexpectedValueException; 13 use UnexpectedValueException;
14
15 use function array_change_key_case;
16 use function array_key_exists;
17 use function explode;
18 use function implode;
19 use function is_array;
20 use function is_callable;
21 use function strtolower;
22
23 use const CASE_LOWER;
16 24
17 /** 25 /**
18 * Class for marshaling a request object from the current PHP environment. 26 * Class for marshaling a request object from the current PHP environment.
19 * 27 *
20 * Logic largely refactored from the ZF2 Zend\Http\PhpEnvironment\Request class. 28 * Logic largely refactored from the ZF2 Zend\Http\PhpEnvironment\Request class.
54 array $query = null, 62 array $query = null,
55 array $body = null, 63 array $body = null,
56 array $cookies = null, 64 array $cookies = null,
57 array $files = null 65 array $files = null
58 ) { 66 ) {
59 $server = static::normalizeServer($server ?: $_SERVER); 67 $server = normalizeServer(
60 $files = static::normalizeFiles($files ?: $_FILES); 68 $server ?: $_SERVER,
61 $headers = static::marshalHeaders($server); 69 is_callable(self::$apacheRequestHeaders) ? self::$apacheRequestHeaders : null
70 );
71 $files = normalizeUploadedFiles($files ?: $_FILES);
72 $headers = marshalHeadersFromSapi($server);
62 73
63 if (null === $cookies && array_key_exists('cookie', $headers)) { 74 if (null === $cookies && array_key_exists('cookie', $headers)) {
64 $cookies = self::parseCookieHeader($headers['cookie']); 75 $cookies = parseCookieHeader($headers['cookie']);
65 } 76 }
66 77
67 return new ServerRequest( 78 return new ServerRequest(
68 $server, 79 $server,
69 $files, 80 $files,
70 static::marshalUriFromServer($server, $headers), 81 marshalUriFromSapi($server, $headers),
71 static::get('REQUEST_METHOD', $server, 'GET'), 82 marshalMethodFromSapi($server),
72 'php://input', 83 'php://input',
73 $headers, 84 $headers,
74 $cookies ?: $_COOKIE, 85 $cookies ?: $_COOKIE,
75 $query ?: $_GET, 86 $query ?: $_GET,
76 $body ?: $_POST, 87 $body ?: $_POST,
77 static::marshalProtocolVersion($server) 88 marshalProtocolVersionFromSapi($server)
78 ); 89 );
79 } 90 }
80 91
81 /** 92 /**
82 * Access a value in an array, returning a default value if not found 93 * Access a value in an array, returning a default value if not found
83 * 94 *
84 * Will also do a case-insensitive search if a case sensitive search fails. 95 * @deprecated since 1.8.0; no longer used internally.
85 *
86 * @param string $key 96 * @param string $key
87 * @param array $values 97 * @param array $values
88 * @param mixed $default 98 * @param mixed $default
89 * @return mixed 99 * @return mixed
90 */ 100 */
104 * 114 *
105 * If found, it is returned as a string, using comma concatenation. 115 * If found, it is returned as a string, using comma concatenation.
106 * 116 *
107 * If not, the $default is returned. 117 * If not, the $default is returned.
108 * 118 *
119 * @deprecated since 1.8.0; no longer used internally.
109 * @param string $header 120 * @param string $header
110 * @param array $headers 121 * @param array $headers
111 * @param mixed $default 122 * @param mixed $default
112 * @return string 123 * @return string
113 */ 124 */
114 public static function getHeader($header, array $headers, $default = null) 125 public static function getHeader($header, array $headers, $default = null)
115 { 126 {
116 $header = strtolower($header); 127 $header = strtolower($name);
117 $headers = array_change_key_case($headers, CASE_LOWER); 128 $headers = array_change_key_case($headers, CASE_LOWER);
118 if (array_key_exists($header, $headers)) { 129 if (array_key_exists($header, $headers)) {
119 $value = is_array($headers[$header]) ? implode(', ', $headers[$header]) : $headers[$header]; 130 $value = is_array($headers[$header]) ? implode(', ', $headers[$header]) : $headers[$header];
120 return $value; 131 return $value;
121 } 132 }
126 /** 137 /**
127 * Marshal the $_SERVER array 138 * Marshal the $_SERVER array
128 * 139 *
129 * Pre-processes and returns the $_SERVER superglobal. 140 * Pre-processes and returns the $_SERVER superglobal.
130 * 141 *
142 * @deprected since 1.8.0; use Zend\Diactoros\normalizeServer() instead.
131 * @param array $server 143 * @param array $server
132 * @return array 144 * @return array
133 */ 145 */
134 public static function normalizeServer(array $server) 146 public static function normalizeServer(array $server)
135 { 147 {
136 // This seems to be the only way to get the Authorization header on Apache 148 return normalizeServer(
137 $apacheRequestHeaders = self::$apacheRequestHeaders; 149 $server ?: $_SERVER,
138 if (isset($server['HTTP_AUTHORIZATION']) 150 is_callable(self::$apacheRequestHeaders) ? self::$apacheRequestHeaders : null
139 || ! is_callable($apacheRequestHeaders) 151 );
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 } 152 }
157 153
158 /** 154 /**
159 * Normalize uploaded files 155 * Normalize uploaded files
160 * 156 *
161 * Transforms each value into an UploadedFileInterface instance, and ensures 157 * Transforms each value into an UploadedFileInterface instance, and ensures
162 * that nested arrays are normalized. 158 * that nested arrays are normalized.
163 * 159 *
160 * @deprecated since 1.8.0; use \Zend\Diactoros\normalizeUploadedFiles instead.
164 * @param array $files 161 * @param array $files
165 * @return array 162 * @return array
166 * @throws InvalidArgumentException for unrecognized values 163 * @throws InvalidArgumentException for unrecognized values
167 */ 164 */
168 public static function normalizeFiles(array $files) 165 public static function normalizeFiles(array $files)
169 { 166 {
170 $normalized = []; 167 return normalizeUploadedFiles($files);
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 } 168 }
191 169
192 /** 170 /**
193 * Marshal headers from $_SERVER 171 * Marshal headers from $_SERVER
194 * 172 *
173 * @deprecated since 1.8.0; use Zend\Diactoros\marshalHeadersFromSapi().
195 * @param array $server 174 * @param array $server
196 * @return array 175 * @return array
197 */ 176 */
198 public static function marshalHeaders(array $server) 177 public static function marshalHeaders(array $server)
199 { 178 {
200 $headers = []; 179 return marshalHeadersFromSapi($server);
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 } 180 }
229 181
230 /** 182 /**
231 * Marshal the URI from the $_SERVER array and headers 183 * Marshal the URI from the $_SERVER array and headers
232 * 184 *
185 * @deprecated since 1.8.0; use Zend\Diactoros\marshalUriFromSapi() instead.
233 * @param array $server 186 * @param array $server
234 * @param array $headers 187 * @param array $headers
235 * @return Uri 188 * @return Uri
236 */ 189 */
237 public static function marshalUriFromServer(array $server, array $headers) 190 public static function marshalUriFromServer(array $server, array $headers)
238 { 191 {
239 $uri = new Uri(''); 192 return marshalUriFromSapi($server, $headers);
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 } 193 }
286 194
287 /** 195 /**
288 * Marshal the host and port from HTTP headers and/or the PHP environment 196 * Marshal the host and port from HTTP headers and/or the PHP environment
289 * 197 *
198 * @deprecated since 1.8.0; use Zend\Diactoros\marshalUriFromSapi() instead,
199 * and pull the host and port from the Uri instance that function
200 * returns.
290 * @param stdClass $accumulator 201 * @param stdClass $accumulator
291 * @param array $server 202 * @param array $server
292 * @param array $headers 203 * @param array $headers
293 */ 204 */
294 public static function marshalHostAndPortFromHeaders(stdClass $accumulator, array $server, array $headers) 205 public static function marshalHostAndPortFromHeaders(stdClass $accumulator, array $server, array $headers)
295 { 206 {
296 if (self::getHeader('host', $headers, false)) { 207 $uri = marshalUriFromSapi($server, $headers);
297 self::marshalHostAndPortFromHeader($accumulator, self::getHeader('host', $headers)); 208 $accumulator->host = $uri->getHost();
298 return; 209 $accumulator->port = $uri->getPort();
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 } 210 }
318 211
319 /** 212 /**
320 * Detect the base URI for the request 213 * Detect the base URI for the request
321 * 214 *
322 * Looks at a variety of criteria in order to attempt to autodetect a base 215 * Looks at a variety of criteria in order to attempt to autodetect a base
323 * URI, including rewrite URIs, proxy URIs, etc. 216 * URI, including rewrite URIs, proxy URIs, etc.
324 * 217 *
325 * From ZF2's Zend\Http\PhpEnvironment\Request class 218 * @deprecated since 1.8.0; use Zend\Diactoros\marshalUriFromSapi() instead,
326 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 219 * and pull the path from the Uri instance that function returns.
327 * @license http://framework.zend.com/license/new-bsd New BSD License
328 *
329 * @param array $server 220 * @param array $server
330 * @return string 221 * @return string
331 */ 222 */
332 public static function marshalRequestUri(array $server) 223 public static function marshalRequestUri(array $server)
333 { 224 {
334 // IIS7 with URL Rewrite: make sure we get the unencoded url 225 $uri = marshalUriFromSapi($server, []);
335 // (double slash problem). 226 return $uri->getPath();
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 } 227 }
367 228
368 /** 229 /**
369 * Strip the query string from a path 230 * Strip the query string from a path
370 * 231 *
232 * @deprecated since 1.8.0; no longer used internally.
371 * @param mixed $path 233 * @param mixed $path
372 * @return string 234 * @return string
373 */ 235 */
374 public static function stripQueryString($path) 236 public static function stripQueryString($path)
375 { 237 {
376 if (($qpos = strpos($path, '?')) !== false) { 238 return explode('?', $path, 2)[0];
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 } 239 }
522 } 240 }