Textpattern | PHP Cross Reference | Content Management Systems |
Description: Inspects the current HTTP request.
1 <?php 2 3 /* 4 * Textpattern Content Management System 5 * http://textpattern.com 6 * 7 * Copyright (C) 2016 The Textpattern Development Team 8 * 9 * This file is part of Textpattern. 10 * 11 * Textpattern is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU General Public License 13 * as published by the Free Software Foundation, version 2. 14 * 15 * Textpattern is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with Textpattern. If not, see <http://www.gnu.org/licenses/>. 22 */ 23 24 /** 25 * Inspects the current HTTP request. 26 * 27 * Handles content negotiations and extracting data from headers safely on 28 * different web servers. 29 * 30 * @since 4.6.0 31 * @package HTTP 32 */ 33 34 namespace Textpattern\Http; 35 36 class Request 37 { 38 /** 39 * Protocol-port map. 40 * 41 * @var array 42 */ 43 44 protected $protocolMap = array( 45 'http' => 80, 46 'https' => 443, 47 ); 48 49 /** 50 * Stores headers. 51 * 52 * @var array 53 */ 54 55 protected $headers; 56 57 /** 58 * Magic quotes GCP. 59 * 60 * @var bool 61 */ 62 63 protected $magicQuotesGpc = false; 64 65 /** 66 * Stores referer. 67 * 68 * @var string 69 */ 70 71 protected $referer; 72 73 /** 74 * Content types accepted by the client. 75 * 76 * @var array 77 */ 78 79 protected $acceptedTypes; 80 81 /** 82 * Formats mapping. 83 * 84 * @var array 85 */ 86 87 protected $acceptsFormats = array( 88 'html' => array('text/html', 'application/xhtml+xml', '*/*'), 89 'txt' => array('text/plain', '*/*'), 90 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript', 'application/ecmascript', 'application/x-ecmascript', '*/*'), 91 'css' => array('text/css', '*/*'), 92 'json' => array('application/json', 'application/x-json', '*/*'), 93 'xml' => array('text/xml', 'application/xml', 'application/x-xml', '*/*'), 94 'rdf' => array('application/rdf+xml', '*/*'), 95 'atom' => array('application/atom+xml', '*/*'), 96 'rss' => array('application/rss+xml', '*/*'), 97 ); 98 99 /** 100 * Raw request data. 101 * 102 * Wraps around PHP's $_SERVER variable. 103 * 104 * @var \Textpattern\Server\Config 105 */ 106 107 protected $request; 108 109 /** 110 * Constructor. 111 * 112 * <code> 113 * echo Txp::get('\Textpattern\Http\Request', new Abc_Custom_Request_Data)->getHostName(); 114 * </code> 115 * 116 * @param \Textpattern\Server\Config|null $request The raw request data, defaults to the current request body 117 */ 118 119 public function __construct(\Textpattern\Server\Config $request = null) 120 { 121 if ($request === null) { 122 $this->request = \Txp::get('\Textpattern\Server\Config'); 123 } else { 124 $this->request = $request; 125 } 126 127 $this->magicQuotesGpc = $this->request->getMagicQuotesGpc(); 128 } 129 130 /** 131 * Checks whether the client accepts a certain response format. 132 * 133 * By default discards formats with quality factors below an arbitrary 134 * threshold as jQuery adds a wildcard content-type with quality of '0.01' 135 * to the 'Accept' header for XHR requests. 136 * 137 * Supplied format of 'html', 'txt', 'js', 'css', 'json', 'xml', 'rdf', 138 * 'atom' or 'rss' is autocompleted and matched againsts multiple valid MIMEs. 139 * 140 * Both of the following will return MIME for JSON if 'json' format is 141 * supported: 142 * 143 * <code> 144 * echo Txp::get('\Textpattern\Http\Request')->getAcceptedType('json'); 145 * echo Txp::get('\Textpattern\Http\Request')->getAcceptedType('application/json'); 146 * </code> 147 * 148 * The method can also be used to check an array of types: 149 * 150 * <code> 151 * echo Txp::get('\Textpattern\Http\Request')->getAcceptedType(array('application/xml', 'application/x-xml')); 152 * </code> 153 * 154 * Stops on first accepted format. 155 * 156 * @param string|array $formats Format to check 157 * @param float $threshold Quality threshold 158 * @return string|bool Supported type, or FALSE if not 159 */ 160 161 public function getAcceptedType($formats, $threshold = 0.1) 162 { 163 if ($this->acceptedTypes === null) { 164 $this->acceptedTypes = $this->getAcceptsMap($this->request->getVariable('HTTP_ACCEPT')); 165 } 166 167 foreach ((array) $formats as $format) { 168 if (isset($this->acceptsFormats[$format])) { 169 $format = $this->acceptsFormats[$format]; 170 } 171 172 foreach ((array) $format as $type) { 173 if (isset($this->acceptedTypes[$type]) && $this->acceptedTypes[$type]['q'] >= $threshold) { 174 return $type; 175 } 176 } 177 } 178 179 return false; 180 } 181 182 /** 183 * Gets accepted language. 184 * 185 * If $languages is NULL, returns client's favoured language. If 186 * string, checks whether the language is supported and 187 * if an array, returns the language that the client favours the most. 188 * 189 * <code> 190 * echo Txp::get('\Textpattern\Http\Request')->getAcceptedLanguage('fi-FI'); 191 * </code> 192 * 193 * The above will return 'fi-FI' as long as the Accept-Language header 194 * contains an indentifier that matches Finnish, such as 'fi-fi', 'fi-Fi' 195 * or 'fi'. 196 * 197 * @param string|array $languages Languages to check 198 * @param float $threshold Quality threshold 199 * @return string|bool Accepted language, or FALSE 200 */ 201 202 public function getAcceptedLanguage($languages = null, $threshold = 0.1) 203 { 204 $accepts = $this->getAcceptsMap($this->request->getVariable('HTTP_ACCEPT_LANGUAGE')); 205 206 if ($languages === null) { 207 $accepts = array_keys($accepts); 208 209 return array_shift($accepts); 210 } 211 212 $top = 0; 213 $acceptedLanguage = false; 214 215 foreach ((array) $languages as $language) { 216 $search = array($language); 217 218 if ($identifiers = \Txp::get('\Textpattern\L10n\Locale')->getLocaleIdentifiers($language)) { 219 $search = array_map('strtolower', array_merge($search, $identifiers)); 220 } 221 222 foreach ($accepts as $accept => $params) { 223 if (in_array(strtolower($accept), $search, true) && $params['q'] >= $threshold && $params['q'] >= $top) { 224 $top = $quality; // FIXME: $quality is made out of thin air. 225 $acceptedLanguage = $language; 226 } 227 } 228 } 229 230 return $acceptedLanguage; 231 } 232 233 /** 234 * Gets accepted encoding. 235 * 236 * Negotiates a common encoding between the client and the server. 237 * 238 * <code> 239 * if (Txp::get('\Textpattern\Http\Request')->getAcceptedEncoding('gzip')) { 240 * echo 'Client accepts gzip.'; 241 * } 242 * </code> 243 * 244 * @param string|array $encodings Encoding 245 * @param float $threshold Quality threshold 246 * @return string|bool Encoding method, or FALSE 247 */ 248 249 public function getAcceptedEncoding($encodings = null, $threshold = 0.1) 250 { 251 $accepts = $this->getAcceptsMap($this->request->getVariable('HTTP_ACCEPT_ENCODING')); 252 253 if ($encodings === null) { 254 $accepts = array_keys($accepts); 255 256 return array_shift($accepts); 257 } 258 259 foreach ((array) $encodings as $encoding) { 260 if (isset($accepts[$encoding]) && $accepts[$encoding]['q'] >= $threshold) { 261 return $encoding; 262 } 263 } 264 265 return false; 266 } 267 268 /** 269 * Gets an absolute URL pointing to the requested document. 270 * 271 * <code> 272 * echo Txp::get('\Textpattern\Http\Request')->getUrl(); 273 * </code> 274 * 275 * The above will return URL pointing to the requested 276 * page, e.g. http://example.test/path/to/subpage. 277 * 278 * @return string The URL 279 */ 280 281 public function getUrl() 282 { 283 $port = ''; 284 285 if (($portNumber = $this->getPort()) !== false && strpos($this->getHost(), ':') === false) { 286 $port = ':'.$portNumber; 287 } 288 289 return $this->getProtocol().'://'.$this->getHost().$port.$this->getUri(); 290 } 291 292 /** 293 * Gets the server hostname. 294 * 295 * <code> 296 * echo Txp::get('\Textpattern\Http\Request')->getHost(); 297 * </code> 298 * 299 * Returns 'example.com' if requesting 300 * http://example.test/path/to/subpage. 301 * 302 * @return string The host 303 */ 304 305 public function getHost() 306 { 307 return (string) $this->request->getVariable('HTTP_HOST'); 308 } 309 310 /** 311 * Gets the port, if not default. 312 * 313 * This method returns FALSE, if the port is the request protocol's default. 314 * Neither '80' or 443 for HTTPS are returned. 315 * 316 * <code> 317 * echo Txp::get('\Textpattern\Http\Request')->getPort(); 318 * </code> 319 * 320 * Returns '8080' if requesting http://example.test:8080/path/to/subpage. 321 * 322 * @return int|bool Port number, or FALSE 323 */ 324 325 public function getPort() 326 { 327 $port = (int) $this->request->getVariable('SERVER_PORT'); 328 $protocol = $this->getProtocol(); 329 330 if ($port && (!isset($this->protocolMap[$protocol]) || $port !== $this->protocolMap[$protocol])) { 331 return $port; 332 } 333 334 return false; 335 } 336 337 /** 338 * Gets the client IP address. 339 * 340 * This method supports proxies and uses 'X_FORWARDED_FOR' HTTP header if 341 * deemed necessary. 342 * 343 * <code> 344 * echo Txp::get('\Textpattern\Http\Request')->getIp(); 345 * </code> 346 * 347 * Returns the IP address the request came from, e.g. '0.0.0.0'. 348 * Can be either IPv6 or IPv4 depending on the request. 349 * 350 * @return string The IP address 351 */ 352 353 public function getIp() 354 { 355 $ip = $this->request->getVariable('REMOTE_ADDR'); 356 $proxy = $this->getHeader('X-Forwarded-For'); 357 358 if ($proxy && ($ip === '127.0.0.1' || $ip === '::1' || $ip === '::ffff:127.0.0.1' || $ip === $this->request->getVariable('SERVER_ADDR'))) { 359 $ips = explode(',', $proxy); 360 $ip = trim($ips[0]); 361 } 362 363 return $ip; 364 } 365 366 /** 367 * Gets client hostname. 368 * 369 * This method resolves client's hostname. It uses Textpattern's visitor 370 * logs as a cache layer. 371 * 372 * <code> 373 * echo Txp::get('\Textpattern\Http\Request')->getRemoteHostname(); 374 * </code> 375 * 376 * @return string|bool The hostname, or FALSE on failure 377 */ 378 379 public function getRemoteHostname() 380 { 381 $ip = $this->getIp(); 382 383 if (($host = safe_field("host", 'txp_log', "ip = '".doSlash($ip)."' LIMIT 1")) !== false) { 384 return $host; 385 } 386 387 if ($host = @gethostbyaddr($ip)) { 388 if ($host !== $ip && @gethostbyname($host) !== $ip) { 389 return $ip; 390 } 391 392 return $host; 393 } 394 395 return false; 396 } 397 398 /** 399 * Gets the request protocol. 400 * 401 * <code> 402 * echo Txp::get('\Textpattern\Http\Request')->getProtocol(); 403 * </code> 404 * 405 * Returns 'https' if requesting https://example.test:8080/path/to/subpage. 406 * 407 * @return string Either 'http' or 'https' 408 */ 409 410 public function getProtocol() 411 { 412 if (($https = $this->request->getVariable('HTTPS')) && $https !== 'off') { 413 return 'https'; 414 } 415 416 if (($https = $this->getHeader('Front-End-Https')) && strtolower($https) === 'on') { 417 return 'https'; 418 } 419 420 if (($https = $this->getHeader('X-Forwarded-Proto')) && strtolower($https) === 'https') { 421 return 'https'; 422 } 423 424 return 'http'; 425 } 426 427 /** 428 * Gets referer. 429 * 430 * Returns referer header if it does not originate from the current 431 * hostname or come from a HTTPS page to a HTTP page. 432 * 433 * <code> 434 * echo Txp::get('\Textpattern\Http\Request')->getReferer(); 435 * </code> 436 * 437 * Returns full URL such as 'http://example.com/referring/page.php?id=12'. 438 * 439 * @return string|bool Referer, or FALSE if not available 440 */ 441 442 public function getReferer() 443 { 444 if ($this->referer === null) { 445 $protocol = $this->referer = false; 446 447 if ($referer = $this->request->getVariable('HTTP_REFERER')) { 448 if (strpos($referer, '://')) { 449 $referer = explode('://', $referer); 450 $protocol = array_shift($referer); 451 $referer = join('://', $referer); 452 } 453 454 if (!$protocol || ($protocol === 'https' && $this->getProtocol() !== 'https://')) { 455 return false; 456 } 457 458 if (preg_match('/^[^\.]*\.?'.preg_quote(preg_replace('/^www\./', '', $this->getHost()), '/').'/i', $referer)) { 459 return false; 460 } 461 462 $this->referer = $protocol.'://'.$referer; 463 } 464 } 465 466 return $this->referer; 467 } 468 469 /** 470 * Gets requested URI. 471 * 472 * <code> 473 * echo Txp::get('\Textpattern\Http\Request')->getUri(); 474 * </code> 475 * 476 * Returns '/some/requested/page?and=query' if requesting 477 * http://example.com/some/requested/page?and=query. 478 * 479 * @return string The URI 480 */ 481 482 public function getUri() 483 { 484 return (string) $this->request->getVariable('REQUEST_URI'); 485 } 486 487 /** 488 * Gets an array map of raw request headers. 489 * 490 * This method is web server agnostic. 491 * 492 * The following: 493 * 494 * <code> 495 * print_r(Txp::get('\Textpattern\Http\Request')->getHeaders()); 496 * </code> 497 * 498 * Returns: 499 * 500 * <code> 501 * Array 502 * ( 503 * [Host] => example.test 504 * [Connection] => keep-alive 505 * [Cache-Control] => max-age=0 506 * [User-Agent] => User-Agent 507 * [Referer] => http://example.test/textpattern/index.php 508 * [Accept-Encoding] => gzip,deflate,sdch 509 * [Accept-Language] => en-US,en;q=0.8,fi;q=0.6 510 * [Cookie] => toggle_show_spam=1 511 * ) 512 * </code> 513 * 514 * @return array An array of HTTP request headers 515 */ 516 517 public function getHeaders() 518 { 519 if ($this->headers !== null) { 520 return $this->headers; 521 } 522 523 if (function_exists('apache_request_headers')) { 524 if ($this->headers = apache_request_headers()) { 525 return $this->headers; 526 } 527 } 528 529 $this->headers = array(); 530 531 foreach ($_SERVER as $name => $value) { 532 if (strpos($name, 'HTTP_') === 0 && is_scalar($value)) { 533 $parts = explode('_', $name); 534 array_shift($parts); 535 536 foreach ($parts as &$part) { 537 $part = ucfirst(strtolower($part)); 538 } 539 540 $this->headers[join('-', $parts)] = (string) $value; 541 } 542 } 543 544 return $this->headers; 545 } 546 547 /** 548 * Gets a raw HTTP request header value. 549 * 550 * <code> 551 * echo Txp::get('\Textpattern\Http\Request')->getHeader('User-Agent'); 552 * </code> 553 * 554 * Will return the client's User-Agent header, if it has any. If the client 555 * didn't send User-Agent, the method returns FALSE. 556 * 557 * @param string $name The header name 558 * @return string|bool The header value, or FALSE on failure 559 */ 560 561 public function getHeader($name) 562 { 563 if ($headers = $this->getHeaders()) { 564 if (isset($headers[$name])) { 565 return $headers[$name]; 566 } 567 } 568 569 return false; 570 } 571 572 /** 573 * Gets an array of HTTP cookies. 574 * 575 * <code> 576 * print_r(Txp::get('\Textpattern\Http\Request')->getHeaders()); 577 * </code> 578 * 579 * Returns: 580 * 581 * <code> 582 * Array( 583 * [foobar] => value 584 * ) 585 * </code> 586 * 587 * Returned cookie values are processed properly for you, and will not 588 * contain runtime quoting slashes or be URL encoded. Just pick and choose. 589 * 590 * @return array An array of cookies 591 */ 592 593 public function getCookies() 594 { 595 $out = array(); 596 597 if ($_COOKIE) { 598 foreach ($_COOKIE as $name => $value) { 599 $out[$name] = $this->getCookie($name); 600 } 601 } 602 603 return $out; 604 } 605 606 /** 607 * Gets a HTTP cookie. 608 * 609 * <code> 610 * echo Txp::get('\Textpattern\Http\Request')->getCookie('foobar'); 611 * </code> 612 * 613 * @param string $name The cookie name 614 * @return string The value 615 */ 616 617 public function getCookie($name) 618 { 619 if (isset($_COOKIE[$name])) { 620 if ($this->magicQuotesGpc) { 621 return doStrip($_COOKIE[$name]); 622 } 623 624 return $_COOKIE[$name]; 625 } 626 627 return ''; 628 } 629 630 /** 631 * Gets a query string. 632 * 633 * <code> 634 * print_r(Txp::get('\Textpattern\Http\Request')->getQuery()); 635 * </code> 636 * 637 * If requesting "?event=article&step=save", the above returns: 638 * 639 * <code> 640 * Array 641 * ( 642 * [event] => article 643 * [step] => save 644 * ) 645 * </code> 646 * 647 * @return array An array of parameters 648 */ 649 650 public function getQuery() 651 { 652 $out = array(); 653 654 if ($_GET) { 655 foreach ($_GET as $name => $value) { 656 $out[$name] = $this->getParam($name); 657 } 658 } 659 660 if ($_POST) { 661 foreach ($_POST as $name => $value) { 662 $out[$name] = $this->getPost($name); 663 } 664 } 665 666 return $out; 667 } 668 669 /** 670 * Gets a HTTP query string parameter. 671 * 672 * @param $name The parameter name 673 * @return mixed 674 */ 675 676 public function getParam($name) 677 { 678 if (isset($_GET[$name])) { 679 $out = $_GET[$name]; 680 681 if ($this->magicQuotesGpc) { 682 $out = doStrip($out); 683 } 684 685 $out = doArray($out, 'deCRLF'); 686 687 return doArray($out, 'deNull'); 688 } 689 690 return $this->getPost($name); 691 } 692 693 /** 694 * Gets a HTTP post parameter. 695 * 696 * @param string $name The parameter name 697 * @return mixed 698 */ 699 700 public function getPost($name) 701 { 702 $out = ''; 703 704 if (isset($_POST[$name])) { 705 $out = $_POST[$name]; 706 707 if ($this->magicQuotesGpc) { 708 $out = doStrip($out); 709 } 710 } 711 712 return doArray($out, 'deNull'); 713 } 714 715 /** 716 * Builds a content-negotiation accepts map from the given value. 717 * 718 * Keys are the accepted type and the value are the params. If client 719 * doesn't specify quality, defaults to 1.0. Values are sorted by the 720 * quality, from the highest to the lowest. 721 * 722 * This method can be used to parse Accept, Accept-Charset, Accept-Encoding 723 * and Accept-Language header values. 724 * 725 * <code> 726 * print_r(Txp::get('\Textpattern\Http\Request')->getAcceptsMap('en-us;q=1.0,en;q=0.9')); 727 * </code> 728 * 729 * Returns: 730 * 731 * <code> 732 * Array 733 * ( 734 * [en-us] => Array 735 * ( 736 * [q] => 1.0 737 * ) 738 * [en] => Array 739 * ( 740 * [q] => 0.9 741 * ) 742 * ) 743 * </code> 744 * 745 * @param string $header The header string 746 * @return array Accepts map 747 */ 748 749 public function getAcceptsMap($header) 750 { 751 $types = explode(',', $header); 752 $accepts = array(); 753 $sort = array(); 754 755 foreach ($types as $type) { 756 if ($type = trim($type)) { 757 if ($parts = explode(';', $type)) { 758 $type = array_shift($parts); 759 760 $params = array( 761 'q' => 1.0, 762 ); 763 764 foreach ($parts as $value) { 765 if (strpos($value, '=') === false) { 766 $params[$value] = true; 767 } else { 768 $value = explode('=', $value); 769 $params[array_shift($value)] = join('=', $value); 770 } 771 } 772 773 $params['q'] = floatval($params['q']); 774 $accepts[$type] = $params; 775 $sort[$type] = $params['q']; 776 } 777 } 778 } 779 780 array_multisort($sort, SORT_DESC, $accepts); 781 782 return $accepts; 783 } 784 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title