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