PageRenderTime 71ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/application/libraries/Input.php

http://github.com/tcm-project/tangocms
PHP | 331 lines | 183 code | 23 blank | 125 comment | 38 complexity | 840d58de3ff3be5a48abc9c31780b088 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /**
  3. * Zula Framework Input
  4. * Cleans all input data (POST, GET etc) and then gives the superglobals
  5. * a blank array value.
  6. *
  7. * @patches submit all patches to patches@tangocms.org
  8. *
  9. * @author Alex Cartwright
  10. * @copyright Copyright (C) 2007, 2008, 2009 Alex Cartwright
  11. * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU/LGPL 2.1
  12. * @package Zula_Input
  13. */
  14. class Input extends Zula_LibraryBase {
  15. /**
  16. * The CSRF input element name used
  17. */
  18. const _CSRF_INAME = 'zula-csrf-token';
  19. /**
  20. * Every input we are using - pretty much like $_REQUEST
  21. * @var array
  22. */
  23. private $inputs = array(
  24. 'POST' => null,
  25. 'GET' => null,
  26. 'COOKIE' => null,
  27. 'CLI' => null,
  28. );
  29. /**
  30. * Returns the CSRF error message that should be used
  31. *
  32. * @return string
  33. */
  34. static public function csrfMsg() {
  35. return t('Suspected malicious CSRF attack, unable to proceed', I18n::_DTD);
  36. }
  37. /**
  38. * Constructor function
  39. * Sets the class properties that will be used later on in the class.
  40. *
  41. * @return object
  42. */
  43. public function __construct() {
  44. if ( function_exists( 'set_magic_quotes_runtime' ) ) {
  45. // Yes yes I know it is deprecated, that is why we shut it up.
  46. @set_magic_quotes_runtime( false );
  47. }
  48. }
  49. /**
  50. * Makes sure keys only contain certain characters
  51. *
  52. * @param string $key
  53. * @return string
  54. */
  55. protected function cleanInputKey( $key ) {
  56. return str_replace( "\0", '', $key );
  57. }
  58. /**
  59. * Cleans input data by removing slashes if needed, standadizes new lines
  60. * and also helps protect against XSS attacks.
  61. *
  62. * @param mixed $data
  63. * @return mixed
  64. */
  65. protected function cleanInputData( $data ) {
  66. static $hasMQ = null;
  67. if ( $hasMQ === null ) {
  68. $hasMQ = function_exists( 'get_magic_quotes_gpc' ) && get_magic_quotes_gpc();
  69. }
  70. if ( is_array( $data ) ) {
  71. foreach( $data as $key=>$val ) {
  72. $data[ $this->cleanInputKey( $key ) ] = $this->cleanInputData( $val );
  73. }
  74. return $data;
  75. } else if ( $hasMQ ) {
  76. $data = stripslashes( $data );
  77. }
  78. // Standadize newlines
  79. return preg_replace( "/\015\012|\015|\012/", "\n", str_replace("\0", '', $data) );
  80. }
  81. /**
  82. * Generates a unique token key, to help protect
  83. * against CSRF attacks
  84. *
  85. * @param bool $withHtml Can return the HTML input to be used
  86. * @return string
  87. */
  88. public function createToken( $withHtml=false ) {
  89. do {
  90. $token = zula_hash( uniqid( session_id(), true ) );
  91. } while( isset( $_SESSION['csrf-tokens'][ $token ] ) );
  92. // Store the token and life of it
  93. $_SESSION['csrf-tokens'][ $token ] = time();
  94. if ( $withHtml == false ) {
  95. return $token;
  96. } else {
  97. return '<div><input type="hidden" name="'.self::_CSRF_INAME.'" value="'.$token.'"></div>';
  98. }
  99. }
  100. /**
  101. * Checks if a CSRF Token exists in session and is
  102. * in-date, as a token will only exists for 2hours
  103. *
  104. * @param string $from Either 'post' or 'get'
  105. * @return bool
  106. */
  107. public function checkToken( $from='post' ) {
  108. try {
  109. $token = $from == 'post' ? $this->post( self::_CSRF_INAME ) : $this->get( 'zct' ); # zct = Zula CSRF Token
  110. } catch ( Input_KeyNoExist $e ) {
  111. return false;
  112. }
  113. unset( $this->inputs['POST'][ self::_CSRF_INAME ] );
  114. if ( !empty( $_SESSION['csrf-tokens'][ $token ] ) ) {
  115. if ( time() <= ($_SESSION['csrf-tokens'][ $token ] + (60*60*2)) ) {
  116. return true;
  117. }
  118. unset( $_SESSION['csrf-tokens'][ $token ] ); # Remove and invalidate the token
  119. }
  120. // Log the possible CSRF attack
  121. $this->_log->message( '***CSRF token failed*** Possible malicious attack!', Log::L_WARNING );
  122. return false;
  123. }
  124. /**
  125. * Fetchs input from the correct superglobal and empties
  126. * the array so they can not be used directly in the script.
  127. *
  128. * @param string $type
  129. * @return bool
  130. */
  131. protected function fetchInput( $type ) {
  132. if ( !array_key_exists( $type, $this->inputs ) ) {
  133. throw new Input_Exception( 'unknown input type "'.$type.'"' );
  134. }
  135. if ( $this->inputs[ $type ] === null ) {
  136. switch( $type ) {
  137. case 'POST':
  138. $inputData = $_POST;
  139. if ( isset( $_SESSION['post-restore'] ) ) {
  140. $inputData = zula_merge_recursive( $inputData, $_SESSION['post-restore'] );
  141. unset( $_SESSION['post-restore'] );
  142. }
  143. $_POST = array();
  144. break;
  145. case 'GET':
  146. $inputData = $_GET;
  147. $_GET = array();
  148. break;
  149. case 'COOKIE':
  150. $inputData = $_COOKIE;
  151. $_COOKIE = array();
  152. break;
  153. case 'CLI':
  154. $inputData = array();
  155. if ( !isset( $_SERVER['argv'] ) ) {
  156. throw new Input_Exception( "'register_argc_argv' appears to be disabled" );
  157. }
  158. $cliArgs = array_slice( $_SERVER['argv'], 1 );
  159. while ( ($arg = array_shift($cliArgs)) !== null ) {
  160. if ( $arg[0] === '-' ) {
  161. if ( preg_match( '#^--([A-Za-z_\-]+)=(.*?)$#', $arg, $matches ) ) {
  162. // Support for --flag=parameter
  163. $flag = $matches[1];
  164. $param = $matches[2];
  165. } else {
  166. $flag = ltrim( $arg, '-' );
  167. $param = array_shift( $cliArgs );
  168. }
  169. if ( isset( $inputData[$flag] ) ){
  170. $inputData[ $flag ] = (array) $inputData[ $flag ];
  171. $inputData[ $flag ][] = $param;
  172. } else {
  173. $inputData[ $flag ] = $param;
  174. }
  175. }
  176. }
  177. break;
  178. }
  179. }
  180. foreach( $inputData as $key=>$val ) {
  181. if ( ($newKey = $this->cleanInputKey($key)) != $key ) {
  182. unset( $inputData[ $key ] );
  183. }
  184. $inputData[ $newKey ] = $this->cleanInputData( $val );
  185. }
  186. $this->inputs[ $type ] = $inputData;
  187. return true;
  188. }
  189. /**
  190. * Checks if a input key exists for the correct type
  191. *
  192. * @param string $type
  193. * @param string $key
  194. * @return bool
  195. */
  196. public function has( $type, $key ) {
  197. try {
  198. $this->get( $key, $type, false );
  199. } catch ( Input_KeyNoExist $e ) {
  200. return false;
  201. }
  202. return true;
  203. }
  204. /**
  205. * Returns the entire array of the correct input type
  206. *
  207. * @param string $type
  208. * @param bool $trim
  209. * @return array|bool
  210. */
  211. public function getAll( $type, $trim=true ) {
  212. $type = strtoupper( $type );
  213. if ( $this->inputs[ $type ] === null ) {
  214. $this->fetchInput( $type );
  215. }
  216. if ( isset( $this->inputs[ $type ] ) ) {
  217. $tmpVal = $this->inputs[ $type ];
  218. if ( $trim ) {
  219. $tmpVal = is_array($tmpVal) ? zula_array_map_recursive( 'trim', $tmpVal ) : trim( $tmpVal );
  220. }
  221. return $tmpVal;
  222. } else {
  223. trigger_error( 'Input::getAll() could not get all of type "'.$type.'". Type is invalid', E_USER_WARNING );
  224. return false;
  225. }
  226. }
  227. /**
  228. * Returns the value of the correct input type. To access a
  229. * multidimensional array, use the syntax 'foo/bar/car' where
  230. * a '/' deliminates the next array index.
  231. *
  232. * @param string $key
  233. * @param string $type
  234. * @param bool $trim
  235. * @return mixed
  236. */
  237. public function get( $key, $type='get', $trim=true ) {
  238. $type = strtoupper( $type );
  239. if ( $this->inputs[ $type ] === null ) {
  240. $this->fetchInput( $type );
  241. }
  242. $key = preg_split( '#(?<!\\\)/#', trim($key, '/') );
  243. foreach( $key as $val ) {
  244. $val = stripslashes( $val );
  245. if ( !isset( $tmpVal ) && isset( $this->inputs[ $type ][ $val ] ) ) {
  246. $tmpVal = $this->inputs[ $type ][ $val ];
  247. } else if ( isset( $tmpVal ) && is_array( $tmpVal ) && isset( $tmpVal[ $val ] ) ) {
  248. $tmpVal = $tmpVal[ $val ];
  249. } else {
  250. throw new Input_KeyNoExist( 'input key "'.stripslashes( implode('/', $key) ).'" for data type "'.$type.'" does not exist' );
  251. }
  252. }
  253. if ( $trim ) {
  254. $tmpVal = is_array($tmpVal) ? zula_array_map_recursive( 'trim', $tmpVal ) : trim( $tmpVal );
  255. }
  256. return $tmpVal;
  257. }
  258. /**
  259. * Another quick alias to get( $key, 'post', ... )
  260. *
  261. * @param string $key
  262. * @param bool $trim
  263. * @return string
  264. * @see Input::get
  265. */
  266. public function post( $key, $trim=true ) {
  267. return $this->get( $key, 'post', $trim );
  268. }
  269. /**
  270. * Another quick alias to get( $key, 'cookie', ... )
  271. *
  272. * @param string $key
  273. * @param bool $trim
  274. * @return string
  275. * @see Input::get
  276. */
  277. public function cookie( $key, $trim=true ) {
  278. return $this->get( $key, 'cookie', $trim );
  279. }
  280. /**
  281. * Another quick alias to get( $key, 'cli', ... )
  282. *
  283. * @param string $key
  284. * @param bool $trim
  285. * @return string
  286. * @see Input::get
  287. */
  288. public function cli( $key, $trim=true ) {
  289. return $this->get( $key, 'cli', $trim );
  290. }
  291. /**
  292. * Checks if an array of items exists for either of
  293. * the methods/inputs (GET, POST, COOKIE)
  294. *
  295. * @param array $items
  296. * @param string $type
  297. * @return bool
  298. */
  299. public function hasItems( array $items, $type='post' ) {
  300. foreach( $items as $item ) {
  301. if ( !$this->has( $type, $item ) ) {
  302. return false;
  303. }
  304. }
  305. return true;
  306. }
  307. }
  308. ?>