PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/system/libraries/Input.php

https://github.com/lmorchard/friendfeedarchiver
PHP | 415 lines | 228 code | 53 blank | 134 comment | 29 complexity | 984f9ef8f42bf3ab7868426bc869b8ee MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * Input library.
  4. *
  5. * $Id: Input.php 1911 2008-02-04 16:13:16Z PugFish $
  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. // Singleton instance
  14. protected static $instance;
  15. protected $use_xss_clean = FALSE;
  16. public $ip_address = FALSE;
  17. public $user_agent = FALSE;
  18. /**
  19. * Retrieve a singleton instance of Input. This will always be the first
  20. * created instance of this class.
  21. *
  22. * @return object
  23. */
  24. public static function instance()
  25. {
  26. // Create an instance if none exists
  27. empty(self::$instance) and new Input;
  28. return self::$instance;
  29. }
  30. /**
  31. * Sets whether to globally enable the XSS processing.
  32. */
  33. public function __construct()
  34. {
  35. // Use XSS clean?
  36. $this->use_xss_clean = (bool) Config::item('core.global_xss_filtering');
  37. if (self::$instance === NULL)
  38. {
  39. $this->user_agent = Kohana::$user_agent;
  40. if (ini_get('register_globals'))
  41. {
  42. // Prevent GLOBALS override attacks
  43. isset($_REQUEST['GLOBALS']) and exit('Global variable overload attack.');
  44. // Destroy the REQUEST global
  45. $_REQUEST = array();
  46. // These globals are standard and should not be removed
  47. $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION');
  48. // This loop has the same effect as disabling register_globals
  49. foreach ($GLOBALS as $key => $val)
  50. {
  51. if ( ! in_array($key, $preserve))
  52. {
  53. // NULL-ify the global variable
  54. global $$key;
  55. $$key = NULL;
  56. // Unset the global variable
  57. unset($GLOBALS[$key]);
  58. unset($$key);
  59. }
  60. }
  61. // Warn the developer about register globals
  62. Log::add('debug', 'Register globals is enabled. To save resources, disable register_globals in php.ini');
  63. }
  64. if (is_array($_GET) AND count($_GET) > 0)
  65. {
  66. foreach ($_GET as $key => $val)
  67. {
  68. // Sanitize $_GET
  69. $_GET[$this->clean_input_keys($key)] = $this->clean_input_data($val);
  70. }
  71. }
  72. else
  73. {
  74. $_GET = array();
  75. }
  76. if (is_array($_POST) AND count($_POST) > 0)
  77. {
  78. foreach ($_POST as $key => $val)
  79. {
  80. // Sanitize $_POST
  81. $_POST[$this->clean_input_keys($key)] = $this->clean_input_data($val);
  82. }
  83. }
  84. else
  85. {
  86. $_POST = array();
  87. }
  88. if (is_array($_COOKIE) AND count($_COOKIE) > 0)
  89. {
  90. foreach ($_COOKIE as $key => $val)
  91. {
  92. // Sanitize $_COOKIE
  93. $_COOKIE[$this->clean_input_keys($key)] = $this->clean_input_data($val);
  94. }
  95. }
  96. else
  97. {
  98. $_COOKIE = array();
  99. }
  100. // Create a singleton
  101. self::$instance = $this;
  102. Log::add('debug', 'Global GET, POST and COOKIE data sanitized');
  103. }
  104. Log::add('debug', 'Input Library initialized');
  105. }
  106. /**
  107. * Fetch an item from a global array.
  108. *
  109. * @param string array to access (get, post, cookie or server)
  110. * @param array arguments (array key, xss_clean)
  111. * @return mixed
  112. */
  113. public function __call($global, $args = array())
  114. {
  115. // Array to be searched, assigned by reference
  116. $array = array();
  117. switch (strtolower($global))
  118. {
  119. case 'get': $array =& $_GET; break;
  120. case 'post': $array =& $_POST; break;
  121. case 'cookie': $array =& $_COOKIE; break;
  122. case 'server': $array =& $_SERVER; break;
  123. default:
  124. throw new Kohana_Exception('core.invalid_method', $global, get_class($this));
  125. }
  126. if (count($args) === 0)
  127. return $array;
  128. // If the last argument is a boolean, it's the XSS clean flag
  129. $xss_clean = (is_bool(end($args))) ? array_pop($args) : FALSE;
  130. // Reset the array pointer
  131. reset($args);
  132. // Multiple inputs require us to return an array
  133. $return_array = (count($args) > 1);
  134. // Compose the data to return
  135. $data = array();
  136. while ($key = array_shift($args))
  137. {
  138. if (isset($array[$key]))
  139. {
  140. // XSS clean if the data has not already been cleaned
  141. $data[$key] = ($this->use_xss_clean == FALSE AND $xss_clean == TRUE) ? $this->xss_clean($array[$key]) : $array[$key];
  142. }
  143. else
  144. {
  145. $data[$key] = NULL;
  146. }
  147. }
  148. // Return the global value
  149. return ($return_array) ? $data : current($data);
  150. }
  151. /**
  152. * This is a helper function. It escapes data and standardizes newline characters to '\n'.
  153. *
  154. * @param unknown_type string to clean
  155. * @return string
  156. */
  157. protected function clean_input_data($str)
  158. {
  159. if (is_array($str))
  160. {
  161. $new_array = array();
  162. foreach ($str as $key => $val)
  163. {
  164. $new_array[$this->clean_input_keys($key)] = $this->clean_input_data($val);
  165. }
  166. return $new_array;
  167. }
  168. if (get_magic_quotes_gpc())
  169. {
  170. $str = stripslashes($str);
  171. }
  172. if ($this->use_xss_clean === TRUE)
  173. {
  174. $str = $this->xss_clean($str);
  175. }
  176. // Standardize newlines
  177. return str_replace(array("\r\n", "\r"), "\n", $str);
  178. }
  179. /**
  180. * This is a helper function. To prevent malicious users
  181. * from trying to exploit keys we make sure that keys are
  182. * only named with alpha-numeric text and a few other items.
  183. *
  184. * @param string string to clean
  185. * @return string
  186. */
  187. protected function clean_input_keys($str)
  188. {
  189. $chars = (PCRE_UNICODE_PROPERTIES) ? '\pL' : 'a-zA-Z';
  190. if ( ! preg_match('#^['.$chars.'0-9:_/-]+$#uD', $str))
  191. {
  192. exit('Disallowed key characters in global data.');
  193. }
  194. return $str;
  195. }
  196. /**
  197. * Fetch the IP Address.
  198. *
  199. * @return string
  200. */
  201. public function ip_address()
  202. {
  203. if ($this->ip_address !== FALSE)
  204. return $this->ip_address;
  205. if ($this->server('REMOTE_ADDR') AND $this->server('HTTP_CLIENT_IP'))
  206. {
  207. $this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
  208. }
  209. elseif ($this->server('REMOTE_ADDR'))
  210. {
  211. $this->ip_address = $_SERVER['REMOTE_ADDR'];
  212. }
  213. elseif ($this->server('HTTP_CLIENT_IP'))
  214. {
  215. $this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
  216. }
  217. elseif ($this->server('HTTP_X_FORWARDED_FOR'))
  218. {
  219. $this->ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
  220. }
  221. if ($this->ip_address === FALSE)
  222. {
  223. $this->ip_address = '0.0.0.0';
  224. return $this->ip_address;
  225. }
  226. if (strstr($this->ip_address, ','))
  227. {
  228. $x = explode(',', $this->ip_address);
  229. $this->ip_address = end($x);
  230. }
  231. if ( ! $this->valid_ip($this->ip_address))
  232. {
  233. $this->ip_address = '0.0.0.0';
  234. }
  235. return $this->ip_address;
  236. }
  237. /**
  238. * Validates an IPv4 address based on RFC specifications.
  239. *
  240. * @param string IP to validate
  241. * @return boolean
  242. */
  243. public function valid_ip($ip)
  244. {
  245. return valid::ip($ip);
  246. }
  247. /**
  248. * Get the user agent of the current request.
  249. *
  250. * @return string
  251. */
  252. public function user_agent()
  253. {
  254. return $this->user_agent;
  255. }
  256. /**
  257. * Clean cross site scripting exploits from string.
  258. * HTMLPurifier may be used if installed, otherwise defaults to built in method.
  259. * Note - This function should only be used to deal with data upon submission.
  260. * It's not something that should be used for general runtime processing
  261. * since it requires a fair amount of processing overhead.
  262. *
  263. * @param string data to clean
  264. * @param string xss_clean method to use ('htmlpurifier' or defaults to built in method)
  265. * @return string
  266. */
  267. public function xss_clean($data, $tool = NULL)
  268. {
  269. if (is_array($data))
  270. {
  271. foreach ($data as $key => $val)
  272. {
  273. $data[$key] = $this->xss_clean($val, $tool);
  274. }
  275. return $data;
  276. }
  277. // It is a string
  278. $string = $data;
  279. // Do not clean empty strings
  280. if (trim($string) == '')
  281. return $string;
  282. if ( ! is_string($tool))
  283. {
  284. // Fetch the configured tool
  285. if (is_bool($tool = Config::item('core.global_xss_filtering')))
  286. {
  287. // Make sure that the default tool is used
  288. $tool = 'default';
  289. }
  290. }
  291. switch ($tool)
  292. {
  293. case 'htmlpurifier':
  294. /**
  295. * @todo License should go here, http://htmlpurifier.org/
  296. */
  297. require_once Kohana::find_file('vendor', 'htmlpurifier/HTMLPurifier.auto');
  298. require_once 'HTMLPurifier.func.php';
  299. // Set configuration
  300. $config = HTMLPurifier_Config::createDefault();
  301. $config->set('HTML', 'TidyLevel', 'none'); // Only XSS cleaning now
  302. // Run HTMLPurifier
  303. $string = HTMLPurifier($string, $config);
  304. break;
  305. default:
  306. // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
  307. // +----------------------------------------------------------------------+
  308. // | Copyright (c) 2001-2006 Bitflux GmbH |
  309. // +----------------------------------------------------------------------+
  310. // | Licensed under the Apache License, Version 2.0 (the "License"); |
  311. // | you may not use this file except in compliance with the License. |
  312. // | You may obtain a copy of the License at |
  313. // | http://www.apache.org/licenses/LICENSE-2.0 |
  314. // | Unless required by applicable law or agreed to in writing, software |
  315. // | distributed under the License is distributed on an "AS IS" BASIS, |
  316. // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
  317. // | implied. See the License for the specific language governing |
  318. // | permissions and limitations under the License. |
  319. // +----------------------------------------------------------------------+
  320. // | Author: Christian Stocker <chregu@bitflux.ch> |
  321. // +----------------------------------------------------------------------+
  322. //
  323. // Kohana Modifications:
  324. // * Changed double quotes to single quotes, changed indenting and spacing
  325. // * Removed magic_quotes stuff
  326. // * Increased regex readability:
  327. // * Used delimeters that aren't found in the pattern
  328. // * Removed all unneeded escapes
  329. // * Deleted U modifiers and swapped greediness where needed
  330. // * Increased regex speed:
  331. // * Made capturing parentheses non-capturing where possible
  332. // * Removed parentheses where possible
  333. // * Split up alternation alternatives
  334. //
  335. $string = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $string);
  336. // fix &entitiy\n;
  337. $string = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $string);
  338. $string = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $string);
  339. $string = html_entity_decode($string, ENT_COMPAT, 'UTF-8');
  340. // remove any attribute starting with "on" or xmlns
  341. $string = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*>#iu', '$1>', $string);
  342. // remove javascript: and vbscript: protocol
  343. $string = 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...', $string);
  344. $string = 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...', $string);
  345. $string = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $string);
  346. //<span style="width: expression(alert('Ping!'));"></span>
  347. // only works in ie...
  348. $string = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*>#i', '$1>', $string);
  349. $string = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*>#i', '$1>', $string);
  350. $string = 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>', $string);
  351. //remove namespaced elements (we do not need them...)
  352. $string = preg_replace('#</*\w+:\w[^>]*>#i', '',$string);
  353. //remove really unwanted tags
  354. do {
  355. $oldstring = $string;
  356. $string = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*>#i', '', $string);
  357. } while ($oldstring != $string);
  358. break;
  359. }
  360. return $string;
  361. }
  362. } // End Input Class