3 declare(strict_types=1);
5 namespace Laminas\Diactoros;
7 use function array_change_key_case;
8 use function array_key_exists;
14 use function is_array;
16 use function is_string;
18 use function preg_match;
19 use function preg_replace;
21 use function str_contains;
24 use function strtolower;
30 * Marshal a Uri instance based on the values presnt in the $_SERVER array and headers.
32 * @deprecated This function is deprecated as of 2.11.1, and will be removed in
33 * 3.0.0. As of 2.11.1, it is no longer used internally.
35 * @param array $server SAPI parameters
36 * @param array $headers HTTP request headers
38 function marshalUriFromSapi(array $server, array $headers): Uri
41 * Retrieve a header value from an array of headers using a case-insensitive lookup.
43 * @param array $headers Key/value header pairs
44 * @param mixed $default Default value to return if header not found
47 $getHeaderFromArray = static function (string $name, array $headers, $default = null) {
48 $header = strtolower($name);
49 $headers = array_change_key_case($headers, CASE_LOWER);
50 if (array_key_exists($header, $headers)) {
51 return is_array($headers[$header]) ? implode(', ', $headers[$header]) : $headers[$header];
58 * Marshal the host and port from HTTP headers and/or the PHP environment.
60 * @return array Array of two items, host and port, in that order (can be
61 * passed to a list() operation).
63 $marshalHostAndPort = static function (array $headers, array $server) use ($getHeaderFromArray): array {
65 * @param string|array $host
66 * @return array Array of two items, host and port, in that order (can be
67 * passed to a list() operation).
69 $marshalHostAndPortFromHeader = static function ($host) {
70 if (is_array($host)) {
71 $host = implode(', ', $host);
76 // works for regname, IPv4 & IPv6
77 if (preg_match('|\:(\d+)$|', $host, $matches)) {
78 $host = substr($host, 0, -1 * (strlen($matches[1]) + 1));
79 $port = (int) $matches[1];
82 return [$host, $port];
86 * @return array Array of two items, host and port, in that order (can be
87 * passed to a list() operation).
89 $marshalIpv6HostAndPort = static function (array $server, ?int $port): array {
90 $host = '[' . $server['SERVER_ADDR'] . ']';
92 if ($port . ']' === substr($host, strrpos($host, ':') + 1)) {
93 // The last digit of the IPv6-Address has been taken as port
94 // Unset the port so the default port can be used
97 return [$host, $port];
100 static $defaults = ['', null];
102 $forwardedHost = $getHeaderFromArray('x-forwarded-host', $headers, false);
103 if ($forwardedHost !== false) {
104 return $marshalHostAndPortFromHeader($forwardedHost);
107 $host = $getHeaderFromArray('host', $headers, false);
108 if ($host !== false) {
109 return $marshalHostAndPortFromHeader($host);
112 if (! isset($server['SERVER_NAME'])) {
116 $host = $server['SERVER_NAME'];
117 $port = isset($server['SERVER_PORT']) ? (int) $server['SERVER_PORT'] : null;
120 ! isset($server['SERVER_ADDR'])
121 || ! preg_match('/^\[[0-9a-fA-F\:]+\]$/', $host)
123 return [$host, $port];
126 // Misinterpreted IPv6-Address
127 // Reported for Safari on Windows
128 return $marshalIpv6HostAndPort($server, $port);
132 * Detect the path for the request
134 * Looks at a variety of criteria in order to attempt to autodetect the base
135 * request path, including:
137 * - IIS7 UrlRewrite environment
141 * From Laminas\Http\PhpEnvironment\Request class
143 $marshalRequestPath = static function (array $server): string {
144 // IIS7 with URL Rewrite: make sure we get the unencoded url
145 // (double slash problem).
146 $iisUrlRewritten = $server['IIS_WasUrlRewritten'] ?? null;
147 $unencodedUrl = $server['UNENCODED_URL'] ?? '';
148 if ('1' === $iisUrlRewritten && ! empty($unencodedUrl)) {
149 return $unencodedUrl;
152 $requestUri = $server['REQUEST_URI'] ?? null;
154 if ($requestUri !== null) {
155 return preg_replace('#^[^/:]+://[^/]+#', '', $requestUri);
158 $origPathInfo = $server['ORIG_PATH_INFO'] ?? null;
159 if (empty($origPathInfo)) {
163 return $origPathInfo;
170 $marshalHttpsValue = static function ($https): bool {
171 if (is_bool($https)) {
175 if (! is_string($https)) {
176 throw new Exception\InvalidArgumentException(sprintf(
177 'SAPI HTTPS value MUST be a string or boolean; received %s',
182 return 'on' === strtolower($https);
184 if (array_key_exists('HTTPS', $server)) {
185 $https = $marshalHttpsValue($server['HTTPS']);
186 } elseif (array_key_exists('https', $server)) {
187 $https = $marshalHttpsValue($server['https']);
194 || strtolower($getHeaderFromArray('x-forwarded-proto', $headers, '')) === 'https'
198 $uri = $uri->withScheme($scheme);
201 [$host, $port] = $marshalHostAndPort($headers, $server);
202 if (! empty($host)) {
203 $uri = $uri->withHost($host);
204 if (! empty($port)) {
205 $uri = $uri->withPort($port);
210 $path = $marshalRequestPath($server);
212 // Strip query string
213 $path = explode('?', $path, 2)[0];
217 if (isset($server['QUERY_STRING'])) {
218 $query = ltrim($server['QUERY_STRING'], '?');
223 if (str_contains($path, '#')) {
224 $parts = explode('#', $path, 2);
225 assert(count($parts) >= 2);
226 [$path, $fragment] = $parts;
231 ->withFragment($fragment)