3 declare(strict_types=1);
5 namespace Laminas\Diactoros;
7 use function array_change_key_case;
8 use function array_key_exists;
11 use function is_array;
13 use function preg_match;
14 use function preg_replace;
17 use function strtolower;
21 * Marshal a Uri instance based on the values presnt in the $_SERVER array and headers.
23 * @param array $server SAPI parameters
24 * @param array $headers HTTP request headers
26 function marshalUriFromSapi(array $server, array $headers) : Uri
29 * Retrieve a header value from an array of headers using a case-insensitive lookup.
31 * @param array $headers Key/value header pairs
32 * @param mixed $default Default value to return if header not found
35 $getHeaderFromArray = function (string $name, array $headers, $default = null) {
36 $header = strtolower($name);
37 $headers = array_change_key_case($headers, CASE_LOWER);
38 if (array_key_exists($header, $headers)) {
39 $value = is_array($headers[$header]) ? implode(', ', $headers[$header]) : $headers[$header];
47 * Marshal the host and port from HTTP headers and/or the PHP environment.
49 * @return array Array of two items, host and port, in that order (can be
50 * passed to a list() operation).
52 $marshalHostAndPort = function (array $headers, array $server) use ($getHeaderFromArray) : array {
54 * @param string|array $host
55 * @return array Array of two items, host and port, in that order (can be
56 * passed to a list() operation).
58 $marshalHostAndPortFromHeader = function ($host) {
59 if (is_array($host)) {
60 $host = implode(', ', $host);
65 // works for regname, IPv4 & IPv6
66 if (preg_match('|\:(\d+)$|', $host, $matches)) {
67 $host = substr($host, 0, -1 * (strlen($matches[1]) + 1));
68 $port = (int) $matches[1];
71 return [$host, $port];
75 * @return array Array of two items, host and port, in that order (can be
76 * passed to a list() operation).
78 $marshalIpv6HostAndPort = function (array $server, ?int $port) : array {
79 $host = '[' . $server['SERVER_ADDR'] . ']';
81 if ($port . ']' === substr($host, strrpos($host, ':') + 1)) {
82 // The last digit of the IPv6-Address has been taken as port
83 // Unset the port so the default port can be used
86 return [$host, $port];
89 static $defaults = ['', null];
91 $forwardedHost = $getHeaderFromArray('x-forwarded-host', $headers, false);
92 if ($forwardedHost !== false) {
93 return $marshalHostAndPortFromHeader($forwardedHost);
96 $host = $getHeaderFromArray('host', $headers, false);
97 if ($host !== false) {
98 return $marshalHostAndPortFromHeader($host);
101 if (! isset($server['SERVER_NAME'])) {
105 $host = $server['SERVER_NAME'];
106 $port = isset($server['SERVER_PORT']) ? (int) $server['SERVER_PORT'] : null;
108 if (! isset($server['SERVER_ADDR'])
109 || ! preg_match('/^\[[0-9a-fA-F\:]+\]$/', $host)
111 return [$host, $port];
114 // Misinterpreted IPv6-Address
115 // Reported for Safari on Windows
116 return $marshalIpv6HostAndPort($server, $port);
120 * Detect the path for the request
122 * Looks at a variety of criteria in order to attempt to autodetect the base
123 * request path, including:
125 * - IIS7 UrlRewrite environment
129 * From Laminas\Http\PhpEnvironment\Request class
131 $marshalRequestPath = function (array $server) : string {
132 // IIS7 with URL Rewrite: make sure we get the unencoded url
133 // (double slash problem).
134 $iisUrlRewritten = $server['IIS_WasUrlRewritten'] ?? null;
135 $unencodedUrl = $server['UNENCODED_URL'] ?? '';
136 if ('1' === $iisUrlRewritten && ! empty($unencodedUrl)) {
137 return $unencodedUrl;
140 $requestUri = $server['REQUEST_URI'] ?? null;
142 if ($requestUri !== null) {
143 return preg_replace('#^[^/:]+://[^/]+#', '', $requestUri);
146 $origPathInfo = $server['ORIG_PATH_INFO'] ?? null;
147 if (empty($origPathInfo)) {
151 return $origPathInfo;
158 $marshalHttpsValue = function ($https) : bool {
159 if (is_bool($https)) {
163 if (! is_string($https)) {
164 throw new Exception\InvalidArgumentException(sprintf(
165 'SAPI HTTPS value MUST be a string or boolean; received %s',
170 return 'on' === strtolower($https);
172 if (array_key_exists('HTTPS', $server)) {
173 $https = $marshalHttpsValue($server['HTTPS']);
174 } elseif (array_key_exists('https', $server)) {
175 $https = $marshalHttpsValue($server['https']);
181 || strtolower($getHeaderFromArray('x-forwarded-proto', $headers, '')) === 'https'
185 $uri = $uri->withScheme($scheme);
188 [$host, $port] = $marshalHostAndPort($headers, $server);
189 if (! empty($host)) {
190 $uri = $uri->withHost($host);
191 if (! empty($port)) {
192 $uri = $uri->withPort($port);
197 $path = $marshalRequestPath($server);
199 // Strip query string
200 $path = explode('?', $path, 2)[0];
204 if (isset($server['QUERY_STRING'])) {
205 $query = ltrim($server['QUERY_STRING'], '?');
210 if (strpos($path, '#') !== false) {
211 [$path, $fragment] = explode('#', $path, 2);
216 ->withFragment($fragment)