PageRenderTime 40ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/Nette/Web/HttpRequest.php

https://github.com/DocX/nette
PHP | 720 lines | 371 code | 159 blank | 190 comment | 80 complexity | ad5d7a2cb3628d2fceecc7b72406ce4d MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Nette Framework
  4. *
  5. * Copyright (c) 2004, 2009 David Grudl (http://davidgrudl.com)
  6. *
  7. * This source file is subject to the "Nette license" that is bundled
  8. * with this package in the file license.txt.
  9. *
  10. * For more information please see http://nettephp.com
  11. *
  12. * @copyright Copyright (c) 2004, 2009 David Grudl
  13. * @license http://nettephp.com/license Nette license
  14. * @link http://nettephp.com
  15. * @category Nette
  16. * @package Nette\Web
  17. */
  18. /*namespace Nette\Web;*/
  19. require_once dirname(__FILE__) . '/../Object.php';
  20. require_once dirname(__FILE__) . '/../Web/IHttpRequest.php';
  21. /**
  22. * HttpRequest provides access scheme for request sent via HTTP.
  23. *
  24. * @author David Grudl
  25. * @copyright Copyright (c) 2004, 2009 David Grudl
  26. * @package Nette\Web
  27. *
  28. * @property UriScript $uri
  29. * @property-read Uri $originalUri
  30. * @property-read array $query
  31. * @property-read array $post
  32. * @property-read string $postRaw
  33. * @property-read array $files
  34. * @property-read array $cookies
  35. * @property-read string $method
  36. * @property-read array $headers
  37. * @property-read Uri $referer
  38. * @property-read string $remoteAddress
  39. * @property-read string $remoteHost
  40. * @property-read bool $secured
  41. */
  42. class HttpRequest extends /*Nette\*/Object implements IHttpRequest
  43. {
  44. /** @var array */
  45. protected $query;
  46. /** @var array */
  47. protected $post;
  48. /** @var array */
  49. protected $files;
  50. /** @var array */
  51. protected $cookies;
  52. /** @var UriScript {@link HttpRequest::getUri()} */
  53. protected $uri;
  54. /** @var Uri {@link HttpRequest::getOriginalUri()} */
  55. protected $originalUri;
  56. /** @var array {@link HttpRequest::getHeaders()} */
  57. protected $headers;
  58. /** @var array */
  59. protected $uriFilter = array(
  60. PHP_URL_PATH => array('#/{2,}#' => '/'), // '%20' => ''
  61. 0 => array(), // '#[.,)]$#' => ''
  62. );
  63. /** @var string */
  64. protected $encoding;
  65. /********************* URI ****************d*g**/
  66. /**
  67. * Returns URL object.
  68. * @return UriScript
  69. */
  70. final public function getUri()
  71. {
  72. if ($this->uri === NULL) {
  73. $this->detectUri();
  74. }
  75. return $this->uri;
  76. }
  77. /**
  78. * Sets URL object.
  79. * @param UriScript
  80. * @return HttpRequest provides a fluent interface
  81. */
  82. public function setUri(UriScript $uri)
  83. {
  84. $this->uri = clone $uri;
  85. $this->query = NULL;
  86. $this->uri->canonicalize();
  87. $this->uri->freeze();
  88. return $this;
  89. }
  90. /**
  91. * Returns URL object.
  92. * @return Uri
  93. */
  94. final public function getOriginalUri()
  95. {
  96. if ($this->originalUri === NULL) {
  97. $this->detectUri();
  98. }
  99. return $this->originalUri;
  100. }
  101. /**
  102. * Sets request URI filter.
  103. * @param string pattern to search for
  104. * @param string string to replace
  105. * @param int PHP_URL_PATH or NULL
  106. * @return void
  107. */
  108. public function addUriFilter($pattern, $replacement = '', $component = NULL)
  109. {
  110. $pattern = '#' . $pattern . '#';
  111. $component = $component === PHP_URL_PATH ? PHP_URL_PATH : 0;
  112. if ($replacement === NULL) {
  113. unset($this->uriFilter[$component][$pattern]);
  114. } else {
  115. $this->uriFilter[$component][$pattern] = $replacement;
  116. }
  117. $this->uri = NULL;
  118. }
  119. /**
  120. * Returns request URI filter.
  121. * @return array
  122. */
  123. final public function getUriFilters()
  124. {
  125. return $this->uriFilter;
  126. }
  127. /**
  128. * Detects uri, base path and script path of the request.
  129. * @return void
  130. */
  131. protected function detectUri()
  132. {
  133. $uri = $this->uri = new UriScript;
  134. $uri->scheme = $this->isSecured() ? 'https' : 'http';
  135. $uri->user = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '';
  136. $uri->password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
  137. // host & port
  138. if (isset($_SERVER['HTTP_HOST'])) {
  139. $pair = explode(':', $_SERVER['HTTP_HOST']);
  140. } elseif (isset($_SERVER['SERVER_NAME'])) {
  141. $pair = explode(':', $_SERVER['SERVER_NAME']);
  142. } else {
  143. $pair = array('');
  144. }
  145. $uri->host = $pair[0];
  146. if (isset($pair[1])) {
  147. $uri->port = (int) $pair[1];
  148. } elseif (isset($_SERVER['SERVER_PORT'])) {
  149. $uri->port = (int) $_SERVER['SERVER_PORT'];
  150. }
  151. // path & query
  152. if (isset($_SERVER['REQUEST_URI'])) { // Apache, IIS 6.0
  153. $requestUri = $_SERVER['REQUEST_URI'];
  154. } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 (PHP as CGI ?)
  155. $requestUri = $_SERVER['ORIG_PATH_INFO'];
  156. if (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] != '') {
  157. $requestUri .= '?' . $_SERVER['QUERY_STRING'];
  158. }
  159. } else {
  160. $requestUri = '';
  161. }
  162. $tmp = explode('?', $requestUri, 2);
  163. $this->originalUri = new Uri($uri);
  164. $this->originalUri->path = $tmp[0];
  165. $this->originalUri->query = isset($tmp[1]) ? $tmp[1] : '';
  166. $this->originalUri->freeze();
  167. $requestUri = preg_replace(array_keys($this->uriFilter[0]), array_values($this->uriFilter[0]), $requestUri);
  168. $tmp = explode('?', $requestUri, 2);
  169. $uri->path = preg_replace(array_keys($this->uriFilter[PHP_URL_PATH]), array_values($this->uriFilter[PHP_URL_PATH]), $tmp[0]);
  170. $uri->path = /*Nette\*/String::fixEncoding($uri->path);
  171. $uri->query = isset($tmp[1]) ? $tmp[1] : '';
  172. // normalized uri
  173. $uri->canonicalize();
  174. // detect base URI-path - inspired by Zend Framework (c) Zend Technologies USA Inc. (http://www.zend.com), new BSD license
  175. $filename = isset($_SERVER['SCRIPT_FILENAME']) ? basename($_SERVER['SCRIPT_FILENAME']) : NULL;
  176. $scriptPath = '';
  177. if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $filename) {
  178. $scriptPath = rtrim($_SERVER['SCRIPT_NAME'], '/');
  179. } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $filename) {
  180. $scriptPath = $_SERVER['PHP_SELF'];
  181. } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename) {
  182. $scriptPath = $_SERVER['ORIG_SCRIPT_NAME']; // 1and1 shared hosting compatibility
  183. } elseif (isset($_SERVER['PHP_SELF'], $_SERVER['SCRIPT_FILENAME'])) {
  184. // Backtrack up the script_filename to find the portion matching php_self
  185. $path = $_SERVER['PHP_SELF'];
  186. $segs = explode('/', trim($_SERVER['SCRIPT_FILENAME'], '/'));
  187. $segs = array_reverse($segs);
  188. $index = 0;
  189. $last = count($segs);
  190. do {
  191. $seg = $segs[$index];
  192. $scriptPath = '/' . $seg . $scriptPath;
  193. $index++;
  194. } while (($last > $index) && (FALSE !== ($pos = strpos($path, $scriptPath))) && (0 != $pos));
  195. }
  196. // Does the scriptPath have anything in common with the request_uri?
  197. if (strncmp($uri->path, $scriptPath, strlen($scriptPath)) === 0) {
  198. // whole $scriptPath in URL
  199. $uri->scriptPath = $scriptPath;
  200. } elseif (strncmp($uri->path, $scriptPath, strrpos($scriptPath, '/') + 1) === 0) {
  201. // directory portion of $scriptPath in URL
  202. $uri->scriptPath = substr($scriptPath, 0, strrpos($scriptPath, '/') + 1);
  203. } elseif (strpos($uri->path, basename($scriptPath)) === FALSE) {
  204. // no match whatsoever; set it blank
  205. $uri->scriptPath = '/';
  206. } elseif ((strlen($uri->path) >= strlen($scriptPath))
  207. && ((FALSE !== ($pos = strpos($uri->path, $scriptPath))) && ($pos !== 0))) {
  208. // If using mod_rewrite or ISAPI_Rewrite strip the script filename
  209. // out of scriptPath. $pos !== 0 makes sure it is not matching a value
  210. // from PATH_INFO or QUERY_STRING
  211. $uri->scriptPath = substr($uri->path, 0, $pos + strlen($scriptPath));
  212. } else {
  213. $uri->scriptPath = $scriptPath;
  214. }
  215. $uri->freeze();
  216. }
  217. /********************* query, post, files & cookies ****************d*g**/
  218. /**
  219. * Returns variable provided to the script via URL query ($_GET).
  220. * If no key is passed, returns the entire array.
  221. * @param string key
  222. * @param mixed default value
  223. * @return mixed
  224. */
  225. final public function getQuery($key = NULL, $default = NULL)
  226. {
  227. if ($this->query === NULL) {
  228. $this->initialize();
  229. }
  230. if (func_num_args() === 0) {
  231. return $this->query;
  232. } elseif (isset($this->query[$key])) {
  233. return $this->query[$key];
  234. } else {
  235. return $default;
  236. }
  237. }
  238. /**
  239. * Returns variable provided to the script via POST method ($_POST).
  240. * If no key is passed, returns the entire array.
  241. * @param string key
  242. * @param mixed default value
  243. * @return mixed
  244. */
  245. final public function getPost($key = NULL, $default = NULL)
  246. {
  247. if ($this->post === NULL) {
  248. $this->initialize();
  249. }
  250. if (func_num_args() === 0) {
  251. return $this->post;
  252. } elseif (isset($this->post[$key])) {
  253. return $this->post[$key];
  254. } else {
  255. return $default;
  256. }
  257. }
  258. /**
  259. * Returns HTTP POST data in raw format (only for "application/x-www-form-urlencoded").
  260. * @return string
  261. */
  262. public function getPostRaw()
  263. {
  264. return file_get_contents('php://input');
  265. }
  266. /**
  267. * Returns uploaded file.
  268. * @param string key (or more keys)
  269. * @return HttpUploadedFile
  270. */
  271. final public function getFile($key)
  272. {
  273. if ($this->files === NULL) {
  274. $this->initialize();
  275. }
  276. $args = func_get_args();
  277. return /*Nette\*/ArrayTools::get($this->files, $args);
  278. }
  279. /**
  280. * Returns uploaded files.
  281. * @return array
  282. */
  283. final public function getFiles()
  284. {
  285. if ($this->files === NULL) {
  286. $this->initialize();
  287. }
  288. return $this->files;
  289. }
  290. /**
  291. * Returns variable provided to the script via HTTP cookies.
  292. * @param string key
  293. * @param mixed default value
  294. * @return mixed
  295. */
  296. final public function getCookie($key, $default = NULL)
  297. {
  298. if ($this->cookies === NULL) {
  299. $this->initialize();
  300. }
  301. if (func_num_args() === 0) {
  302. return $this->cookies;
  303. } elseif (isset($this->cookies[$key])) {
  304. return $this->cookies[$key];
  305. } else {
  306. return $default;
  307. }
  308. }
  309. /**
  310. * Returns variables provided to the script via HTTP cookies.
  311. * @return array
  312. */
  313. final public function getCookies()
  314. {
  315. if ($this->cookies === NULL) {
  316. $this->initialize();
  317. }
  318. return $this->cookies;
  319. }
  320. /**
  321. * Recursively converts and checks encoding.
  322. * @param array
  323. * @param string
  324. * @return HttpRequest provides a fluent interface
  325. */
  326. public function setEncoding($encoding)
  327. {
  328. if (strcasecmp($encoding, $this->encoding)) {
  329. $this->encoding = $encoding;
  330. $this->query = $this->post = $this->cookies = $this->files = NULL; // reinitialization required
  331. }
  332. return $this;
  333. }
  334. /**
  335. * Initializes $this->query, $this->files, $this->cookies and $this->files arrays
  336. * @return void
  337. */
  338. public function initialize()
  339. {
  340. $filter = (!in_array(ini_get("filter.default"), array("", "unsafe_raw")) || ini_get("filter.default_flags"));
  341. parse_str($this->getUri()->query, $this->query);
  342. if (!$this->query) {
  343. $this->query = $filter ? filter_input_array(INPUT_GET, FILTER_UNSAFE_RAW) : (empty($_GET) ? array() : $_GET);
  344. }
  345. $this->post = $filter ? filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW) : (empty($_POST) ? array() : $_POST);
  346. $this->cookies = $filter ? filter_input_array(INPUT_COOKIE, FILTER_UNSAFE_RAW) : (empty($_COOKIE) ? array() : $_COOKIE);
  347. $gpc = (bool) get_magic_quotes_gpc();
  348. $enc = (bool) $this->encoding;
  349. $old = error_reporting(error_reporting() ^ E_NOTICE);
  350. $nonChars = '#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u';
  351. // remove fucking quotes and check (and optionally convert) encoding
  352. if ($gpc || $enc) {
  353. $utf = strcasecmp($this->encoding, 'UTF-8') === 0;
  354. $list = array(& $this->query, & $this->post, & $this->cookies);
  355. while (list($key, $val) = each($list)) {
  356. foreach ($val as $k => $v) {
  357. unset($list[$key][$k]);
  358. if ($gpc) {
  359. $k = stripslashes($k);
  360. }
  361. if ($enc && is_string($k) && (preg_match($nonChars, $k) || preg_last_error())) {
  362. // invalid key -> ignore
  363. } elseif (is_array($v)) {
  364. $list[$key][$k] = $v;
  365. $list[] = & $list[$key][$k];
  366. } else {
  367. if ($gpc && !$filter) {
  368. $v = stripSlashes($v);
  369. }
  370. if ($enc) {
  371. if ($utf) {
  372. $v = /*Nette\*/String::fixEncoding($v);
  373. } else {
  374. if (!/*Nette\*/String::checkEncoding($v)) {
  375. $v = iconv($this->encoding, 'UTF-8//IGNORE', $v);
  376. }
  377. $v = html_entity_decode($v, ENT_NOQUOTES, 'UTF-8');
  378. }
  379. $v = preg_replace($nonChars, '', $v);
  380. }
  381. $list[$key][$k] = $v;
  382. }
  383. }
  384. }
  385. unset($list, $key, $val, $k, $v);
  386. }
  387. // structure $files and create HttpUploadedFile objects
  388. $this->files = array();
  389. $list = array();
  390. if (!empty($_FILES)) {
  391. foreach ($_FILES as $k => $v) {
  392. if ($enc && is_string($k) && (preg_match($nonChars, $k) || preg_last_error())) continue;
  393. $v['@'] = & $this->files[$k];
  394. $list[] = $v;
  395. }
  396. }
  397. while (list(, $v) = each($list)) {
  398. if (!isset($v['name'])) {
  399. continue;
  400. } elseif (!is_array($v['name'])) {
  401. if ($gpc) {
  402. $v['name'] = stripSlashes($v['name']);
  403. }
  404. if ($enc) {
  405. $v['name'] = preg_replace($nonChars, '', /*Nette\*/String::fixEncoding($v['name']));
  406. }
  407. $v['@'] = new HttpUploadedFile($v);
  408. continue;
  409. }
  410. foreach ($v['name'] as $k => $foo) {
  411. if ($enc && is_string($k) && (preg_match($nonChars, $k) || preg_last_error())) continue;
  412. $list[] = array(
  413. 'name' => $v['name'][$k],
  414. 'type' => $v['type'][$k],
  415. 'size' => $v['size'][$k],
  416. 'tmp_name' => $v['tmp_name'][$k],
  417. 'error' => $v['error'][$k],
  418. '@' => & $v['@'][$k],
  419. );
  420. }
  421. }
  422. error_reporting($old);
  423. }
  424. /********************* method & headers ****************d*g**/
  425. /**
  426. * Returns HTTP request method (GET, POST, HEAD, PUT, ...). The method is case-sensitive.
  427. * @return string
  428. */
  429. public function getMethod()
  430. {
  431. return isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : NULL;
  432. }
  433. /**
  434. * Checks if the request method is the given one.
  435. * @param string
  436. * @return bool
  437. */
  438. public function isMethod($method)
  439. {
  440. return isset($_SERVER['REQUEST_METHOD']) ? strcasecmp($_SERVER['REQUEST_METHOD'], $method) === 0 : FALSE;
  441. }
  442. /**
  443. * Checks if the request method is POST.
  444. * @return bool
  445. */
  446. public function isPost()
  447. {
  448. return $this->isMethod('POST');
  449. }
  450. /**
  451. * Return the value of the HTTP header. Pass the header name as the
  452. * plain, HTTP-specified header name (e.g. 'Accept-Encoding').
  453. * @param string
  454. * @param mixed
  455. * @return mixed
  456. */
  457. final public function getHeader($header, $default = NULL)
  458. {
  459. $header = strtolower($header);
  460. $headers = $this->getHeaders();
  461. if (isset($headers[$header])) {
  462. return $headers[$header];
  463. } else {
  464. return $default;
  465. }
  466. }
  467. /**
  468. * Returns all HTTP headers.
  469. * @return array
  470. */
  471. public function getHeaders()
  472. {
  473. if ($this->headers === NULL) {
  474. // lazy initialization
  475. if (function_exists('apache_request_headers')) {
  476. $this->headers = array_change_key_case(apache_request_headers(), CASE_LOWER);
  477. } else {
  478. $this->headers = array();
  479. foreach ($_SERVER as $k => $v) {
  480. if (strncmp($k, 'HTTP_', 5) == 0) {
  481. $this->headers[ strtr(strtolower(substr($k, 5)), '_', '-') ] = $v;
  482. }
  483. }
  484. }
  485. }
  486. return $this->headers;
  487. }
  488. /**
  489. * Returns referrer.
  490. * @return Uri|NULL
  491. */
  492. final public function getReferer()
  493. {
  494. $uri = self::getHeader('referer');
  495. return $uri ? new Uri($uri) : NULL;
  496. }
  497. /**
  498. * Is the request is sent via secure channel (https).
  499. * @return bool
  500. */
  501. public function isSecured()
  502. {
  503. return isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off');
  504. }
  505. /**
  506. * Is AJAX request?
  507. * @return bool
  508. */
  509. public function isAjax()
  510. {
  511. return $this->getHeader('X-Requested-With') === 'XMLHttpRequest';
  512. }
  513. /**
  514. * Returns the IP address of the remote client.
  515. * @return string
  516. */
  517. public function getRemoteAddress()
  518. {
  519. return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : NULL;
  520. }
  521. /**
  522. * Returns the host of the remote client.
  523. * @return string
  524. */
  525. public function getRemoteHost()
  526. {
  527. if (!isset($_SERVER['REMOTE_HOST'])) {
  528. if (!isset($_SERVER['REMOTE_ADDR'])) {
  529. return NULL;
  530. }
  531. $_SERVER['REMOTE_HOST'] = getHostByAddr($_SERVER['REMOTE_ADDR']);
  532. }
  533. return $_SERVER['REMOTE_HOST'];
  534. }
  535. /**
  536. * Parse Accept-Language header and returns prefered language.
  537. * @param array Supported languages
  538. * @return string
  539. */
  540. public function detectLanguage(array $langs)
  541. {
  542. $header = $this->getHeader('accept-language');
  543. if (!$header) return NULL;
  544. $s = strtolower($header); // case insensitive
  545. $s = strtr($s, '_', '-'); // cs_CZ means cs-CZ
  546. rsort($langs); // first more specific
  547. preg_match_all('#(' . implode('|', $langs) . ')(?:-[^\s,;=]+)?\s*(?:;\s*q=([0-9.]+))?#', $s, $matches);
  548. if (!$matches[0]) {
  549. return NULL;
  550. }
  551. $max = 0;
  552. $lang = NULL;
  553. foreach ($matches[1] as $key => $value) {
  554. $q = $matches[2][$key] === '' ? 1.0 : (float) $matches[2][$key];
  555. if ($q > $max) {
  556. $max = $q; $lang = $value;
  557. }
  558. }
  559. return $lang;
  560. }
  561. }