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