bytic /
http
| 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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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 |