/system/libraries/Input.php

http://github.com/ushahidi/Ushahidi_Web · PHP · 454 lines · 232 code · 59 blank · 163 comment · 32 complexity · f81d924edd24d9b043019d7919fff0f4 MD5 · raw file

  1. <?php defined('SYSPATH') OR die('No direct access allowed.');
  2. /**
  3. * Input library.
  4. *
  5. * $Id: Input.php 3917 2009-01-21 03:06:22Z zombor $
  6. *
  7. * @package Core
  8. * @author Kohana Team
  9. * @copyright (c) 2007-2008 Kohana Team
  10. * @license http://kohanaphp.com/license.html
  11. */
  12. class Input_Core {
  13. // Enable or disable automatic XSS cleaning
  14. protected $use_xss_clean = FALSE;
  15. // Are magic quotes enabled?
  16. protected $magic_quotes_gpc = FALSE;
  17. // IP address of current user
  18. public $ip_address;
  19. // Input singleton
  20. protected static $instance;
  21. /**
  22. * Retrieve a singleton instance of Input. This will always be the first
  23. * created instance of this class.
  24. *
  25. * @return object
  26. */
  27. public static function instance()
  28. {
  29. if (self::$instance === NULL)
  30. {
  31. // Create a new instance
  32. return new Input;
  33. }
  34. return self::$instance;
  35. }
  36. /**
  37. * Sanitizes global GET, POST and COOKIE data. Also takes care of
  38. * magic_quotes and register_globals, if they have been enabled.
  39. *
  40. * @return void
  41. */
  42. public function __construct()
  43. {
  44. // Use XSS clean?
  45. $this->use_xss_clean = (bool) Kohana::config('core.global_xss_filtering');
  46. if (self::$instance === NULL)
  47. {
  48. // magic_quotes_runtime is enabled
  49. if (get_magic_quotes_runtime())
  50. {
  51. set_magic_quotes_runtime(0);
  52. Kohana::log('debug', 'Disable magic_quotes_runtime! It is evil and deprecated: http://php.net/magic_quotes');
  53. }
  54. // magic_quotes_gpc is enabled
  55. if (get_magic_quotes_gpc())
  56. {
  57. $this->magic_quotes_gpc = TRUE;
  58. Kohana::log('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes');
  59. }
  60. // register_globals is enabled
  61. if (ini_get('register_globals'))
  62. {
  63. if (isset($_REQUEST['GLOBALS']))
  64. {
  65. // Prevent GLOBALS override attacks
  66. exit('Global variable overload attack.');
  67. }
  68. // Destroy the REQUEST global
  69. $_REQUEST = array();
  70. // These globals are standard and should not be removed
  71. $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION');
  72. // This loop has the same effect as disabling register_globals
  73. foreach (array_diff(array_keys($GLOBALS), $preserve) as $key)
  74. {
  75. global $$key;
  76. $$key = NULL;
  77. // Unset the global variable
  78. unset($GLOBALS[$key], $$key);
  79. }
  80. // Warn the developer about register globals
  81. Kohana::log('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals');
  82. }
  83. if (is_array($_GET))
  84. {
  85. foreach ($_GET as $key => $val)
  86. {
  87. // Sanitize $_GET
  88. $_GET[$this->clean_input_keys($key)] = $this->clean_input_data($val);
  89. }
  90. }
  91. else
  92. {
  93. $_GET = array();
  94. }
  95. if (is_array($_POST))
  96. {
  97. foreach ($_POST as $key => $val)
  98. {
  99. // Sanitize $_POST
  100. $_POST[$this->clean_input_keys($key)] = $this->clean_input_data($val);
  101. }
  102. }
  103. else
  104. {
  105. $_POST = array();
  106. }
  107. if (is_array($_COOKIE))
  108. {
  109. foreach ($_COOKIE as $key => $val)
  110. {
  111. // Ignore special attributes in RFC2109 compliant cookies
  112. if ($key == '$Version' OR $key == '$Path' OR $key == '$Domain')
  113. continue;
  114. // Sanitize $_COOKIE
  115. $_COOKIE[$this->clean_input_keys($key)] = $this->clean_input_data($val);
  116. }
  117. }
  118. else
  119. {
  120. $_COOKIE = array();
  121. }
  122. // Create a singleton
  123. self::$instance = $this;
  124. Kohana::log('debug', 'Global GET, POST and COOKIE data sanitized');
  125. }
  126. }
  127. /**
  128. * Fetch an item from the $_GET array.
  129. *
  130. * @param string key to find
  131. * @param mixed default value
  132. * @param boolean XSS clean the value
  133. * @return mixed
  134. */
  135. public function get($key = array(), $default = NULL, $xss_clean = FALSE)
  136. {
  137. return $this->search_array($_GET, $key, $default, $xss_clean);
  138. }
  139. /**
  140. * Fetch an item from the $_POST array.
  141. *
  142. * @param string key to find
  143. * @param mixed default value
  144. * @param boolean XSS clean the value
  145. * @return mixed
  146. */
  147. public function post($key = array(), $default = NULL, $xss_clean = FALSE)
  148. {
  149. return $this->search_array($_POST, $key, $default, $xss_clean);
  150. }
  151. /**
  152. * Fetch an item from the $_COOKIE array.
  153. *
  154. * @param string key to find
  155. * @param mixed default value
  156. * @param boolean XSS clean the value
  157. * @return mixed
  158. */
  159. public function cookie($key = array(), $default = NULL, $xss_clean = FALSE)
  160. {
  161. return $this->search_array($_COOKIE, $key, $default, $xss_clean);
  162. }
  163. /**
  164. * Fetch an item from the $_SERVER array.
  165. *
  166. * @param string key to find
  167. * @param mixed default value
  168. * @param boolean XSS clean the value
  169. * @return mixed
  170. */
  171. public function server($key = array(), $default = NULL, $xss_clean = FALSE)
  172. {
  173. return $this->search_array($_SERVER, $key, $default, $xss_clean);
  174. }
  175. /**
  176. * Fetch an item from a global array.
  177. *
  178. * @param array array to search
  179. * @param string key to find
  180. * @param mixed default value
  181. * @param boolean XSS clean the value
  182. * @return mixed
  183. */
  184. protected function search_array($array, $key, $default = NULL, $xss_clean = FALSE)
  185. {
  186. if ($key === array())
  187. return $array;
  188. if ( ! isset($array[$key]))
  189. return $default;
  190. // Get the value
  191. $value = $array[$key];
  192. if ($this->use_xss_clean === FALSE AND $xss_clean === TRUE)
  193. {
  194. // XSS clean the value
  195. $value = $this->xss_clean($value);
  196. }
  197. return $value;
  198. }
  199. /**
  200. * Fetch the IP Address.
  201. *
  202. * @return string
  203. */
  204. public function ip_address()
  205. {
  206. if ($this->ip_address !== NULL)
  207. return $this->ip_address;
  208. if ($ip = $this->server('HTTP_CLIENT_IP'))
  209. {
  210. $this->ip_address = $ip;
  211. }
  212. elseif ($ip = $this->server('REMOTE_ADDR'))
  213. {
  214. $this->ip_address = $ip;
  215. }
  216. elseif ($ip = $this->server('HTTP_X_FORWARDED_FOR'))
  217. {
  218. $this->ip_address = $ip;
  219. }
  220. if ($comma = strrpos($this->ip_address, ',') !== FALSE)
  221. {
  222. $this->ip_address = substr($this->ip_address, $comma + 1);
  223. }
  224. if ( ! valid::ip($this->ip_address))
  225. {
  226. // Use an empty IP
  227. $this->ip_address = '0.0.0.0';
  228. }
  229. return $this->ip_address;
  230. }
  231. /**
  232. * Clean cross site scripting exploits from string.
  233. * HTMLPurifier may be used if installed, otherwise defaults to built in method.
  234. * Note - This function should only be used to deal with data upon submission.
  235. * It's not something that should be used for general runtime processing
  236. * since it requires a fair amount of processing overhead.
  237. *
  238. * @param string data to clean
  239. * @param string xss_clean method to use ('htmlpurifier' or defaults to built-in method)
  240. * @return string
  241. */
  242. public function xss_clean($data, $tool = NULL)
  243. {
  244. if ($tool === NULL)
  245. {
  246. // Use the default tool
  247. $tool = Kohana::config('core.global_xss_filtering');
  248. }
  249. if (is_array($data))
  250. {
  251. foreach ($data as $key => $val)
  252. {
  253. $data[$key] = $this->xss_clean($val, $tool);
  254. }
  255. return $data;
  256. }
  257. // Do not clean empty strings
  258. if (trim($data) === '')
  259. return $data;
  260. if ($tool === TRUE)
  261. {
  262. // NOTE: This is necessary because switch is NOT type-sensative!
  263. $tool = 'default';
  264. }
  265. switch ($tool)
  266. {
  267. case 'htmlpurifier':
  268. /**
  269. * @todo License should go here, http://htmlpurifier.org/
  270. */
  271. if ( ! class_exists('HTMLPurifier_Config', FALSE))
  272. {
  273. // Load HTMLPurifier
  274. require_once APPPATH.'libraries/htmlpurifier/HTMLPurifier.auto.php';
  275. require 'HTMLPurifier.func.php';
  276. }
  277. // Set configuration
  278. $config = HTMLPurifier_Config::createDefault();
  279. $config->set('Cache.SerializerPath', APPPATH.'cache');
  280. $config->set('HTML.TidyLevel', 'none'); // Only XSS cleaning now
  281. $config->set('HTML.SafeIframe', true);
  282. $config->set('URI.SafeIframeRegexp', Kohana::config('config.safe_iframe_regexp', FALSE, TRUE));
  283. // Run HTMLPurifier
  284. $data = HTMLPurifier($data, $config);
  285. break;
  286. default:
  287. // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
  288. // +----------------------------------------------------------------------+
  289. // | Copyright (c) 2001-2006 Bitflux GmbH |
  290. // +----------------------------------------------------------------------+
  291. // | Licensed under the Apache License, Version 2.0 (the "License"); |
  292. // | you may not use this file except in compliance with the License. |
  293. // | You may obtain a copy of the License at |
  294. // | http://www.apache.org/licenses/LICENSE-2.0 |
  295. // | Unless required by applicable law or agreed to in writing, software |
  296. // | distributed under the License is distributed on an "AS IS" BASIS, |
  297. // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
  298. // | implied. See the License for the specific language governing |
  299. // | permissions and limitations under the License. |
  300. // +----------------------------------------------------------------------+
  301. // | Author: Christian Stocker <chregu@bitflux.ch> |
  302. // +----------------------------------------------------------------------+
  303. //
  304. // Kohana Modifications:
  305. // * Changed double quotes to single quotes, changed indenting and spacing
  306. // * Removed magic_quotes stuff
  307. // * Increased regex readability:
  308. // * Used delimeters that aren't found in the pattern
  309. // * Removed all unneeded escapes
  310. // * Deleted U modifiers and swapped greediness where needed
  311. // * Increased regex speed:
  312. // * Made capturing parentheses non-capturing where possible
  313. // * Removed parentheses where possible
  314. // * Split up alternation alternatives
  315. // * Made some quantifiers possessive
  316. // Fix &entity\n;
  317. $data = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $data);
  318. $data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
  319. $data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
  320. $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
  321. // Remove any attribute starting with "on" or xmlns
  322. $data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);
  323. // Remove javascript: and vbscript: protocols
  324. $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
  325. $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
  326. $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);
  327. // Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
  328. $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
  329. $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
  330. $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);
  331. // Remove namespaced elements (we do not need them)
  332. $data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);
  333. do
  334. {
  335. // Remove really unwanted tags
  336. $old_data = $data;
  337. $data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
  338. }
  339. while ($old_data !== $data);
  340. break;
  341. }
  342. return $data;
  343. }
  344. /**
  345. * This is a helper method. It enforces W3C specifications for allowed
  346. * key name strings, to prevent malicious exploitation.
  347. *
  348. * @param string string to clean
  349. * @return string
  350. */
  351. public function clean_input_keys($str)
  352. {
  353. $chars = PCRE_UNICODE_PROPERTIES ? '\pL' : 'a-zA-Z';
  354. if ( ! preg_match('#^['.$chars.'0-9:_.-]++$#uD', $str))
  355. {
  356. exit('Disallowed key characters in global data.');
  357. }
  358. return $str;
  359. }
  360. /**
  361. * This is a helper method. It escapes data and forces all newline
  362. * characters to "\n".
  363. *
  364. * @param unknown_type string to clean
  365. * @return string
  366. */
  367. public function clean_input_data($str)
  368. {
  369. if (is_array($str))
  370. {
  371. $new_array = array();
  372. foreach ($str as $key => $val)
  373. {
  374. // Recursion!
  375. $new_array[$this->clean_input_keys($key)] = $this->clean_input_data($val);
  376. }
  377. return $new_array;
  378. }
  379. if ($this->magic_quotes_gpc === TRUE)
  380. {
  381. // Remove annoying magic quotes
  382. $str = stripslashes($str);
  383. }
  384. if ($this->use_xss_clean === TRUE)
  385. {
  386. $str = $this->xss_clean($str);
  387. }
  388. if (strpos($str, "\r") !== FALSE)
  389. {
  390. // Standardize newlines
  391. $str = str_replace(array("\r\n", "\r"), "\n", $str);
  392. }
  393. return $str;
  394. }
  395. } // End Input Class