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