$value) { if ($value instanceof UploadedFileInterface) { $normalized[$key] = $value; continue; } if (is_array($value) && isset($value['tmp_name'])) { $normalized[$key] = self::createUploadedFileFromSpec($value); continue; } if (is_array($value)) { $normalized[$key] = self::normalizeFiles($value); continue; } throw new InvalidArgumentException('Invalid value in files specification'); } return $normalized; } /** * Marshal headers from $_SERVER * * @param array $server * @return array */ public static function marshalHeaders(array $server) { $headers = []; foreach ($server as $key => $value) { // Apache prefixes environment variables with REDIRECT_ // if they are added by rewrite rules if (strpos($key, 'REDIRECT_') === 0) { $key = substr($key, 9); // We will not overwrite existing variables with the // prefixed versions, though if (array_key_exists($key, $server)) { continue; } } if ($value && strpos($key, 'HTTP_') === 0) { $name = strtr(strtolower(substr($key, 5)), '_', '-'); $headers[$name] = $value; continue; } if ($value && strpos($key, 'CONTENT_') === 0) { $name = 'content-' . strtolower(substr($key, 8)); $headers[$name] = $value; continue; } } return $headers; } /** * Marshal the URI from the $_SERVER array and headers * * @param array $server * @param array $headers * @return Uri */ public static function marshalUriFromServer(array $server, array $headers) { $uri = new Uri(''); // URI scheme $scheme = 'http'; $https = self::get('HTTPS', $server); if (($https && 'off' !== $https) || self::getHeader('x-forwarded-proto', $headers, false) === 'https' ) { $scheme = 'https'; } if (! empty($scheme)) { $uri = $uri->withScheme($scheme); } // Set the host $accumulator = (object) ['host' => '', 'port' => null]; self::marshalHostAndPortFromHeaders($accumulator, $server, $headers); $host = $accumulator->host; $port = $accumulator->port; if (! empty($host)) { $uri = $uri->withHost($host); if (! empty($port)) { $uri = $uri->withPort($port); } } // URI path $path = self::marshalRequestUri($server); $path = self::stripQueryString($path); // URI query $query = ''; if (isset($server['QUERY_STRING'])) { $query = ltrim($server['QUERY_STRING'], '?'); } // URI fragment $fragment = ''; if (strpos($path, '#') !== false) { list($path, $fragment) = explode('#', $path, 2); } return $uri ->withPath($path) ->withFragment($fragment) ->withQuery($query); } /** * Marshal the host and port from HTTP headers and/or the PHP environment * * @param stdClass $accumulator * @param array $server * @param array $headers */ public static function marshalHostAndPortFromHeaders(stdClass $accumulator, array $server, array $headers) { if (self::getHeader('host', $headers, false)) { self::marshalHostAndPortFromHeader($accumulator, self::getHeader('host', $headers)); return; } if (! isset($server['SERVER_NAME'])) { return; } $accumulator->host = $server['SERVER_NAME']; if (isset($server['SERVER_PORT'])) { $accumulator->port = (int) $server['SERVER_PORT']; } if (! isset($server['SERVER_ADDR']) || ! preg_match('/^\[[0-9a-fA-F\:]+\]$/', $accumulator->host)) { return; } // Misinterpreted IPv6-Address // Reported for Safari on Windows self::marshalIpv6HostAndPort($accumulator, $server); } /** * Detect the base URI for the request * * Looks at a variety of criteria in order to attempt to autodetect a base * URI, including rewrite URIs, proxy URIs, etc. * * From ZF2's Zend\Http\PhpEnvironment\Request class * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * * @param array $server * @return string */ public static function marshalRequestUri(array $server) { // IIS7 with URL Rewrite: make sure we get the unencoded url // (double slash problem). $iisUrlRewritten = self::get('IIS_WasUrlRewritten', $server); $unencodedUrl = self::get('UNENCODED_URL', $server, ''); if ('1' == $iisUrlRewritten && ! empty($unencodedUrl)) { return $unencodedUrl; } $requestUri = self::get('REQUEST_URI', $server); // Check this first so IIS will catch. $httpXRewriteUrl = self::get('HTTP_X_REWRITE_URL', $server); if ($httpXRewriteUrl !== null) { $requestUri = $httpXRewriteUrl; } // Check for IIS 7.0 or later with ISAPI_Rewrite $httpXOriginalUrl = self::get('HTTP_X_ORIGINAL_URL', $server); if ($httpXOriginalUrl !== null) { $requestUri = $httpXOriginalUrl; } if ($requestUri !== null) { return preg_replace('#^[^/:]+://[^/]+#', '', $requestUri); } $origPathInfo = self::get('ORIG_PATH_INFO', $server); if (empty($origPathInfo)) { return '/'; } return $origPathInfo; } /** * Strip the query string from a path * * @param mixed $path * @return string */ public static function stripQueryString($path) { if (($qpos = strpos($path, '?')) !== false) { return substr($path, 0, $qpos); } return $path; } /** * Marshal the host and port from the request header * * @param stdClass $accumulator * @param string|array $host * @return void */ private static function marshalHostAndPortFromHeader(stdClass $accumulator, $host) { if (is_array($host)) { $host = implode(', ', $host); } $accumulator->host = $host; $accumulator->port = null; // works for regname, IPv4 & IPv6 if (preg_match('|\:(\d+)$|', $accumulator->host, $matches)) { $accumulator->host = substr($accumulator->host, 0, -1 * (strlen($matches[1]) + 1)); $accumulator->port = (int) $matches[1]; } } /** * Marshal host/port from misinterpreted IPv6 address * * @param stdClass $accumulator * @param array $server */ private static function marshalIpv6HostAndPort(stdClass $accumulator, array $server) { $accumulator->host = '[' . $server['SERVER_ADDR'] . ']'; $accumulator->port = $accumulator->port ?: 80; if ($accumulator->port . ']' === substr($accumulator->host, strrpos($accumulator->host, ':') + 1)) { // The last digit of the IPv6-Address has been taken as port // Unset the port so the default port can be used $accumulator->port = null; } } /** * Create and return an UploadedFile instance from a $_FILES specification. * * If the specification represents an array of values, this method will * delegate to normalizeNestedFileSpec() and return that return value. * * @param array $value $_FILES struct * @return array|UploadedFileInterface */ private static function createUploadedFileFromSpec(array $value) { if (is_array($value['tmp_name'])) { return self::normalizeNestedFileSpec($value); } return new UploadedFile( $value['tmp_name'], $value['size'], $value['error'], $value['name'], $value['type'] ); } /** * Normalize an array of file specifications. * * Loops through all nested files and returns a normalized array of * UploadedFileInterface instances. * * @param array $files * @return UploadedFileInterface[] */ private static function normalizeNestedFileSpec(array $files = []) { $normalizedFiles = []; foreach (array_keys($files['tmp_name']) as $key) { $spec = [ 'tmp_name' => $files['tmp_name'][$key], 'size' => $files['size'][$key], 'error' => $files['error'][$key], 'name' => $files['name'][$key], 'type' => $files['type'][$key], ]; $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); } return $normalizedFiles; } /** * Return HTTP protocol version (X.Y) * * @param array $server * @return string */ private static function marshalProtocolVersion(array $server) { if (! isset($server['SERVER_PROTOCOL'])) { return '1.1'; } if (! preg_match('#^(HTTP/)?(?P[1-9]\d*(?:\.\d)?)$#', $server['SERVER_PROTOCOL'], $matches)) { throw new UnexpectedValueException(sprintf( 'Unrecognized protocol version (%s)', $server['SERVER_PROTOCOL'] )); } return $matches['version']; } /** * Parse a cookie header according to RFC 6265. * * PHP will replace special characters in cookie names, which results in other cookies not being available due to * overwriting. Thus, the server request should take the cookies from the request header instead. * * @param $cookieHeader * @return array */ private static function parseCookieHeader($cookieHeader) { preg_match_all('( (?:^\\n?[ \t]*|;[ ]) (?P[!#$%&\'*+-.0-9A-Z^_`a-z|~]+) = (?P"?) (?P[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*) (?P=DQUOTE) (?=\\n?[ \t]*$|;[ ]) )x', $cookieHeader, $matches, PREG_SET_ORDER); $cookies = []; foreach ($matches as $match) { $cookies[$match['name']] = urldecode($match['value']); } return $cookies; } }