778d31bfb670d59b6404ff9ae52ab7c555693def
[GitHub/WoltLab/WCF.git] /
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Laminas\Diactoros;
6
7 use function array_change_key_case;
8 use function array_key_exists;
9 use function explode;
10 use function implode;
11 use function is_array;
12 use function ltrim;
13 use function preg_match;
14 use function preg_replace;
15 use function strlen;
16 use function strpos;
17 use function strtolower;
18 use function substr;
19
20 /**
21 * Marshal a Uri instance based on the values presnt in the $_SERVER array and headers.
22 *
23 * @param array $server SAPI parameters
24 * @param array $headers HTTP request headers
25 */
26 function marshalUriFromSapi(array $server, array $headers) : Uri
27 {
28 /**
29 * Retrieve a header value from an array of headers using a case-insensitive lookup.
30 *
31 * @param array $headers Key/value header pairs
32 * @param mixed $default Default value to return if header not found
33 * @return mixed
34 */
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];
40 return $value;
41 }
42
43 return $default;
44 };
45
46 /**
47 * Marshal the host and port from HTTP headers and/or the PHP environment.
48 *
49 * @return array Array of two items, host and port, in that order (can be
50 * passed to a list() operation).
51 */
52 $marshalHostAndPort = function (array $headers, array $server) use ($getHeaderFromArray) : array {
53 /**
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).
57 */
58 $marshalHostAndPortFromHeader = function ($host) {
59 if (is_array($host)) {
60 $host = implode(', ', $host);
61 }
62
63 $port = null;
64
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];
69 }
70
71 return [$host, $port];
72 };
73
74 /**
75 * @return array Array of two items, host and port, in that order (can be
76 * passed to a list() operation).
77 */
78 $marshalIpv6HostAndPort = function (array $server, ?int $port) : array {
79 $host = '[' . $server['SERVER_ADDR'] . ']';
80 $port = $port ?: 80;
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
84 $port = null;
85 }
86 return [$host, $port];
87 };
88
89 static $defaults = ['', null];
90
91 $forwardedHost = $getHeaderFromArray('x-forwarded-host', $headers, false);
92 if ($forwardedHost !== false) {
93 return $marshalHostAndPortFromHeader($forwardedHost);
94 }
95
96 $host = $getHeaderFromArray('host', $headers, false);
97 if ($host !== false) {
98 return $marshalHostAndPortFromHeader($host);
99 }
100
101 if (! isset($server['SERVER_NAME'])) {
102 return $defaults;
103 }
104
105 $host = $server['SERVER_NAME'];
106 $port = isset($server['SERVER_PORT']) ? (int) $server['SERVER_PORT'] : null;
107
108 if (! isset($server['SERVER_ADDR'])
109 || ! preg_match('/^\[[0-9a-fA-F\:]+\]$/', $host)
110 ) {
111 return [$host, $port];
112 }
113
114 // Misinterpreted IPv6-Address
115 // Reported for Safari on Windows
116 return $marshalIpv6HostAndPort($server, $port);
117 };
118
119 /**
120 * Detect the path for the request
121 *
122 * Looks at a variety of criteria in order to attempt to autodetect the base
123 * request path, including:
124 *
125 * - IIS7 UrlRewrite environment
126 * - REQUEST_URI
127 * - ORIG_PATH_INFO
128 *
129 * From Laminas\Http\PhpEnvironment\Request class
130 */
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;
138 }
139
140 $requestUri = $server['REQUEST_URI'] ?? null;
141
142 if ($requestUri !== null) {
143 return preg_replace('#^[^/:]+://[^/]+#', '', $requestUri);
144 }
145
146 $origPathInfo = $server['ORIG_PATH_INFO'] ?? null;
147 if (empty($origPathInfo)) {
148 return '/';
149 }
150
151 return $origPathInfo;
152 };
153
154 $uri = new Uri('');
155
156 // URI scheme
157 $scheme = 'http';
158 $marshalHttpsValue = function ($https) : bool {
159 if (is_bool($https)) {
160 return $https;
161 }
162
163 if (! is_string($https)) {
164 throw new Exception\InvalidArgumentException(sprintf(
165 'SAPI HTTPS value MUST be a string or boolean; received %s',
166 gettype($https)
167 ));
168 }
169
170 return 'on' === strtolower($https);
171 };
172 if (array_key_exists('HTTPS', $server)) {
173 $https = $marshalHttpsValue($server['HTTPS']);
174 } elseif (array_key_exists('https', $server)) {
175 $https = $marshalHttpsValue($server['https']);
176 } else {
177 $https = false;
178 }
179
180 if ($https
181 || strtolower($getHeaderFromArray('x-forwarded-proto', $headers, '')) === 'https'
182 ) {
183 $scheme = 'https';
184 }
185 $uri = $uri->withScheme($scheme);
186
187 // Set the host
188 [$host, $port] = $marshalHostAndPort($headers, $server);
189 if (! empty($host)) {
190 $uri = $uri->withHost($host);
191 if (! empty($port)) {
192 $uri = $uri->withPort($port);
193 }
194 }
195
196 // URI path
197 $path = $marshalRequestPath($server);
198
199 // Strip query string
200 $path = explode('?', $path, 2)[0];
201
202 // URI query
203 $query = '';
204 if (isset($server['QUERY_STRING'])) {
205 $query = ltrim($server['QUERY_STRING'], '?');
206 }
207
208 // URI fragment
209 $fragment = '';
210 if (strpos($path, '#') !== false) {
211 [$path, $fragment] = explode('#', $path, 2);
212 }
213
214 return $uri
215 ->withPath($path)
216 ->withFragment($fragment)
217 ->withQuery($query);
218 }