1 | <?php |
||||
2 | |||||
3 | namespace Nip\Http\Request; |
||||
4 | |||||
5 | /** |
||||
6 | * Nip Framework |
||||
7 | * |
||||
8 | * @category Nip |
||||
9 | * @copyright 2009 Nip Framework |
||||
10 | * @license http://www.opensource.org/licenses/mit-license.php The MIT License |
||||
11 | * @version SVN: $Id: Http.php 135 2009-05-27 16:48:23Z victor.stanciu $ |
||||
12 | */ |
||||
13 | |||||
14 | class Http |
||||
15 | { |
||||
16 | protected $request; |
||||
17 | |||||
18 | /** |
||||
19 | * @var string |
||||
20 | */ |
||||
21 | protected $pathInfo; |
||||
22 | |||||
23 | /** |
||||
24 | * @var string |
||||
25 | */ |
||||
26 | protected $requestUri; |
||||
27 | /** |
||||
28 | * @var string |
||||
29 | */ |
||||
30 | protected $baseUrl; |
||||
31 | |||||
32 | /** |
||||
33 | * Normalizes a query string. |
||||
34 | * |
||||
35 | * It builds a normalized query string, where keys/value pairs are alphabetized, |
||||
36 | * have consistent escaping and unneeded delimiters are removed. |
||||
37 | * |
||||
38 | * @param string $qs Query string |
||||
39 | * |
||||
40 | * @return string A normalized query string for the Request |
||||
41 | */ |
||||
42 | 1 | public static function normalizeQueryString($qs) |
|||
43 | { |
||||
44 | 1 | if ('' == $qs) { |
|||
45 | return ''; |
||||
46 | } |
||||
47 | 1 | $parts = []; |
|||
48 | 1 | $order = []; |
|||
49 | 1 | foreach (explode('&', $qs) as $param) { |
|||
50 | 1 | if ('' === $param || '=' === $param[0]) { |
|||
51 | // Ignore useless delimiters, e.g. "x=y&". |
||||
52 | // Also ignore pairs with empty key, even if there was a value, e.g. "=value", |
||||
53 | // as such nameless values cannot be retrieved anyway. |
||||
54 | // PHP also does not include them when building _GET. |
||||
55 | continue; |
||||
56 | } |
||||
57 | 1 | $keyValuePair = explode('=', $param, 2); |
|||
58 | // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default |
||||
59 | // (as defined in enctype application/x-www-form-urlencoded). |
||||
60 | // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. |
||||
61 | // This is why we use urldecode and then normalize to |
||||
62 | // RFC 3986 with rawurlencode. |
||||
63 | 1 | $parts[] = isset($keyValuePair[1]) ? |
|||
64 | 1 | rawurlencode(urldecode($keyValuePair[0])) . '=' . rawurlencode(urldecode($keyValuePair[1])) : rawurlencode(urldecode($keyValuePair[0])); |
|||
65 | 1 | $order[] = urldecode($keyValuePair[0]); |
|||
66 | } |
||||
67 | 1 | array_multisort($order, SORT_ASC, $parts); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
68 | |||||
69 | 1 | return implode('&', $parts); |
|||
70 | } |
||||
71 | |||||
72 | /** |
||||
73 | * Generates a normalized URI (URL) for the Request. |
||||
74 | * |
||||
75 | * @return string A normalized URI (URL) for the Request |
||||
76 | * |
||||
77 | * @see getQueryString() |
||||
78 | */ |
||||
79 | 1 | public function getUri() |
|||
80 | { |
||||
81 | 1 | if (null !== $qs = $this->getQueryString()) { |
|||
82 | 1 | $qs = '?' . $qs; |
|||
83 | } |
||||
84 | 1 | return $this->getSchemeAndHttpHost() . $this->getBaseUrl() . $this->getRequest()->getPathInfo() . $qs; |
|||
85 | } |
||||
86 | |||||
87 | /** |
||||
88 | * Generates the normalized query string for the Request. |
||||
89 | * |
||||
90 | * It builds a normalized query string, where keys/value pairs are alphabetized |
||||
91 | * and have consistent escaping. |
||||
92 | * |
||||
93 | * @return string|null A normalized query string for the Request |
||||
94 | */ |
||||
95 | 1 | public function getQueryString() |
|||
96 | { |
||||
97 | 1 | $qs = static::normalizeQueryString($this->getRequest()->server->get('QUERY_STRING')); |
|||
98 | |||||
99 | 1 | return '' === $qs ? null : $qs; |
|||
100 | } |
||||
101 | |||||
102 | /** |
||||
103 | * @return mixed |
||||
104 | */ |
||||
105 | 8 | public function getRequest() |
|||
106 | { |
||||
107 | 8 | return $this->request; |
|||
108 | } |
||||
109 | |||||
110 | /* |
||||
111 | * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) |
||||
112 | * |
||||
113 | * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd). |
||||
114 | * |
||||
115 | * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
||||
116 | */ |
||||
117 | |||||
118 | /** |
||||
119 | * @param mixed $request |
||||
120 | */ |
||||
121 | 8 | public function setRequest($request) |
|||
122 | { |
||||
123 | 8 | $this->request = $request; |
|||
124 | 8 | } |
|||
125 | |||||
126 | /** |
||||
127 | * Gets the scheme and HTTP host. |
||||
128 | * |
||||
129 | * If the URL was called with basic authentication, the user |
||||
130 | * and the password are not added to the generated string. |
||||
131 | * |
||||
132 | * @return string The scheme and HTTP host |
||||
133 | */ |
||||
134 | 8 | public function getSchemeAndHttpHost() |
|||
135 | { |
||||
136 | 8 | return $this->getScheme() . '://' . $this->getHttpHost(); |
|||
137 | } |
||||
138 | |||||
139 | /** |
||||
140 | * Gets the request's scheme. |
||||
141 | * |
||||
142 | * @return string |
||||
143 | */ |
||||
144 | 8 | public function getScheme() |
|||
145 | { |
||||
146 | 8 | return $this->isSecure() ? 'https' : 'http'; |
|||
147 | } |
||||
148 | |||||
149 | /** |
||||
150 | * @return bool |
||||
151 | */ |
||||
152 | 8 | public function isSecure() |
|||
153 | { |
||||
154 | 8 | $https = $this->getRequest()->server->get('HTTPS'); |
|||
155 | |||||
156 | 8 | return !empty($https) && 'off' !== strtolower($https); |
|||
157 | } |
||||
158 | |||||
159 | /** |
||||
160 | * Returns the HTTP host being requested. |
||||
161 | * |
||||
162 | * The port name will be appended to the host if it's non-standard. |
||||
163 | * |
||||
164 | * @return string |
||||
165 | */ |
||||
166 | 8 | public function getHttpHost() |
|||
167 | { |
||||
168 | 8 | $scheme = $this->getScheme(); |
|||
169 | 8 | $port = $this->getPort(); |
|||
170 | 8 | if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) { |
|||
171 | 8 | return $this->getHost(); |
|||
172 | } |
||||
173 | |||||
174 | return $this->getHost() . ':' . $port; |
||||
175 | } |
||||
176 | |||||
177 | /** |
||||
178 | * @return mixed |
||||
179 | */ |
||||
180 | 8 | public function getPort() |
|||
181 | { |
||||
182 | 8 | return $this->getRequest()->server->get('SERVER_PORT'); |
|||
183 | } |
||||
184 | |||||
185 | /** |
||||
186 | * Returns the host name. |
||||
187 | * |
||||
188 | * This method can read the client host name from the "X-Forwarded-Host" header |
||||
189 | * when trusted proxies were set via "setTrustedProxies()". |
||||
190 | * |
||||
191 | * The "X-Forwarded-Host" header must contain the client host name. |
||||
192 | * |
||||
193 | * If your reverse proxy uses a different header name than "X-Forwarded-Host", |
||||
194 | * configure it via "setTrustedHeaderName()" with the "client-host" key. |
||||
195 | * |
||||
196 | * @return string |
||||
197 | * |
||||
198 | * @throws \UnexpectedValueException when the host name is invalid |
||||
199 | */ |
||||
200 | 8 | public function getHost() |
|||
201 | { |
||||
202 | 8 | if (!$host = $this->getRequest()->headers->get('HOST')) { |
|||
203 | if (!$host = $this->getRequest()->server->get('SERVER_NAME')) { |
||||
204 | $host = $this->getRequest()->server->get('SERVER_ADDR', ''); |
||||
205 | } |
||||
206 | } |
||||
207 | // trim and remove port number from host |
||||
208 | // host is lowercase as per RFC 952/2181 |
||||
209 | 8 | $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); |
|||
210 | |||||
211 | // as the host can come from the user (HTTP_HOST and depending on the configuration, |
||||
212 | // SERVER_NAME too can come from the user) |
||||
213 | // check that it does not contain forbidden characters (see RFC 952 and RFC 2181) |
||||
214 | // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names |
||||
215 | 8 | if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) { |
|||
216 | throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $host)); |
||||
217 | } |
||||
218 | |||||
219 | 8 | return $host; |
|||
220 | } |
||||
221 | |||||
222 | /** |
||||
223 | * Returns the root URL from which this request is executed. |
||||
224 | * |
||||
225 | * The base URL never ends with a /. |
||||
226 | * |
||||
227 | * This is similar to getBasePath(), except that it also includes the |
||||
228 | * script filename (e.g. index.php) if one exists. |
||||
229 | * |
||||
230 | * @return string The raw URL (i.e. not urldecoded) |
||||
231 | */ |
||||
232 | 8 | public function getBaseUrl() |
|||
233 | { |
||||
234 | 8 | if (null === $this->baseUrl) { |
|||
235 | 8 | $this->baseUrl = $this->prepareBaseUrl(); |
|||
236 | } |
||||
237 | 8 | return $this->baseUrl; |
|||
238 | } |
||||
239 | |||||
240 | /** |
||||
241 | * Returns the requested URI (path and query string). |
||||
242 | * |
||||
243 | * @return string The raw URI (i.e. not URI decoded) |
||||
244 | */ |
||||
245 | 8 | public function getRequestUri() |
|||
246 | { |
||||
247 | 8 | if (null === $this->requestUri) { |
|||
248 | 8 | $this->requestUri = $this->prepareRequestUri(); |
|||
249 | } |
||||
250 | 8 | return $this->requestUri; |
|||
251 | } |
||||
252 | |||||
253 | /** |
||||
254 | * @return bool|mixed |
||||
255 | */ |
||||
256 | public function getSubdomain() |
||||
257 | { |
||||
258 | $name = $this->getServerName(); |
||||
259 | if ($name) { |
||||
260 | if (substr_count($name, '.') > 1) { |
||||
261 | $parts = explode('.', $name); |
||||
262 | return reset($parts); |
||||
263 | } |
||||
264 | } |
||||
265 | |||||
266 | return false; |
||||
267 | } |
||||
268 | |||||
269 | /** |
||||
270 | * @return mixed |
||||
271 | */ |
||||
272 | public function getServerName() |
||||
273 | { |
||||
274 | return $this->getRequest()->server->get('SERVER_NAME'); |
||||
275 | } |
||||
276 | |||||
277 | /** |
||||
278 | * @return bool|mixed|string |
||||
279 | */ |
||||
280 | public function getRootDomain() |
||||
281 | { |
||||
282 | $name = $this->getServerName(); |
||||
283 | if ($name) { |
||||
284 | if (substr_count($name, '.') > 1) { |
||||
285 | $parts = explode('.', $name); |
||||
286 | array_shift($parts); |
||||
287 | return implode('.', $parts); |
||||
288 | } |
||||
289 | return $name; |
||||
290 | } |
||||
291 | |||||
292 | return false; |
||||
293 | } |
||||
294 | |||||
295 | /* |
||||
296 | * Returns the prefix as encoded in the string when the string starts with |
||||
297 | * the given prefix, false otherwise. |
||||
298 | * |
||||
299 | * @param string $string The urlencoded string |
||||
300 | * @param string $prefix The prefix not encoded |
||||
301 | * |
||||
302 | * @return string|false The prefix as it is encoded in $string, or false |
||||
303 | */ |
||||
304 | |||||
305 | /** |
||||
306 | * @return bool |
||||
307 | */ |
||||
308 | public function isConsole() |
||||
309 | { |
||||
310 | if (php_sapi_name() === 'cli') { |
||||
311 | return true; |
||||
312 | } |
||||
313 | if (php_sapi_name() === 'cgi-fcgi') { |
||||
314 | return true; |
||||
315 | } |
||||
316 | |||||
317 | return false; |
||||
318 | } |
||||
319 | |||||
320 | /** |
||||
321 | * Prepares the base URL. |
||||
322 | * |
||||
323 | * @return string |
||||
324 | */ |
||||
325 | 8 | protected function prepareBaseUrl() |
|||
326 | { |
||||
327 | 8 | $filename = basename($this->getRequest()->server->get('SCRIPT_FILENAME')); |
|||
328 | 8 | if (basename($this->getRequest()->server->get('SCRIPT_NAME')) === $filename) { |
|||
329 | 8 | $baseUrl = $this->getRequest()->server->get('SCRIPT_NAME'); |
|||
330 | } elseif (basename($this->getRequest()->server->get('PHP_SELF')) === $filename) { |
||||
331 | $baseUrl = $this->getRequest()->server->get('PHP_SELF'); |
||||
332 | } elseif (basename($this->getRequest()->server->get('ORIG_SCRIPT_NAME')) === $filename) { |
||||
333 | $baseUrl = $this->getRequest()->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility |
||||
334 | } else { |
||||
335 | // Backtrack up the script_filename to find the portion matching |
||||
336 | // php_self |
||||
337 | $path = $this->getRequest()->server->get('PHP_SELF', ''); |
||||
338 | $file = $this->getRequest()->server->get('SCRIPT_FILENAME', ''); |
||||
339 | $segs = explode('/', trim($file, '/')); |
||||
340 | $segs = array_reverse($segs); |
||||
341 | $index = 0; |
||||
342 | $last = count($segs); |
||||
343 | $baseUrl = ''; |
||||
344 | do { |
||||
345 | $seg = $segs[$index]; |
||||
346 | $baseUrl = '/' . $seg . $baseUrl; |
||||
347 | ++$index; |
||||
348 | } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos); |
||||
349 | } |
||||
350 | // Does the baseUrl have anything in common with the request_uri? |
||||
351 | 8 | $requestUri = $this->getRequestUri(); |
|||
352 | 8 | if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { |
|||
353 | // full $baseUrl matches |
||||
354 | 2 | return $prefix; |
|||
0 ignored issues
–
show
|
|||||
355 | } |
||||
356 | 6 | if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, |
|||
357 | 6 | rtrim(dirname($baseUrl), '/' . DIRECTORY_SEPARATOR) . '/') |
|||
358 | ) { |
||||
359 | // directory portion of $baseUrl matches |
||||
360 | 5 | return rtrim($prefix, '/' . DIRECTORY_SEPARATOR); |
|||
0 ignored issues
–
show
$prefix of type true is incompatible with the type string expected by parameter $string of rtrim() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
361 | } |
||||
362 | 1 | $truncatedRequestUri = $requestUri; |
|||
363 | 1 | if (false !== $pos = strpos($requestUri, '?')) { |
|||
364 | 1 | $truncatedRequestUri = substr($requestUri, 0, $pos); |
|||
365 | } |
||||
366 | 1 | $basename = basename($baseUrl); |
|||
367 | 1 | if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { |
|||
368 | // no match whatsoever; set it blank |
||||
369 | 1 | return ''; |
|||
370 | } |
||||
371 | // If using mod_rewrite or ISAPI_Rewrite strip the script filename |
||||
372 | // out of baseUrl. $pos !== 0 makes sure it is not matching a value |
||||
373 | // from PATH_INFO or QUERY_STRING |
||||
374 | if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && $pos !== 0) { |
||||
375 | $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); |
||||
376 | } |
||||
377 | return rtrim($baseUrl, '/' . DIRECTORY_SEPARATOR); |
||||
378 | } |
||||
379 | |||||
380 | /** |
||||
381 | * @return string |
||||
382 | */ |
||||
383 | 8 | protected function prepareRequestUri() |
|||
384 | { |
||||
385 | 8 | $requestUri = ''; |
|||
386 | 8 | if ($this->getRequest()->headers->has('X_ORIGINAL_URL')) { |
|||
387 | // IIS with Microsoft Rewrite Module |
||||
388 | $requestUri = $this->getRequest()->headers->get('X_ORIGINAL_URL'); |
||||
389 | $this->getRequest()->headers->remove('X_ORIGINAL_URL'); |
||||
390 | $this->getRequest()->server->remove('HTTP_X_ORIGINAL_URL'); |
||||
391 | $this->getRequest()->server->remove('UNENCODED_URL'); |
||||
392 | $this->getRequest()->server->remove('IIS_WasUrlRewritten'); |
||||
393 | 8 | } elseif ($this->getRequest()->headers->has('X_REWRITE_URL')) { |
|||
394 | // IIS with ISAPI_Rewrite |
||||
395 | $requestUri = $this->getRequest()->headers->get('X_REWRITE_URL'); |
||||
396 | $this->getRequest()->headers->remove('X_REWRITE_URL'); |
||||
397 | 8 | } elseif ($this->getRequest()->server->get('IIS_WasUrlRewritten') == '1' && $this->getRequest()->server->get('UNENCODED_URL') != '') { |
|||
398 | // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) |
||||
399 | $requestUri = $this->getRequest()->server->get('UNENCODED_URL'); |
||||
400 | $this->getRequest()->server->remove('UNENCODED_URL'); |
||||
401 | $this->getRequest()->server->remove('IIS_WasUrlRewritten'); |
||||
402 | 8 | } elseif ($this->getRequest()->server->has('REQUEST_URI')) { |
|||
403 | 8 | $requestUri = $this->getRequest()->server->get('REQUEST_URI'); |
|||
404 | // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path |
||||
405 | 8 | $schemeAndHttpHost = $this->getSchemeAndHttpHost(); |
|||
406 | 8 | if (strpos($requestUri, $schemeAndHttpHost) === 0) { |
|||
407 | 8 | $requestUri = substr($requestUri, strlen($schemeAndHttpHost)); |
|||
408 | } |
||||
409 | } elseif ($this->getRequest()->server->has('ORIG_PATH_INFO')) { |
||||
410 | // IIS 5.0, PHP as CGI |
||||
411 | $requestUri = $this->getRequest()->server->get('ORIG_PATH_INFO'); |
||||
412 | if ('' != $this->getRequest()->server->get('QUERY_STRING')) { |
||||
413 | $requestUri .= '?' . $this->getRequest()->server->get('QUERY_STRING'); |
||||
414 | } |
||||
415 | $this->getRequest()->server->remove('ORIG_PATH_INFO'); |
||||
416 | } |
||||
417 | // normalize the request URI to ease creating sub-requests from this request |
||||
418 | 8 | $this->getRequest()->server->set('REQUEST_URI', $requestUri); |
|||
419 | 8 | return $requestUri; |
|||
420 | } |
||||
421 | |||||
422 | /** |
||||
423 | * @param $string |
||||
424 | * @param $prefix |
||||
425 | * @return bool |
||||
426 | */ |
||||
427 | 7 | private function getUrlencodedPrefix($string, $prefix) |
|||
428 | { |
||||
429 | 7 | if (0 !== strpos(rawurldecode($string), $prefix)) { |
|||
430 | 5 | return false; |
|||
431 | } |
||||
432 | 7 | $len = strlen($prefix); |
|||
433 | 7 | if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) { |
|||
434 | 7 | return $match[0]; |
|||
435 | } |
||||
436 | |||||
437 | return false; |
||||
438 | } |
||||
439 | } |
||||
440 |