PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/fRequest.php

https://bitbucket.org/ZilIsiltk/flourish
PHP | 790 lines | 386 code | 118 blank | 286 comment | 101 complexity | 37ce432b4660d2cccb86461a88693d50 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-2010 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.0b15
  20. * @changes 1.0.0b15 Added documentation about `[sub-key]` syntax, added `[sub-key]` support to ::check() [wb, 2010-09-12]
  21. * @changes 1.0.0b14 Rewrote ::set() to not require recursion for array syntax [wb, 2010-09-12]
  22. * @changes 1.0.0b13 Fixed ::set() to work with `PUT` requests [wb, 2010-06-30]
  23. * @changes 1.0.0b12 Fixed a bug with ::getBestAcceptLanguage() returning the second-best language [wb, 2010-05-27]
  24. * @changes 1.0.0b11 Added ::isAjax() [al, 2010-03-15]
  25. * @changes 1.0.0b10 Fixed ::get() to not truncate integers to the 32bit integer limit [wb, 2010-03-05]
  26. * @changes 1.0.0b9 Updated class to use new fSession API [wb, 2009-10-23]
  27. * @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]
  28. * @changes 1.0.0b7 Fixed a bug with ::filter() not properly creating new `$_FILES` entries [wb, 2009-07-02]
  29. * @changes 1.0.0b6 ::filter() now works with empty prefixes and filtering the `$_FILES` superglobal has been fixed [wb, 2009-07-02]
  30. * @changes 1.0.0b5 Changed ::filter() so that it can be called multiple times for multi-level filtering [wb, 2009-06-02]
  31. * @changes 1.0.0b4 Added the HTML escaping functions ::encode() and ::prepare() [wb, 2009-05-27]
  32. * @changes 1.0.0b3 Updated class to use new fSession API [wb, 2009-05-08]
  33. * @changes 1.0.0b2 Added ::generateCSRFToken() from fCRUD::generateRequestToken() and ::validateCSRFToken() from fCRUD::validateRequestToken() [wb, 2009-05-08]
  34. * @changes 1.0.0b The initial implementation [wb, 2007-06-14]
  35. */
  36. class fRequest
  37. {
  38. // The following constants allow for nice looking callbacks to static methods
  39. const check = 'fRequest::check';
  40. const encode = 'fRequest::encode';
  41. const filter = 'fRequest::filter';
  42. const generateCSRFToken = 'fRequest::generateCSRFToken';
  43. const get = 'fRequest::get';
  44. const getAcceptLanguages = 'fRequest::getAcceptLanguages';
  45. const getAcceptTypes = 'fRequest::getAcceptTypes';
  46. const getBestAcceptLanguage = 'fRequest::getBestAcceptLanguage';
  47. const getBestAcceptType = 'fRequest::getBestAcceptType';
  48. const getValid = 'fRequest::getValid';
  49. const isAjax = 'fRequest::isAjax';
  50. const isDelete = 'fRequest::isDelete';
  51. const isGet = 'fRequest::isGet';
  52. const isPost = 'fRequest::isPost';
  53. const isPut = 'fRequest::isPut';
  54. const overrideAction = 'fRequest::overrideAction';
  55. const prepare = 'fRequest::prepare';
  56. const reset = 'fRequest::reset';
  57. const set = 'fRequest::set';
  58. const unfilter = 'fRequest::unfilter';
  59. const validateCSRFToken = 'fRequest::validateCSRFToken';
  60. /**
  61. * A backup copy of `$_FILES` for ::unfilter()
  62. *
  63. * @var array
  64. */
  65. static private $backup_files = array();
  66. /**
  67. * A backup copy of `$_GET` for ::unfilter()
  68. *
  69. * @var array
  70. */
  71. static private $backup_get = array();
  72. /**
  73. * A backup copy of `$_POST` for unfilter()
  74. *
  75. * @var array
  76. */
  77. static private $backup_post = array();
  78. /**
  79. * A backup copy of the local `PUT`/`DELETE` post data for ::unfilter()
  80. *
  81. * @var array
  82. */
  83. static private $backup_put_delete = array();
  84. /**
  85. * The key/value pairs from the post data of a `PUT`/`DELETE` request
  86. *
  87. * @var array
  88. */
  89. static private $put_delete = NULL;
  90. /**
  91. * Indicated if the parameter specified is set in the `$_GET` or `$_POST` superglobals or in the post data of a `PUT` or `DELETE` request
  92. *
  93. * @param string $key The key to check - array elements can be checked via `[sub-key]` syntax
  94. * @return boolean If the parameter is set
  95. */
  96. static public function check($key)
  97. {
  98. self::initPutDelete();
  99. $array_dereference = NULL;
  100. if (strpos($key, '[')) {
  101. $bracket_pos = strpos($key, '[');
  102. $array_dereference = substr($key, $bracket_pos);
  103. $key = substr($key, 0, $bracket_pos);
  104. }
  105. if (!isset($_GET[$key]) && !isset($_POST[$key]) && !isset(self::$put_delete[$key])) {
  106. return FALSE;
  107. }
  108. $values = array($_GET, $_POST, self::$put_delete);
  109. if ($array_dereference) {
  110. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  111. $array_keys = array_map('current', $array_keys);
  112. array_unshift($array_keys, $key);
  113. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  114. foreach ($values as &$value) {
  115. if (!is_array($value) || !isset($value[$array_key])) {
  116. $value = NULL;
  117. } else {
  118. $value = $value[$array_key];
  119. }
  120. }
  121. }
  122. $key = end($array_keys);
  123. }
  124. return isset($values[0][$key]) || isset($values[1][$key]) || isset($values[2][$key]);
  125. }
  126. /**
  127. * Gets a value from ::get() and passes it through fHTML::encode()
  128. *
  129. * @param string $key The key to get the value of - array elements can be accessed via `[sub-key]` syntax
  130. * @param string $cast_to Cast the value to this data type
  131. * @param mixed $default_value If the parameter is not set in the `DELETE`/`PUT` post data, `$_POST` or `$_GET`, use this value instead
  132. * @return string The encoded value
  133. */
  134. static public function encode($key, $cast_to=NULL, $default_value=NULL)
  135. {
  136. return fHTML::encode(self::get($key, $cast_to, $default_value));
  137. }
  138. /**
  139. * 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
  140. *
  141. * @internal
  142. *
  143. * @param string $prefix The prefix to filter by
  144. * @param mixed $key If the field is an array, get the value corresponding to this key
  145. * @return void
  146. */
  147. static public function filter($prefix, $key)
  148. {
  149. self::initPutDelete();
  150. $regex = '#^' . preg_quote($prefix, '#') . '#';
  151. $current_backup = sizeof(self::$backup_files);
  152. self::$backup_files[] = $_FILES;
  153. $_FILES = array();
  154. foreach (self::$backup_files[$current_backup] as $field => $value) {
  155. $matches_prefix = !$prefix || ($prefix && strpos($field, $prefix) === 0);
  156. if ($matches_prefix && is_array($value) && isset($value['name'][$key])) {
  157. $new_field = preg_replace($regex, '', $field);
  158. $_FILES[$new_field] = array();
  159. $_FILES[$new_field]['name'] = $value['name'][$key];
  160. $_FILES[$new_field]['type'] = $value['type'][$key];
  161. $_FILES[$new_field]['tmp_name'] = $value['tmp_name'][$key];
  162. $_FILES[$new_field]['error'] = $value['error'][$key];
  163. $_FILES[$new_field]['size'] = $value['size'][$key];
  164. }
  165. }
  166. $globals = array(
  167. 'get' => array('array' => &$_GET, 'backup' => &self::$backup_get),
  168. 'post' => array('array' => &$_POST, 'backup' => &self::$backup_post),
  169. 'put/delete' => array('array' => &self::$put_delete, 'backup' => &self::$backup_put_delete)
  170. );
  171. foreach ($globals as $refs) {
  172. $current_backup = sizeof($refs['backup']);
  173. $refs['backup'][] = $refs['array'];
  174. $refs['array'] = array();
  175. foreach ($refs['backup'][$current_backup] as $field => $value) {
  176. $matches_prefix = !$prefix || ($prefix && strpos($field, $prefix) === 0);
  177. if ($matches_prefix && is_array($value) && isset($value[$key])) {
  178. $new_field = preg_replace($regex, '', $field);
  179. $refs['array'][$new_field] = $value[$key];
  180. }
  181. }
  182. }
  183. }
  184. /**
  185. * 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]
  186. *
  187. * This method will return a random 15 character string that should be
  188. * placed in a hidden `input` element on every HTML form. When the form
  189. * contents are being processed, the token should be retrieved and passed
  190. * into ::validateCSRFToken().
  191. *
  192. * The value returned by this method is stored in the session and then
  193. * checked by the validate method, which helps prevent cross site request
  194. * forgeries and (naive) automated form submissions.
  195. *
  196. * Tokens generated by this method are single use, so a user must request
  197. * the page that generates the token at least once per submission.
  198. *
  199. * @param string $url The URL to generate a token for, default to the current page
  200. * @return string The token to be submitted with the form
  201. */
  202. static public function generateCSRFToken($url=NULL)
  203. {
  204. if ($url === NULL) {
  205. $url = fURL::get();
  206. }
  207. $token = fCryptography::randomString(16);
  208. fSession::add(__CLASS__ . '::' . $url . '::csrf_tokens', $token);
  209. return $token;
  210. }
  211. /**
  212. * Gets a value from the `DELETE`/`PUT` post data, `$_POST` or `$_GET` superglobals (in that order)
  213. *
  214. * A value that exactly equals `''` and is not cast to a specific type will
  215. * become `NULL`.
  216. *
  217. * Valid `$cast_to` types include:
  218. * - `'string'`,
  219. * - `'int'`
  220. * - `'integer'`
  221. * - `'bool'`
  222. * - `'boolean'`
  223. * - `'array'`
  224. * - `'date'`
  225. * - `'time'`
  226. * - `'timestamp'`
  227. *
  228. * It is also possible to append a `?` to a data type to return `NULL`
  229. * whenever the `$key` was not specified in the request, or if the value
  230. * was a blank string.
  231. *
  232. * All text values are interpreted as UTF-8 string and appropriately
  233. * cleaned.
  234. *
  235. * @param string $key The key to get the value of - array elements can be accessed via `[sub-key]` syntax
  236. * @param string $cast_to Cast the value to this data type - see method description for details
  237. * @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.
  238. * @return mixed The value
  239. */
  240. static public function get($key, $cast_to=NULL, $default_value=NULL)
  241. {
  242. self::initPutDelete();
  243. $value = $default_value;
  244. $array_dereference = NULL;
  245. if (strpos($key, '[')) {
  246. $bracket_pos = strpos($key, '[');
  247. $array_dereference = substr($key, $bracket_pos);
  248. $key = substr($key, 0, $bracket_pos);
  249. }
  250. if (isset(self::$put_delete[$key])) {
  251. $value = self::$put_delete[$key];
  252. } elseif (isset($_POST[$key])) {
  253. $value = $_POST[$key];
  254. } elseif (isset($_GET[$key])) {
  255. $value = $_GET[$key];
  256. }
  257. if ($array_dereference) {
  258. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  259. $array_keys = array_map('current', $array_keys);
  260. foreach ($array_keys as $array_key) {
  261. if (!is_array($value) || !isset($value[$array_key])) {
  262. $value = $default_value;
  263. break;
  264. }
  265. $value = $value[$array_key];
  266. }
  267. }
  268. // This allows for data_type? casts to allow NULL through
  269. if ($cast_to !== NULL && substr($cast_to, -1) == '?') {
  270. if ($value === NULL || $value === '') {
  271. return $value;
  272. }
  273. $cast_to = substr($cast_to, 0, -1);
  274. }
  275. if (get_magic_quotes_gpc() && (self::isPost() || self::isGet())) {
  276. if (is_array($value)) {
  277. $value = array_map('stripslashes', $value);
  278. } else {
  279. $value = stripslashes($value);
  280. }
  281. }
  282. // This normalizes an empty element to NULL
  283. if ($cast_to === NULL && $value === '') {
  284. $value = NULL;
  285. } elseif ($cast_to == 'date') {
  286. try {
  287. $value = new fDate($value);
  288. } catch (fValidationException $e) {
  289. $value = new fDate();
  290. }
  291. } elseif ($cast_to == 'time') {
  292. try {
  293. $value = new fTime($value);
  294. } catch (fValidationException $e) {
  295. $value = new fTime();
  296. }
  297. } elseif ($cast_to == 'timestamp') {
  298. try {
  299. $value = new fTimestamp($value);
  300. } catch (fValidationException $e) {
  301. $value = new fTimestamp();
  302. }
  303. } elseif ($cast_to == 'array' && is_string($value) && strpos($value, ',') !== FALSE) {
  304. $value = explode(',', $value);
  305. } elseif ($cast_to == 'array' && ($value === NULL || $value === '')) {
  306. $value = array();
  307. } elseif ($cast_to == 'bool' || $cast_to == 'boolean') {
  308. if (strtolower($value) == 'f' || strtolower($value) == 'false' || strtolower($value) == 'no' || !$value) {
  309. $value = FALSE;
  310. } else {
  311. $value = TRUE;
  312. }
  313. } elseif (($cast_to == 'int' || $cast_to == 'integer') && preg_match('#^-?\d+$#D', $value)) {
  314. // If the cast is an integer and the value is digits, don't cast to prevent
  315. // truncation due to 32 bit integer limits
  316. } elseif ($cast_to) {
  317. settype($value, $cast_to);
  318. }
  319. // Clean values coming in to ensure we don't have invalid UTF-8
  320. if (($cast_to === NULL || $cast_to == 'string' || $cast_to == 'array') && $value !== NULL) {
  321. $value = fUTF8::clean($value);
  322. }
  323. return $value;
  324. }
  325. /**
  326. * Returns the HTTP `Accept-Language`s sorted by their `q` values (from high to low)
  327. *
  328. * @return array An associative array of `{language} => {q value}` sorted (in a stable-fashion) from highest to lowest `q`
  329. */
  330. static public function getAcceptLanguages()
  331. {
  332. return self::processAcceptHeader($_SERVER['HTTP_ACCEPT_LANGUAGE']);
  333. }
  334. /**
  335. * Returns the HTTP `Accept` types sorted by their `q` values (from high to low)
  336. *
  337. * @return array An associative array of `{type} => {q value}` sorted (in a stable-fashion) from highest to lowest `q`
  338. */
  339. static public function getAcceptTypes()
  340. {
  341. return self::processAcceptHeader($_SERVER['HTTP_ACCEPT']);
  342. }
  343. /**
  344. * Returns the best HTTP `Accept-Language` (based on `q` value) - can be filtered to only allow certain languages
  345. *
  346. * @param array $filter An array of languages that are valid to return
  347. * @return string The best language listed in the `Accept-Language` header
  348. */
  349. static public function getBestAcceptLanguage($filter=array())
  350. {
  351. return self::pickBestAcceptItem($_SERVER['HTTP_ACCEPT_LANGUAGE'], $filter);
  352. }
  353. /**
  354. * Returns the best HTTP `Accept` type (based on `q` value) - can be filtered to only allow certain types
  355. *
  356. * @param array $filter An array of types that are valid to return
  357. * @return string The best type listed in the `Accept` header
  358. */
  359. static public function getBestAcceptType($filter=array())
  360. {
  361. return self::pickBestAcceptItem($_SERVER['HTTP_ACCEPT'], $filter);
  362. }
  363. /**
  364. * Gets a value from the `DELETE`/`PUT` post data, `$_POST` or `$_GET` superglobals (in that order), restricting to a specific set of values
  365. *
  366. * @param string $key The key to get the value of - array elements can be accessed via `[sub-key]` syntax
  367. * @param array $valid_values The array of values that are permissible, if one is not selected, picks first
  368. * @return mixed The value
  369. */
  370. static public function getValid($key, $valid_values)
  371. {
  372. settype($valid_values, 'array');
  373. $valid_values = array_merge(array_unique($valid_values));
  374. $value = self::get($key);
  375. if (!in_array($value, $valid_values)) {
  376. return $valid_values[0];
  377. }
  378. return $value;
  379. }
  380. /**
  381. * Parses post data for `PUT` and `DELETE` HTTP methods
  382. *
  383. * @return void
  384. */
  385. static private function initPutDelete()
  386. {
  387. if (is_array(self::$put_delete)) {
  388. return;
  389. }
  390. if (self::isPut() || self::isDelete()) {
  391. parse_str(file_get_contents('php://input'), self::$put_delete);
  392. } else {
  393. self::$put_delete = array();
  394. }
  395. }
  396. /**
  397. * Indicates if the URL was accessed via an XMLHttpRequest
  398. *
  399. * @return boolean If the URL was accessed via an XMLHttpRequest
  400. */
  401. static public function isAjax()
  402. {
  403. return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
  404. }
  405. /**
  406. * Indicates if the URL was accessed via the `DELETE` HTTP method
  407. *
  408. * @return boolean If the URL was accessed via the `DELETE` HTTP method
  409. */
  410. static public function isDelete()
  411. {
  412. return strtolower($_SERVER['REQUEST_METHOD']) == 'delete';
  413. }
  414. /**
  415. * Indicates if the URL was accessed via the `GET` HTTP method
  416. *
  417. * @return boolean If the URL was accessed via the `GET` HTTP method
  418. */
  419. static public function isGet()
  420. {
  421. return strtolower($_SERVER['REQUEST_METHOD']) == 'get';
  422. }
  423. /**
  424. * Indicates if the URL was accessed via the `POST` HTTP method
  425. *
  426. * @return boolean If the URL was accessed via the `POST` HTTP method
  427. */
  428. static public function isPost()
  429. {
  430. return strtolower($_SERVER['REQUEST_METHOD']) == 'post';
  431. }
  432. /**
  433. * Indicates if the URL was accessed via the `PUT` HTTP method
  434. *
  435. * @return boolean If the URL was accessed via the `PUT` HTTP method
  436. */
  437. static public function isPut()
  438. {
  439. return strtolower($_SERVER['REQUEST_METHOD']) == 'put';
  440. }
  441. /**
  442. * Overrides the value of `'action'` in the `DELETE`/`PUT` post data, `$_POST` or `$_GET` superglobals based on the `'action::{action_name}'` value
  443. *
  444. * This method is primarily intended to be used for hanlding multiple
  445. * submit buttons.
  446. *
  447. * @param string $redirect The url to redirect to if the action is overriden. `%action%` will be replaced with the overridden action.
  448. * @return void
  449. */
  450. static public function overrideAction($redirect=NULL)
  451. {
  452. self::initPutDelete();
  453. $found = FALSE;
  454. $globals = array(&$_GET, &$_POST, &self::$put_delete);
  455. foreach ($globals as &$global) {
  456. foreach ($global as $key => $value) {
  457. if (substr($key, 0, 8) == 'action::') {
  458. $found = (boolean) $global['action'] = substr($key, 8);
  459. unset($global[$key]);
  460. }
  461. }
  462. }
  463. if ($redirect && $found) {
  464. fURL::redirect(str_replace('%action%', $found, $redirect));
  465. }
  466. }
  467. /**
  468. * Returns the best HTTP `Accept-*` header item match (based on `q` value), optionally filtered by an array of options
  469. *
  470. * @param string $header The `Accept-*` header to pick the best item from
  471. * @param array $options A list of supported options to pick the best from
  472. * @return string The best accept item, `NULL` if an options array is specified and none are valid
  473. */
  474. static private function pickBestAcceptItem($header, $options)
  475. {
  476. settype($options, 'array');
  477. $items = self::processAcceptHeader($header);
  478. reset($items);
  479. if (!$options) {
  480. return key($items);
  481. }
  482. $top_q = 0;
  483. $top_item = NULL;
  484. foreach ($items as $item => $q) {
  485. if ($q < $top_q) {
  486. continue;
  487. }
  488. // Type matches have /s
  489. if (strpos($item, '/') !== FALSE) {
  490. $regex = '#^' . str_replace('*', '.*', $item) . '$#iD';
  491. // Language matches that don't have a - are a wildcard
  492. } elseif (strpos($item, '-') === FALSE) {
  493. $regex = '#^' . str_replace('*', '.*', $item) . '(-.*)?$#iD';
  494. // Non-wildcard languages are straight-up matches
  495. } else {
  496. $regex = '#^' . str_replace('*', '.*', $item) . '$#iD';
  497. }
  498. foreach ($options as $option) {
  499. if (preg_match($regex, $option) && $top_q < $q) {
  500. $top_q = $q;
  501. $top_item = $option;
  502. continue 2;
  503. }
  504. }
  505. }
  506. return $top_item;
  507. }
  508. /**
  509. * Gets a value from ::get() and passes it through fHTML::prepare()
  510. *
  511. * @param string $key The key to get the value of - array elements can be accessed via `[sub-key]` syntax
  512. * @param string $cast_to Cast the value to this data type
  513. * @param mixed $default_value If the parameter is not set in the `DELETE`/`PUT` post data, `$_POST` or `$_GET`, use this value instead
  514. * @return string The prepared value
  515. */
  516. static public function prepare($key, $cast_to=NULL, $default_value=NULL)
  517. {
  518. return fHTML::prepare(self::get($key, $cast_to, $default_value));
  519. }
  520. /**
  521. * Returns an array of values from one of the HTTP `Accept-*` headers
  522. *
  523. * @return array An associative array of `{value} => {quality}` sorted (in a stable-fashion) from highest to lowest `q`
  524. */
  525. static private function processAcceptHeader($header)
  526. {
  527. $types = explode(',', $header);
  528. $output = array();
  529. // We use this suffix to force stable sorting with the built-in sort function
  530. $suffix = sizeof($types);
  531. foreach ($types as $type) {
  532. $parts = explode(';', $type);
  533. if (!empty($parts[1]) && preg_match('#^q=(\d(?:\.\d)?)#', $parts[1], $match)) {
  534. $q = number_format((float)$match[1], 5);
  535. } else {
  536. $q = number_format(1.0, 5);
  537. }
  538. $q .= $suffix--;
  539. $output[$parts[0]] = $q;
  540. }
  541. arsort($output, SORT_NUMERIC);
  542. foreach ($output as $type => $q) {
  543. $output[$type] = (float) substr($q, 0, -1);
  544. }
  545. return $output;
  546. }
  547. /**
  548. * Resets the configuration and data of the class
  549. *
  550. * @internal
  551. *
  552. * @return void
  553. */
  554. static public function reset()
  555. {
  556. fSession::clear(__CLASS__ . '::');
  557. self::$backup_files = NULL;
  558. self::$backup_get = NULL;
  559. self::$backup_post = NULL;
  560. self::$backup_put_delete = NULL;
  561. self::$put_delete = NULL;
  562. }
  563. /**
  564. * 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
  565. *
  566. * @param string $key The key to set the value to - array elements can be modified via `[sub-key]` syntax
  567. * @param mixed $value The value to set
  568. * @return void
  569. */
  570. static public function set($key, $value)
  571. {
  572. if (self::isPost()) {
  573. $tip =& $_POST;
  574. } elseif (self::isGet()) {
  575. $tip =& $_GET;
  576. } elseif (self::isDelete() || self::isPut()) {
  577. self::initPutDelete();
  578. $tip =& self::$put_delete;
  579. }
  580. if ($bracket_pos = strpos($key, '[')) {
  581. $array_dereference = substr($key, $bracket_pos);
  582. $key = substr($key, 0, $bracket_pos);
  583. preg_match_all('#(?<=\[)[^\[\]]+(?=\])#', $array_dereference, $array_keys, PREG_SET_ORDER);
  584. $array_keys = array_map('current', $array_keys);
  585. array_unshift($array_keys, $key);
  586. foreach (array_slice($array_keys, 0, -1) as $array_key) {
  587. if (!isset($tip[$array_key]) || !is_array($tip[$array_key])) {
  588. $tip[$array_key] = array();
  589. }
  590. $tip =& $tip[$array_key];
  591. }
  592. $tip[end($array_keys)] = $value;
  593. } else {
  594. $tip[$key] = $value;
  595. }
  596. }
  597. /**
  598. * Returns `$_GET`, `$_POST` and `$_FILES` and the `PUT`/`DELTE` post data to the state they were at before ::filter() was called
  599. *
  600. * @internal
  601. *
  602. * @return void
  603. */
  604. static public function unfilter()
  605. {
  606. if (self::$backup_get === array()) {
  607. throw new fProgrammerException(
  608. '%1$s can only be called after %2$s',
  609. __CLASS__ . '::unfilter()',
  610. __CLASS__ . '::filter()'
  611. );
  612. }
  613. $_FILES = array_pop(self::$backup_files);
  614. $_GET = array_pop(self::$backup_get);
  615. $_POST = array_pop(self::$backup_post);
  616. self::$put_delete = array_pop(self::$backup_put_delete);
  617. }
  618. /**
  619. * Validates a request token generated by ::generateCSRFToken()
  620. *
  621. * This method takes a request token and ensures it is valid, otherwise
  622. * it will throw an fValidationException.
  623. *
  624. * @throws fValidationException When the CSRF token specified is invalid
  625. *
  626. * @param string $token The request token to validate
  627. * @param string $url The URL to validate the token for, default to the current page
  628. * @return void
  629. */
  630. static public function validateCSRFToken($token, $url=NULL)
  631. {
  632. if ($url === NULL) {
  633. $url = fURL::get();
  634. }
  635. $key = __CLASS__ . '::' . $url . '::csrf_tokens';
  636. $tokens = fSession::get($key, array());
  637. if (!in_array($token, $tokens)) {
  638. throw new fValidationException(
  639. 'The form submitted could not be validated as authentic, please try submitting it again'
  640. );
  641. }
  642. $tokens = array_diff($tokens, array($token));;
  643. fSession::set($key, $tokens);
  644. }
  645. /**
  646. * Forces use as a static class
  647. *
  648. * @return fRequest
  649. */
  650. private function __construct() { }
  651. }
  652. /**
  653. * Copyright (c) 2007-2010 Will Bond <will@flourishlib.com>, others
  654. *
  655. * Permission is hereby granted, free of charge, to any person obtaining a copy
  656. * of this software and associated documentation files (the "Software"), to deal
  657. * in the Software without restriction, including without limitation the rights
  658. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  659. * copies of the Software, and to permit persons to whom the Software is
  660. * furnished to do so, subject to the following conditions:
  661. *
  662. * The above copyright notice and this permission notice shall be included in
  663. * all copies or substantial portions of the Software.
  664. *
  665. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  666. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  667. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  668. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  669. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  670. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  671. * THE SOFTWARE.
  672. */