PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/base/lib/flourishlib/fRequest.php

https://bitbucket.org/thanhtungnguyenphp/monitos
PHP | 951 lines | 467 code | 135 blank | 349 comment | 139 complexity | b4d20020569e051e63b23b444fb6315b MD5 | raw file
  1. <?php
  2. /**
  3. * Provides request-related methods
  4. *
  5. * This class is implemented to use the UTF-8 character encoding. Please see
  6. * http://flourishlib.com/docs/UTF-8 for more information.
  7. *
  8. * Please also note that using this class in a PUT or DELETE request will
  9. * cause the php://input stream to be consumed, and thus no longer available.
  10. *
  11. * @copyright Copyright (c) 2007-2011 Will Bond, others
  12. * @author Will Bond [wb] <will@flourishlib.com>
  13. * @author Alex Leeds [al] <alex@kingleeds.com>
  14. * @license http://flourishlib.com/license
  15. *
  16. * @package Flourish
  17. * @link http://flourishlib.com/fRequest
  18. *
  19. * @version 1.0.0b18
  20. * @changes 1.0.0b18 Backwards Compatibility Break - ::getBestAcceptType() and ::getBestAcceptLanguage() now return either `NULL`, `FALSE` or a string instead of `NULL` or a string, both methods are more robust in handling edge cases [wb, 2011-02-06]
  21. * @changes 1.0.0b17 Fixed support for 3+ dimensional input arrays, added a fixed for the PHP DoS float bug #53632, added support for type-casted arrays in ::get() [wb, 2011-01-09]
  22. * @changes 1.0.0b16 Backwards Compatibility Break - changed ::get() to remove binary characters when casting to a `string`, changed `int` and `integer` to cast to a real integer when possible, added new types of `binary` and `integer!` [wb, 2010-11-30]
  23. * @changes 1.0.0b15 Added documentation about `[sub-key]` syntax, added `[sub-key]` support to ::check() [wb, 2010-09-12]
  24. * @changes 1.0.0b14 Rewrote ::set() to not require recursion for array syntax [wb, 2010-09-12]
  25. * @changes 1.0.0b13 Fixed ::set() to work with `PUT` requests [wb, 2010-06-30]
  26. * @changes 1.0.0b12 Fixed a bug with ::getBestAcceptLanguage() returning the second-best language [wb, 2010-05-27]
  27. * @changes 1.0.0b11 Added ::isAjax() [al, 2010-03-15]
  28. * @changes 1.0.0b10 Fixed ::get() to not truncate integers to the 32bit integer limit [wb, 2010-03-05]
  29. * @changes 1.0.0b9 Updated class to use new fSession API [wb, 2009-10-23]
  30. * @changes 1.0.0b8 Casting to an integer or string in ::get() now properly casts when the `$key` isn't present in the request, added support for date, time, timestamp and `?` casts [wb, 2009-08-25]
  31. * @changes 1.0.0b7 Fixed a bug with ::filter() not properly creating new `$_FILES` entries [wb, 2009-07-02]
  32. * @changes 1.0.0b6 ::filter() now works with empty prefixes and filtering the `$_FILES` superglobal has been fixed [wb, 2009-07-02]
  33. * @changes 1.0.0b5 Changed ::filter() so that it can be called multiple times for multi-level filtering [wb, 2009-06-02]
  34. * @changes 1.0.0b4 Added the HTML escaping functions ::encode() and ::prepare() [wb, 2009-05-27]
  35. * @changes 1.0.0b3 Updated class to use new fSession API [wb, 2009-05-08]
  36. * @changes 1.0.0b2 Added ::generateCSRFToken() from fCRUD::generateRequestToken() and ::validateCSRFToken() from fCRUD::validateRequestToken() [wb, 2009-05-08]
  37. * @changes 1.0.0b The initial implementation [wb, 2007-06-14]
  38. */
  39. class fRequest
  40. {
  41. // The following constants allow for nice looking callbacks to static methods
  42. const check = 'fRequest::check';
  43. const encode = 'fRequest::encode';
  44. const filter = 'fRequest::filter';
  45. const generateCSRFToken = 'fRequest::generateCSRFToken';
  46. const get = 'fRequest::get';
  47. const getAcceptLanguages = 'fRequest::getAcceptLanguages';
  48. const getAcceptTypes = 'fRequest::getAcceptTypes';
  49. const getBestAcceptLanguage = 'fRequest::getBestAcceptLanguage';
  50. const getBestAcceptType = 'fRequest::getBestAcceptType';
  51. const getValid = 'fRequest::getValid';
  52. const isAjax = 'fRequest::isAjax';
  53. const isDelete = 'fRequest::isDelete';
  54. const isGet = 'fRequest::isGet';
  55. const isPost = 'fRequest::isPost';
  56. const isPut = 'fRequest::isPut';
  57. const overrideAction = 'fRequest::overrideAction';
  58. const prepare = 'fRequest::prepare';
  59. const reset = 'fRequest::reset';
  60. const set = 'fRequest::set';
  61. const unfilter = 'fRequest::unfilter';
  62. const validateCSRFToken = 'fRequest::validateCSRFToken';
  63. /**
  64. * A backup copy of `$_FILES` for ::unfilter()
  65. *
  66. * @var array
  67. */
  68. static private $backup_files = array();
  69. /**
  70. * A backup copy of `$_GET` for ::unfilter()
  71. *
  72. * @var array
  73. */
  74. static private $backup_get = array();
  75. /**
  76. * A backup copy of `$_POST` for unfilter()
  77. *
  78. * @var array
  79. */
  80. static private $backup_post = array();
  81. /**
  82. * A backup copy of the local `PUT`/`DELETE` post data for ::unfilter()
  83. *
  84. * @var array
  85. */
  86. static private $backup_put_delete = array();
  87. /**
  88. * The key/value pairs from the post data of a `PUT`/`DELETE` request
  89. *
  90. * @var array
  91. */
  92. static private $put_delete = NULL;
  93. /**
  94. * Recursively handles casting values
  95. *
  96. * @param string|array $value The value to be casted
  97. * @param string $cast_to The data type to cast to
  98. * @param integer $level The nesting level of the call
  99. * @return mixed The casted `$value`
  100. */
  101. static private function cast($value, $cast_to, $level=0)
  102. {
  103. $level++;
  104. $strict_array = substr($cast_to, -2) == '[]';
  105. $array_type = $cast_to == 'array' || $strict_array;
  106. if ($level == 1 && $array_type) {
  107. if (is_string($value) && strpos($value, ',') !== FALSE) {
  108. $value = explode(',', $value);
  109. } elseif ($value === NULL || $value === '') {
  110. $value = array();
  111. } else {
  112. settype($value, 'array');
  113. }
  114. }
  115. // Iterate through array values and cast them individually
  116. if (is_array($value) && ($cast_to == 'array' || $cast_to === NULL || ($strict_array && $level == 1))) {
  117. if ($value === array()) {
  118. return $value;
  119. }
  120. foreach ($value as $key => $sub_value) {
  121. $value[$key] = self::cast($sub_value, $cast_to, $level);
  122. }
  123. return $value;
  124. }
  125. if ($array_type) {
  126. $cast_to = preg_replace('#\[\]$#D', '', $cast_to);
  127. }
  128. if ($cast_to == 'array' && $level > 1) {
  129. $cast_to = 'string';
  130. }
  131. if (get_magic_quotes_gpc() && (self::isPost() || self::isGet())) {
  132. $value = self::stripSlashes($value);
  133. }
  134. // This normalizes an empty element to NULL
  135. if ($cast_to === NULL && $value === '') {
  136. $value = NULL;
  137. } elseif ($cast_to == 'date') {
  138. try {
  139. $value = new fDate($value);
  140. } catch (fValidationException $e) {
  141. $value = new fDate();
  142. }
  143. } elseif ($cast_to == 'time') {
  144. try {
  145. $value = new fTime($value);
  146. } catch (fValidationException $e) {
  147. $value = new fTime();
  148. }
  149. } elseif ($cast_to == 'timestamp') {
  150. try {
  151. $value = new fTimestamp($value);
  152. } catch (fValidationException $e) {
  153. $value = new fTimestamp();
  154. }
  155. } elseif ($cast_to == 'bool' || $cast_to == 'boolean') {
  156. if (strtolower($value) == 'f' || strtolower($value) == 'false' || strtolower($value) == 'no' || !$value) {
  157. $value = FALSE;
  158. } else {
  159. $value = TRUE;
  160. }
  161. } elseif (($cast_to == 'int' || $cast_to == 'integer') && is_string($value) && preg_match('#^-?\d+$#D', $value)) {
  162. // Only explicitly cast integers than can be represented by a real
  163. // PHP integer to prevent truncation due to 32 bit integer limits
  164. if (strval(intval($value)) == $value) {
  165. $value = (int) $value;
  166. }
  167. // This patches PHP bug #53632 for vulnerable versions of PHP - http://bugs.php.net/bug.php?id=53632
  168. } elseif ($cast_to == 'float' && $value === "2.2250738585072011e-308") {
  169. static $vulnerable_to_53632 = NULL;
  170. if ($vulnerable_to_53632 === NULL) {
  171. $running_version = preg_replace(
  172. '#^(\d+\.\d+\.\d+).*$#D',
  173. '\1',
  174. PHP_VERSION
  175. );
  176. $vulnerable_to_53632 = version_compare($running_version, '5.2.17', '<') || (version_compare($running_version, '5.3.5', '<') && version_compare($running_version, '5.3.0', '>='));
  177. }
  178. if ($vulnerable_to_53632) {
  179. $value = "2.2250738585072012e-308";
  180. }
  181. settype($value, 'float');
  182. } elseif ($cast_to != 'binary' && $cast_to !== NULL) {
  183. $cast_to = str_replace('integer!', 'integer', $cast_to);
  184. settype($value, $cast_to);
  185. }
  186. // Clean values coming in to ensure we don't have invalid UTF-8
  187. if (($cast_to === NULL || $cast_to == 'string' || $cast_to == 'array') && $value !== NULL) {
  188. $value = self::stripLowOrderBytes($value);
  189. $value = fUTF8::clean($value);
  190. }
  191. return $value;
  192. }
  193. /**
  194. * Indicated if the parameter specified is set in the `$_GET` or `$_POST` superglobals or in the post data of a `PUT` or `DELETE` request
  195. *
  196. * @param string $key The key to check - array elements can be checked via `[sub-key]` syntax
  197. * @return boolean If the parameter is set
  198. */
  199. static public function check($key)
  200. {
  201. self::initPutDelete();
  202. $array_dereference = NULL;
  203. if (strpos($key, '[')) {
  204. $bracket_pos = strpos($key, '[');
  205. $array_dereference = substr($key, $bracket_pos);
  206. $key = substr($key, 0, $bracket_pos);
  207. }
  208. if (!isset($_GET[$key]) && !isset($_POST[$key]) && !isset(self::$put_delete[$key])) {
  209. return FALSE;
  210. }
  211. $values = array($_GET, $_POST, self::$put_delete);
  212. if ($array_dereference) {
  213. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  214. $array_keys = array_map('current', $array_keys);
  215. array_unshift($array_keys, $key);
  216. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  217. foreach ($values as &$value) {
  218. if (!is_array($value) || !isset($value[$array_key])) {
  219. $value = NULL;
  220. } else {
  221. $value = $value[$array_key];
  222. }
  223. }
  224. }
  225. $key = end($array_keys);
  226. }
  227. return isset($values[0][$key]) || isset($values[1][$key]) || isset($values[2][$key]);
  228. }
  229. /**
  230. * Gets a value from ::get() and passes it through fHTML::encode()
  231. *
  232. * @param string $key The key to get the value of - array elements can be accessed via `[sub-key]` syntax
  233. * @param string $cast_to Cast the value to this data type
  234. * @param mixed $default_value If the parameter is not set in the `DELETE`/`PUT` post data, `$_POST` or `$_GET`, use this value instead
  235. * @return string The encoded value
  236. */
  237. static public function encode($key, $cast_to=NULL, $default_value=NULL)
  238. {
  239. return fHTML::encode(self::get($key, $cast_to, $default_value));
  240. }
  241. /**
  242. * Parses through `$_FILES`, `$_GET`, `$_POST` and the `PUT`/`DELETE` post data and filters out everything that doesn't match the prefix and key, also removes the prefix from the field name
  243. *
  244. * @internal
  245. *
  246. * @param string $prefix The prefix to filter by
  247. * @param mixed $key If the field is an array, get the value corresponding to this key
  248. * @return void
  249. */
  250. static public function filter($prefix, $key)
  251. {
  252. self::initPutDelete();
  253. $regex = '#^' . preg_quote($prefix, '#') . '#';
  254. $current_backup = sizeof(self::$backup_files);
  255. self::$backup_files[] = $_FILES;
  256. $_FILES = array();
  257. foreach (self::$backup_files[$current_backup] as $field => $value) {
  258. $matches_prefix = !$prefix || ($prefix && strpos($field, $prefix) === 0);
  259. if ($matches_prefix && is_array($value) && isset($value['name'][$key])) {
  260. $new_field = preg_replace($regex, '', $field);
  261. $_FILES[$new_field] = array();
  262. $_FILES[$new_field]['name'] = $value['name'][$key];
  263. $_FILES[$new_field]['type'] = $value['type'][$key];
  264. $_FILES[$new_field]['tmp_name'] = $value['tmp_name'][$key];
  265. $_FILES[$new_field]['error'] = $value['error'][$key];
  266. $_FILES[$new_field]['size'] = $value['size'][$key];
  267. }
  268. }
  269. $globals = array(
  270. 'get' => array('array' => &$_GET, 'backup' => &self::$backup_get),
  271. 'post' => array('array' => &$_POST, 'backup' => &self::$backup_post),
  272. 'put/delete' => array('array' => &self::$put_delete, 'backup' => &self::$backup_put_delete)
  273. );
  274. foreach ($globals as $refs) {
  275. $current_backup = sizeof($refs['backup']);
  276. $refs['backup'][] = $refs['array'];
  277. $refs['array'] = array();
  278. foreach ($refs['backup'][$current_backup] as $field => $value) {
  279. $matches_prefix = !$prefix || ($prefix && strpos($field, $prefix) === 0);
  280. if ($matches_prefix && is_array($value) && isset($value[$key])) {
  281. $new_field = preg_replace($regex, '', $field);
  282. $refs['array'][$new_field] = $value[$key];
  283. }
  284. }
  285. }
  286. }
  287. /**
  288. * Returns a request token that should be placed in each HTML form to prevent [http://en.wikipedia.org/wiki/Cross-site_request_forgery cross-site request forgery]
  289. *
  290. * This method will return a random 15 character string that should be
  291. * placed in a hidden `input` element on every HTML form. When the form
  292. * contents are being processed, the token should be retrieved and passed
  293. * into ::validateCSRFToken().
  294. *
  295. * The value returned by this method is stored in the session and then
  296. * checked by the validate method, which helps prevent cross site request
  297. * forgeries and (naive) automated form submissions.
  298. *
  299. * Tokens generated by this method are single use, so a user must request
  300. * the page that generates the token at least once per submission.
  301. *
  302. * @param string $url The URL to generate a token for, default to the current page
  303. * @return string The token to be submitted with the form
  304. */
  305. static public function generateCSRFToken($url=NULL)
  306. {
  307. if ($url === NULL) {
  308. $url = fURL::get();
  309. }
  310. $token = fCryptography::randomString(16);
  311. fSession::add(__CLASS__ . '::' . $url . '::csrf_tokens', $token);
  312. return $token;
  313. }
  314. /**
  315. * Gets a value from the `DELETE`/`PUT` post data, `$_POST` or `$_GET` superglobals (in that order)
  316. *
  317. * A value that exactly equals `''` and is not cast to a specific type will
  318. * become `NULL`.
  319. *
  320. * Valid `$cast_to` types include:
  321. * - `'string'`
  322. * - `'binary'`
  323. * - `'int'`
  324. * - `'integer'`
  325. * - `'bool'`
  326. * - `'boolean'`
  327. * - `'array'`
  328. * - `'date'`
  329. * - `'time'`
  330. * - `'timestamp'`
  331. *
  332. * It is possible to append a `?` to a data type to return `NULL`
  333. * whenever the `$key` was not specified in the request, or if the value
  334. * was a blank string.
  335. *
  336. * The `array` and unspecified `$cast_to` types allow for multi-dimensional
  337. * arrays of string data. It is possible to cast an input value as a
  338. * single-dimensional array of a specific type by appending `[]` to the
  339. * `$cast_to`.
  340. *
  341. * All `string`, `array` or unspecified `$cast_to` will result in the value(s)
  342. * being interpreted as UTF-8 string and appropriately cleaned of invalid
  343. * byte sequences. Also, all low-byte, non-printable characters will be
  344. * stripped from the value. This includes all bytes less than the value of
  345. * 32 (Space) other than Tab (`\t`), Newline (`\n`) and Cariage Return
  346. * (`\r`).
  347. *
  348. * To preserve low-byte, non-printable characters, or get the raw value
  349. * without cleaning invalid UTF-8 byte sequences, plase use the value of
  350. * `binary` for the `$cast_to` parameter.
  351. *
  352. * Any integers that are beyond the range of 32bit storage will be returned
  353. * as a string. The returned value can be forced to always be a real
  354. * integer, which may cause truncation of the value, by passing `integer!`
  355. * as the `$cast_to`.
  356. *
  357. * @param string $key The key to get the value of - array elements can be accessed via `[sub-key]` syntax
  358. * @param string $cast_to Cast the value to this data type - see method description for details
  359. * @param mixed $default_value If the parameter is not set in the `DELETE`/`PUT` post data, `$_POST` or `$_GET`, use this value instead. This value will get cast if a `$cast_to` is specified.
  360. * @return mixed The value
  361. */
  362. static public function get($key, $cast_to=NULL, $default_value=NULL)
  363. {
  364. self::initPutDelete();
  365. $value = $default_value;
  366. $array_dereference = NULL;
  367. if (strpos($key, '[')) {
  368. $bracket_pos = strpos($key, '[');
  369. $array_dereference = substr($key, $bracket_pos);
  370. $key = substr($key, 0, $bracket_pos);
  371. }
  372. if (isset(self::$put_delete[$key])) {
  373. $value = self::$put_delete[$key];
  374. } elseif (isset($_POST[$key])) {
  375. $value = $_POST[$key];
  376. } elseif (isset($_GET[$key])) {
  377. $value = $_GET[$key];
  378. }
  379. if ($array_dereference) {
  380. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  381. $array_keys = array_map('current', $array_keys);
  382. foreach ($array_keys as $array_key) {
  383. if (!is_array($value) || !isset($value[$array_key])) {
  384. $value = $default_value;
  385. break;
  386. }
  387. $value = $value[$array_key];
  388. }
  389. }
  390. // This allows for data_type? casts to allow NULL through
  391. if ($cast_to !== NULL && substr($cast_to, -1) == '?') {
  392. if ($value === NULL || $value === '') {
  393. return NULL;
  394. }
  395. $cast_to = substr($cast_to, 0, -1);
  396. }
  397. return self::cast($value, $cast_to);
  398. }
  399. /**
  400. * Returns the HTTP `Accept-Language`s sorted by their `q` values (from high to low)
  401. *
  402. * @return array An associative array of `{language} => {q value}` sorted (in a stable-fashion) from highest to lowest `q` - if no header was sent, an empty array will be returned
  403. */
  404. static public function getAcceptLanguages()
  405. {
  406. return self::processAcceptHeader('HTTP_ACCEPT_LANGUAGE');
  407. }
  408. /**
  409. * Returns the HTTP `Accept` types sorted by their `q` values (from high to low)
  410. *
  411. * @return array An associative array of `{type} => {q value}` sorted (in a stable-fashion) from highest to lowest `q` - if no header was sent, an empty array will be returned
  412. */
  413. static public function getAcceptTypes()
  414. {
  415. return self::processAcceptHeader('HTTP_ACCEPT');
  416. }
  417. /**
  418. * Returns the best HTTP `Accept-Language` (based on `q` value) - can be filtered to only allow certain languages
  419. *
  420. * Special conditions affecting the return value:
  421. * - If no `$filter` is provided and the client does not send the `Accept-Language` header, `NULL` will be returned
  422. * - If no `$filter` is provided and the client specifies `*` with the highest `q`, `NULL` will be returned
  423. * - If `$filter` contains one or more values, but the `Accept-Language` header does not match any, `FALSE` will be returned
  424. * - If `$filter` contains one or more values, but the client does not send the `Accept-Language` header, the first entry from `$filter` will be returned
  425. * - If `$filter` contains two or more values, and two of the values have the same `q` value, the one listed first in `$filter` will be returned
  426. *
  427. * @param array $filter An array of languages that are valid to return - these should be in the form `{language}-{territory}`, e.g. `en-us`
  428. * @param string |$language A language that is valid to return
  429. * @param string |...
  430. * @return string|NULL|FALSE The best language listed in the `Accept-Language` header - see method description for edge cases
  431. */
  432. static public function getBestAcceptLanguage($filter=array())
  433. {
  434. if (!is_array($filter)) {
  435. $filter = func_get_args();
  436. }
  437. return self::pickBestAcceptItem('HTTP_ACCEPT_LANGUAGE', $filter);
  438. }
  439. /**
  440. * Returns the best HTTP `Accept` type (based on `q` value) - can be filtered to only allow certain types
  441. *
  442. * Special conditions affecting the return value:
  443. * - If no `$filter` is provided and the client does not send the `Accept` header, `NULL` will be returned
  444. * - If no `$filter` is provided and the client specifies `{@*}*` with the highest `q`, `NULL` will be returned
  445. * - If `$filter` contains one or more values, but the `Accept` header does not match any, `FALSE` will be returned
  446. * - If `$filter` contains one or more values, but the client does not send the `Accept` header, the first entry from `$filter` will be returned
  447. * - If `$filter` contains two or more values, and two of the values have the same `q` value, the one listed first in `$filter` will be returned
  448. *
  449. * @param array $filter An array of types that are valid to return
  450. * @param string |$type A type that is valid to return
  451. * @param string |...
  452. * @return string|NULL|FALSE The best type listed in the `Accept` header - see method description for edge cases
  453. */
  454. static public function getBestAcceptType($filter=array())
  455. {
  456. if (!is_array($filter)) {
  457. $filter = func_get_args();
  458. }
  459. return self::pickBestAcceptItem('HTTP_ACCEPT', $filter);
  460. }
  461. /**
  462. * Gets a value from the `DELETE`/`PUT` post data, `$_POST` or `$_GET` superglobals (in that order), restricting to a specific set of values
  463. *
  464. * @param string $key The key to get the value of - array elements can be accessed via `[sub-key]` syntax
  465. * @param array $valid_values The array of values that are permissible, if one is not selected, picks first
  466. * @return mixed The value
  467. */
  468. static public function getValid($key, $valid_values)
  469. {
  470. settype($valid_values, 'array');
  471. $valid_values = array_merge(array_unique($valid_values));
  472. $value = self::get($key);
  473. if (!in_array($value, $valid_values)) {
  474. return $valid_values[0];
  475. }
  476. return $value;
  477. }
  478. /**
  479. * Parses post data for `PUT` and `DELETE` HTTP methods
  480. *
  481. * @return void
  482. */
  483. static private function initPutDelete()
  484. {
  485. if (is_array(self::$put_delete)) {
  486. return;
  487. }
  488. if (self::isPut() || self::isDelete()) {
  489. parse_str(file_get_contents('php://input'), self::$put_delete);
  490. } else {
  491. self::$put_delete = array();
  492. }
  493. }
  494. /**
  495. * Indicates if the URL was accessed via an XMLHttpRequest
  496. *
  497. * @return boolean If the URL was accessed via an XMLHttpRequest
  498. */
  499. static public function isAjax()
  500. {
  501. return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
  502. }
  503. /**
  504. * Indicates if the URL was accessed via the `DELETE` HTTP method
  505. *
  506. * @return boolean If the URL was accessed via the `DELETE` HTTP method
  507. */
  508. static public function isDelete()
  509. {
  510. return strtolower($_SERVER['REQUEST_METHOD']) == 'delete';
  511. }
  512. /**
  513. * Indicates if the URL was accessed via the `GET` HTTP method
  514. *
  515. * @return boolean If the URL was accessed via the `GET` HTTP method
  516. */
  517. static public function isGet()
  518. {
  519. return strtolower($_SERVER['REQUEST_METHOD']) == 'get';
  520. }
  521. /**
  522. * Indicates if the URL was accessed via the `POST` HTTP method
  523. *
  524. * @return boolean If the URL was accessed via the `POST` HTTP method
  525. */
  526. static public function isPost()
  527. {
  528. return strtolower($_SERVER['REQUEST_METHOD']) == 'post';
  529. }
  530. /**
  531. * Indicates if the URL was accessed via the `PUT` HTTP method
  532. *
  533. * @return boolean If the URL was accessed via the `PUT` HTTP method
  534. */
  535. static public function isPut()
  536. {
  537. return strtolower($_SERVER['REQUEST_METHOD']) == 'put';
  538. }
  539. /**
  540. * Overrides the value of `'action'` in the `DELETE`/`PUT` post data, `$_POST` or `$_GET` superglobals based on the `'action::{action_name}'` value
  541. *
  542. * This method is primarily intended to be used for hanlding multiple
  543. * submit buttons.
  544. *
  545. * @param string $redirect The url to redirect to if the action is overriden. `%action%` will be replaced with the overridden action.
  546. * @return void
  547. */
  548. static public function overrideAction($redirect=NULL)
  549. {
  550. self::initPutDelete();
  551. $found = FALSE;
  552. $globals = array(&$_GET, &$_POST, &self::$put_delete);
  553. foreach ($globals as &$global) {
  554. foreach ($global as $key => $value) {
  555. if (substr($key, 0, 8) == 'action::') {
  556. $found = (boolean) $global['action'] = substr($key, 8);
  557. unset($global[$key]);
  558. }
  559. }
  560. }
  561. if ($redirect && $found) {
  562. fURL::redirect(str_replace('%action%', $found, $redirect));
  563. }
  564. }
  565. /**
  566. * Returns the best HTTP `Accept-*` header item match (based on `q` value), optionally filtered by an array of options
  567. *
  568. * @param string $header_name The key in `$_SERVER` that contains the `Accept-*` header to pick the best item from
  569. * @param array $options A list of supported options to pick the best from
  570. * @return string The best accept item, `FALSE` if an options array is specified and none are valid, `NULL` if anything is accepted
  571. */
  572. static private function pickBestAcceptItem($header_name, $options)
  573. {
  574. settype($options, 'array');
  575. if (!isset($_SERVER[$header_name]) || !strlen($_SERVER[$header_name])) {
  576. if (empty($options)) {
  577. return NULL;
  578. }
  579. return reset($options);
  580. }
  581. $items = self::processAcceptHeader($header_name);
  582. reset($items);
  583. if (!$options) {
  584. $result = key($items);
  585. if ($result == '*/*' || $result == '*') {
  586. $result = NULL;
  587. }
  588. return $result;
  589. }
  590. $top_q = 0;
  591. $top_item = FALSE;
  592. foreach ($options as $option) {
  593. foreach ($items as $item => $q) {
  594. if ($q < $top_q) {
  595. continue;
  596. }
  597. // Type matches have /s
  598. if (strpos($item, '/') !== FALSE) {
  599. $regex = '#^' . str_replace('*', '.*', $item) . '$#iD';
  600. // Language matches that don't have a - are a wildcard
  601. } elseif (strpos($item, '-') === FALSE) {
  602. $regex = '#^' . str_replace('*', '.*', $item) . '(-.*)?$#iD';
  603. // Non-wildcard languages are straight-up matches
  604. } else {
  605. $regex = '#^' . str_replace('*', '.*', $item) . '$#iD';
  606. }
  607. if (preg_match($regex, $option) && $top_q < $q) {
  608. $top_q = $q;
  609. $top_item = $option;
  610. continue;
  611. }
  612. }
  613. }
  614. return $top_item;
  615. }
  616. /**
  617. * Gets a value from ::get() and passes it through fHTML::prepare()
  618. *
  619. * @param string $key The key to get the value of - array elements can be accessed via `[sub-key]` syntax
  620. * @param string $cast_to Cast the value to this data type
  621. * @param mixed $default_value If the parameter is not set in the `DELETE`/`PUT` post data, `$_POST` or `$_GET`, use this value instead
  622. * @return string The prepared value
  623. */
  624. static public function prepare($key, $cast_to=NULL, $default_value=NULL)
  625. {
  626. return fHTML::prepare(self::get($key, $cast_to, $default_value));
  627. }
  628. /**
  629. * Returns an array of values from one of the HTTP `Accept-*` headers
  630. *
  631. * @param string $header_name The key in `$_SERVER` to get the header value from
  632. * @return array An associative array of `{value} => {quality}` sorted (in a stable-fashion) from highest to lowest `q` - an empty array is returned if the header is empty
  633. */
  634. static private function processAcceptHeader($header_name)
  635. {
  636. if (!isset($_SERVER[$header_name]) || !strlen($_SERVER[$header_name])) {
  637. return array();
  638. }
  639. $types = explode(',', $_SERVER[$header_name]);
  640. $output = array();
  641. // We use this suffix to force stable sorting with the built-in sort function
  642. $suffix = sizeof($types);
  643. foreach ($types as $type) {
  644. $parts = explode(';', $type);
  645. if (!empty($parts[1]) && preg_match('#^q=(\d(?:\.\d)?)#', $parts[1], $match)) {
  646. $q = number_format((float)$match[1], 5);
  647. } else {
  648. $q = number_format(1.0, 5);
  649. }
  650. $q .= $suffix--;
  651. $output[$parts[0]] = $q;
  652. }
  653. arsort($output, SORT_NUMERIC);
  654. foreach ($output as $type => $q) {
  655. $output[$type] = (float) substr($q, 0, -1);
  656. }
  657. return $output;
  658. }
  659. /**
  660. * Resets the configuration and data of the class
  661. *
  662. * @internal
  663. *
  664. * @return void
  665. */
  666. static public function reset()
  667. {
  668. fSession::clear(__CLASS__ . '::');
  669. self::$backup_files = NULL;
  670. self::$backup_get = NULL;
  671. self::$backup_post = NULL;
  672. self::$backup_put_delete = NULL;
  673. self::$put_delete = NULL;
  674. }
  675. /**
  676. * Sets a value into the appropriate `$_GET` or `$_POST` superglobal, or the local `PUT`/`DELETE` post data based on what HTTP method was used for the request
  677. *
  678. * @param string $key The key to set the value to - array elements can be modified via `[sub-key]` syntax
  679. * @param mixed $value The value to set
  680. * @return void
  681. */
  682. static public function set($key, $value)
  683. {
  684. if (self::isPost()) {
  685. $tip =& $_POST;
  686. } elseif (self::isGet()) {
  687. $tip =& $_GET;
  688. } elseif (self::isDelete() || self::isPut()) {
  689. self::initPutDelete();
  690. $tip =& self::$put_delete;
  691. }
  692. if ($bracket_pos = strpos($key, '[')) {
  693. $array_dereference = substr($key, $bracket_pos);
  694. $key = substr($key, 0, $bracket_pos);
  695. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  696. $array_keys = array_map('current', $array_keys);
  697. array_unshift($array_keys, $key);
  698. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  699. if (!isset($tip[$array_key]) || !is_array($tip[$array_key])) {
  700. $tip[$array_key] = array();
  701. }
  702. $tip =& $tip[$array_key];
  703. }
  704. $tip[end($array_keys)] = $value;
  705. } else {
  706. $tip[$key] = $value;
  707. }
  708. }
  709. /**
  710. * Removes low-order bytes from a value
  711. *
  712. * @param string|array $value The value to strip
  713. * @return string|array The `$value` with low-order bytes stripped
  714. */
  715. static private function stripLowOrderBytes($value)
  716. {
  717. if (is_array($value)) {
  718. foreach ($value as $key => $sub_value) {
  719. $value[$key] = self::stripLowOrderBytes($sub_value);
  720. }
  721. return $value;
  722. }
  723. return preg_replace('#[\x00-\x08\x0B\x0C\x0E-\x1F]#', '', $value);
  724. }
  725. /**
  726. * Removes slashes from a value
  727. *
  728. * @param string|array $value The value to strip
  729. * @return string|array The `$value` with slashes stripped
  730. */
  731. static private function stripSlashes($value)
  732. {
  733. if (is_array($value)) {
  734. foreach ($value as $key => $sub_value) {
  735. $value[$key] = self::stripSlashes($sub_value);
  736. }
  737. return $value;
  738. }
  739. return stripslashes($value);
  740. }
  741. /**
  742. * Returns `$_GET`, `$_POST` and `$_FILES` and the `PUT`/`DELTE` post data to the state they were at before ::filter() was called
  743. *
  744. * @internal
  745. *
  746. * @return void
  747. */
  748. static public function unfilter()
  749. {
  750. if (self::$backup_get === array()) {
  751. throw new fProgrammerException(
  752. '%1$s can only be called after %2$s',
  753. __CLASS__ . '::unfilter()',
  754. __CLASS__ . '::filter()'
  755. );
  756. }
  757. $_FILES = array_pop(self::$backup_files);
  758. $_GET = array_pop(self::$backup_get);
  759. $_POST = array_pop(self::$backup_post);
  760. self::$put_delete = array_pop(self::$backup_put_delete);
  761. }
  762. /**
  763. * Validates a request token generated by ::generateCSRFToken()
  764. *
  765. * This method takes a request token and ensures it is valid, otherwise
  766. * it will throw an fValidationException.
  767. *
  768. * @throws fValidationException When the CSRF token specified is invalid
  769. *
  770. * @param string $token The request token to validate
  771. * @param string $url The URL to validate the token for, default to the current page
  772. * @return void
  773. */
  774. static public function validateCSRFToken($token, $url=NULL)
  775. {
  776. if ($url === NULL) {
  777. $url = fURL::get();
  778. }
  779. $key = __CLASS__ . '::' . $url . '::csrf_tokens';
  780. $tokens = fSession::get($key, array());
  781. if (!in_array($token, $tokens)) {
  782. throw new fValidationException(
  783. 'The form submitted could not be validated as authentic, please try submitting it again'
  784. );
  785. }
  786. $tokens = array_diff($tokens, array($token));;
  787. fSession::set($key, $tokens);
  788. }
  789. /**
  790. * Forces use as a static class
  791. *
  792. * @return fRequest
  793. */
  794. private function __construct() { }
  795. }
  796. /**
  797. * Copyright (c) 2007-2011 Will Bond <will@flourishlib.com>, others
  798. *
  799. * Permission is hereby granted, free of charge, to any person obtaining a copy
  800. * of this software and associated documentation files (the "Software"), to deal
  801. * in the Software without restriction, including without limitation the rights
  802. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  803. * copies of the Software, and to permit persons to whom the Software is
  804. * furnished to do so, subject to the following conditions:
  805. *
  806. * The above copyright notice and this permission notice shall be included in
  807. * all copies or substantial portions of the Software.
  808. *
  809. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  810. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  811. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  812. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  813. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  814. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  815. * THE SOFTWARE.
  816. */