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