<?php
/**
* @see https://github.com/zendframework/zend-diactoros for the canonical source repository
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Diactoros;
use function array_change_key_case;
use function array_key_exists;
use function explode;
use function implode;
use function is_array;
use function ltrim;
use function preg_match;
use function preg_replace;
use function strlen;
use function strpos;
use function strtolower;
use function substr;
/**
* Marshal a Uri instance based on the values presnt in the $_SERVER array and headers.
*
* @param array $server SAPI parameters
* @param array $headers HTTP request headers
* @return Uri
*/
function marshalUriFromSapi(array $server, array $headers)
{
/**
* Retrieve a header value from an array of headers using a case-insensitive lookup.
*
* @param string $name
* @param array $headers Key/value header pairs
* @param mixed $default Default value to return if header not found
* @return mixed
*/
$getHeaderFromArray = function ($name, array $headers, $default = null) {
$header = strtolower($name);
$headers = array_change_key_case($headers, CASE_LOWER);
if (array_key_exists($header, $headers)) {
$value = is_array($headers[$header]) ? implode(', ', $headers[$header]) : $headers[$header];
return $value;
}
return $default;
};
/**
* Marshal the host and port from HTTP headers and/or the PHP environment.
*
* @param array $headers
* @param array $server
* @return array Array of two items, host and port, in that order (can be
* passed to a list() operation).
*/
$marshalHostAndPort = function (array $headers, array $server) use ($getHeaderFromArray) {
/**
* @param string|array $host
* @return array Array of two items, host and port, in that order (can be
* passed to a list() operation).
*/
$marshalHostAndPortFromHeader = function ($host) {
if (is_array($host)) {
$host = implode(', ', $host);
}
$port = null;
// works for regname, IPv4 & IPv6
if (preg_match('|\:(\d+)$|', $host, $matches)) {
$host = substr($host, 0, -1 * (strlen($matches[1]) + 1));
$port = (int) $matches[1];
}
return [$host, $port];
};
/**
* @param array $server
* @param string $host
* @param null|int $port
* @return array Array of two items, host and port, in that order (can be
* passed to a list() operation).
*/
$marshalIpv6HostAndPort = function (array $server, $host, $port) {
$host = '[' . $server['SERVER_ADDR'] . ']';
$port = $port ?: 80;
if ($port . ']' === substr($host, strrpos($host, ':') + 1)) {
// The last digit of the IPv6-Address has been taken as port
// Unset the port so the default port can be used
$port = null;
}
return [$host, $port];
};
static $defaults = ['', null];
if ($getHeaderFromArray('host', $headers, false)) {
return $marshalHostAndPortFromHeader($getHeaderFromArray('host', $headers));
}
if (! isset($server['SERVER_NAME'])) {
return $defaults;
}
$host = $server['SERVER_NAME'];
$port = isset($server['SERVER_PORT']) ? (int) $server['SERVER_PORT'] : null;
if (! isset($server['SERVER_ADDR'])
|| ! preg_match('/^\[[0-9a-fA-F\:]+\]$/', $host)
) {
return [$host, $port];
}
// Misinterpreted IPv6-Address
// Reported for Safari on Windows
return $marshalIpv6HostAndPort($server, $host, $port);
};
/**
* Detect the path for the request
*
* Looks at a variety of criteria in order to attempt to autodetect the base
* request path, including:
*
* - IIS7 UrlRewrite environment
* - REQUEST_URI
* - ORIG_PATH_INFO
*
* 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 SAPI environment array (typically `$_SERVER`)
* @return string Discovered path
*/
$marshalRequestPath = function (array $server) {
// IIS7 with URL Rewrite: make sure we get the unencoded url
// (double slash problem).
$iisUrlRewritten = array_key_exists('IIS_WasUrlRewritten', $server) ? $server['IIS_WasUrlRewritten'] : null;
$unencodedUrl = array_key_exists('UNENCODED_URL', $server) ? $server['UNENCODED_URL'] : '';
if ('1' === $iisUrlRewritten && ! empty($unencodedUrl)) {
return $unencodedUrl;
}
$requestUri = array_key_exists('REQUEST_URI', $server) ? $server['REQUEST_URI'] : null;
if ($requestUri !== null) {
return preg_replace('#^[^/:]+://[^/]+#', '', $requestUri);
}
$origPathInfo = array_key_exists('ORIG_PATH_INFO', $server) ? $server['ORIG_PATH_INFO'] : null;
if (empty($origPathInfo)) {
return '/';
}
return $origPathInfo;
};
$uri = new Uri('');
// URI scheme
$scheme = 'http';
if (array_key_exists('HTTPS', $server)) {
$https = $server['HTTPS'];
} elseif (array_key_exists('https', $server)) {
$https = $server['https'];
} else {
$https = false;
}
if (($https && 'on' === strtolower($https))
|| strtolower($getHeaderFromArray('x-forwarded-proto', $headers, false)) === 'https'
) {
$scheme = 'https';
}
$uri = $uri->withScheme($scheme);
// Set the host
list($host, $port) = $marshalHostAndPort($headers, $server);
if (! empty($host)) {
$uri = $uri->withHost($host);
if (! empty($port)) {
$uri = $uri->withPort($port);
}
}
// URI path
$path = $marshalRequestPath($server);
// Strip query string
$path = explode('?', $path, 2)[0];
// 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);
}