PageRenderTime 46ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/core/vb/cleaner.php

https://gitlab.com/hub/vbulletin
PHP | 419 lines | 328 code | 24 blank | 67 comment | 33 complexity | 41514072cf5a6e4dc315710adc160a74 MD5 | raw file
Possible License(s): Apache-2.0, Unlicense
  1. <?php
  2. /**
  3. * Class to handle and sanitize variables from GET, POST and COOKIE etc
  4. *
  5. * @package vBulletin
  6. * @version $Revision: 43053 $
  7. * @date $Date: 2011-04-25 13:02:53 -0700 (Mon, 25 Apr 2011) $
  8. */
  9. class vB_Cleaner
  10. {
  11. const TYPE_NOCLEAN = 0;
  12. const TYPE_BOOL = 1;
  13. const TYPE_INT = 2;
  14. const TYPE_UINT = 3;
  15. const TYPE_NUM = 4;
  16. const TYPE_UNUM = 5;
  17. const TYPE_UNIXTIME = 6;
  18. const TYPE_STR = 7;
  19. const TYPE_NOTRIM = 8;
  20. const TYPE_NOHTML = 9;
  21. const TYPE_ARRAY = 10;
  22. const TYPE_FILE = 11;
  23. const TYPE_BINARY = 12;
  24. const TYPE_NOHTMLCOND = 13;
  25. const TYPE_ARRAY_BOOL = 101;
  26. const TYPE_ARRAY_INT = 102;
  27. const TYPE_ARRAY_UINT = 103;
  28. const TYPE_ARRAY_NUM = 104;
  29. const TYPE_ARRAY_UNUM = 105;
  30. const TYPE_ARRAY_UNIXTIME = 106;
  31. const TYPE_ARRAY_STR = 107;
  32. const TYPE_ARRAY_NOTRIM = 108;
  33. const TYPE_ARRAY_NOHTML = 109;
  34. const TYPE_ARRAY_ARRAY = 110;
  35. const TYPE_ARRAY_FILE = self::TYPE_FILE; // An array of "Files" behaves differently than other <input> arrays. TYPE_FILE handles both types.
  36. const TYPE_ARRAY_BINARY = 112;
  37. const TYPE_ARRAY_NOHTMLCOND = 113;
  38. const TYPE_ARRAY_KEYS_INT = 202;
  39. const TYPE_ARRAY_KEYS_STR = 207;
  40. const CONVERT_SINGLE = 100; // Value to subtract from array types to convert to single types
  41. const CONVERT_KEYS = 200; // Value to subtract from array => keys types to convert to single types
  42. const STR_NOHTML = self::TYPE_NOHTML;
  43. /**
  44. * Translation table for short superglobal name to long superglobal name
  45. *
  46. * @var array
  47. */
  48. protected $superglobalLookup = array(
  49. 'g' => '_GET',
  50. 'p' => '_POST',
  51. 'r' => '_REQUEST',
  52. 'c' => '_COOKIE',
  53. 's' => '_SERVER',
  54. 'e' => '_ENV',
  55. 'f' => '_FILES'
  56. );
  57. /**
  58. * Constructor
  59. *
  60. * First, verifies that $GLOBALS has not been modified from the outside.
  61. * Second, ensures that if REQUEST_METHOD is POST all super globals have
  62. * the same keys to avoid variable injection.
  63. * Third, Ensures that register_globals is disabled and unsets all GPC
  64. * variables from the $GLOBALS array if register_globals is not disabled.
  65. * Fourth, moves $_COOKIE vars into the REQUEST_METHOD vars and deletes them
  66. * from the $_REQUEST array.
  67. */
  68. public function __construct()
  69. {
  70. if (!is_array($GLOBALS))
  71. {
  72. die('<strong>Fatal Error:</strong> Invalid URL.');
  73. }
  74. if (isset($_SERVER['REQUEST_METHOD']) AND $_SERVER['REQUEST_METHOD'] == 'POST')
  75. {
  76. foreach (array_keys($_POST) as $key)
  77. {
  78. if (isset($_GET["$key"]))
  79. {
  80. $_GET["$key"] = $_REQUEST["$key"] = $_POST["$key"];
  81. }
  82. }
  83. }
  84. if (!defined('SESSION_BYPASS'))
  85. {
  86. define('SESSION_BYPASS', !empty($_REQUEST['bypass']));
  87. }
  88. if (isset($_SERVER['REQUEST_METHOD']) AND $_SERVER['REQUEST_METHOD'] == 'POST' AND isset($_SERVER['HTTP_X_REQUESTED_WITH']) AND $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' AND
  89. !(isset($_REQUEST['forcenoajax']) AND $_REQUEST['forcenoajax']))
  90. {
  91. $_POST['ajax'] = $_REQUEST['ajax'] = 1;
  92. }
  93. if (@ini_get('register_globals') OR !@ini_get('gpc_order'))
  94. {
  95. foreach ($this->superglobalLookup AS $arrayname)
  96. {
  97. if (!empty($GLOBALS["$arrayname"]))
  98. {
  99. foreach (array_keys($GLOBALS["$arrayname"]) AS $varname)
  100. {
  101. if (!in_array($varname, $this->superglobalLookup))
  102. {
  103. unset($GLOBALS["$varname"]);
  104. }
  105. }
  106. }
  107. }
  108. }
  109. foreach (array_keys($_COOKIE) AS $varname)
  110. {
  111. unset($_REQUEST["$varname"]);
  112. if (isset($_POST["$varname"]))
  113. {
  114. $_REQUEST["$varname"] =& $_POST["$varname"];
  115. }
  116. else if (isset($_GET["$varname"]))
  117. {
  118. $_REQUEST["$varname"] =& $_GET["$varname"];
  119. }
  120. }
  121. }
  122. /**
  123. * Makes data in an array safe to use
  124. *
  125. * @param array The source array containing the data to be cleaned
  126. * @param array Array of variable names and types we want to extract from the source array
  127. *
  128. * @return array
  129. */
  130. public function &cleanArray(&$source, $variables)
  131. {
  132. $return = array();
  133. foreach ($variables AS $varname => $vartype)
  134. {
  135. $return["$varname"] = & $this->clean($source["$varname"], $vartype, isset($source["$varname"]));
  136. }
  137. return $return;
  138. }
  139. /**
  140. * Makes a single variable safe to use and returns it
  141. *
  142. * @param mixed The variable to be cleaned
  143. * @param integer The type of the variable in which we are interested
  144. * @param boolean Whether or not the variable to be cleaned actually is set
  145. *
  146. * @return mixed The cleaned value
  147. */
  148. public function &clean(&$var, $vartype = self::TYPE_NOCLEAN, $exists = true)
  149. {
  150. if ($exists)
  151. {
  152. if (($vartype == self::TYPE_ARRAY OR ($vartype > self::CONVERT_SINGLE AND $vartype < self::CONVERT_KEYS)) AND is_string($var))
  153. {
  154. $tempvar = array();
  155. $tempvar = json_decode($var, true);
  156. $var = $tempvar;
  157. }
  158. if ($vartype < self::CONVERT_SINGLE)
  159. {
  160. $this->doClean($var, $vartype);
  161. }
  162. else if (is_array($var))
  163. {
  164. if ($vartype >= self::CONVERT_KEYS)
  165. {
  166. $var = array_keys($var);
  167. $vartype -= self::CONVERT_KEYS;
  168. }
  169. else
  170. {
  171. $vartype -= self::CONVERT_SINGLE;
  172. }
  173. foreach (array_keys($var) AS $key)
  174. {
  175. $this->doClean($var["$key"], $vartype);
  176. }
  177. }
  178. else
  179. {
  180. $var = array();
  181. }
  182. return $var;
  183. }
  184. else
  185. {
  186. // We use $newvar here to prevent overwrite superglobals. See bug #28898.
  187. if ($vartype < self::CONVERT_SINGLE)
  188. {
  189. switch ($vartype)
  190. {
  191. case self::TYPE_INT:
  192. case self::TYPE_UINT:
  193. case self::TYPE_NUM:
  194. case self::TYPE_UNUM:
  195. case self::TYPE_UNIXTIME:
  196. {
  197. $newvar = 0;
  198. break;
  199. }
  200. case self::TYPE_STR:
  201. case self::TYPE_NOHTML:
  202. case self::TYPE_NOTRIM:
  203. case self::TYPE_NOHTMLCOND:
  204. {
  205. $newvar = '';
  206. break;
  207. }
  208. case self::TYPE_BOOL:
  209. {
  210. $newvar = 0;
  211. break;
  212. }
  213. case self::TYPE_ARRAY:
  214. case self::TYPE_FILE:
  215. {
  216. $newvar = array();
  217. break;
  218. }
  219. case self::TYPE_NOCLEAN:
  220. {
  221. $newvar = null;
  222. break;
  223. }
  224. default:
  225. {
  226. $newvar = null;
  227. }
  228. }
  229. }
  230. else
  231. {
  232. $newvar = array();
  233. }
  234. return $newvar;
  235. }
  236. }
  237. /**
  238. * Does the actual work to make a variable safe
  239. *
  240. * @param mixed The data we want to make safe
  241. * @param integer The type of the data
  242. *
  243. * @return mixed
  244. */
  245. protected function &doClean(&$data, $type)
  246. {
  247. static $booltypes = array('1', 'yes', 'y', 'true', 'on');
  248. switch ($type)
  249. {
  250. case self::TYPE_INT: $data = intval($data);
  251. break;
  252. case self::TYPE_UINT: $data = ($data = intval($data)) < 0 ? 0 : $data;
  253. break;
  254. case self::TYPE_NUM: $data = strval($data) + 0;
  255. break;
  256. case self::TYPE_UNUM: $data = strval($data) + 0;
  257. $data = ($data < 0) ? 0 : $data;
  258. break;
  259. case self::TYPE_BINARY: $data = strval($data);
  260. break;
  261. case self::TYPE_STR: $data = trim(strval($data));
  262. break;
  263. case self::TYPE_NOTRIM: $data = strval($data);
  264. break;
  265. case self::TYPE_NOHTML: $data = vB_String::htmlSpecialCharsUni(trim(strval($data)));
  266. break;
  267. case self::TYPE_BOOL: $data = in_array(strtolower($data), $booltypes) ? 1 : 0;
  268. break;
  269. case self::TYPE_ARRAY: $data = (is_array($data)) ? $data : array();
  270. break;
  271. case self::TYPE_NOHTMLCOND:
  272. {
  273. $data = trim(strval($data));
  274. if (strcspn($data, '<>"') < strlen($data) OR
  275. (strpos($data, '&') !== false AND !preg_match('/&(#[0-9]+|amp|lt|gt|quot);/si', $data)))
  276. {
  277. // data is not htmlspecialchars because it still has characters or entities it shouldn't
  278. $data = vB_String::htmlSpecialCharsUni($data);
  279. }
  280. break;
  281. }
  282. case self::TYPE_FILE:
  283. {
  284. // perhaps redundant :p
  285. if (is_array($data))
  286. {
  287. if (is_array($data['name']))
  288. {
  289. $files = count($data['name']);
  290. for ($index = 0; $index < $files; $index++)
  291. {
  292. $data['name']["$index"] = trim(strval($data['name']["$index"]));
  293. $data['type']["$index"] = trim(strval($data['type']["$index"]));
  294. $data['tmp_name']["$index"] = trim(strval($data['tmp_name']["$index"]));
  295. $data['error']["$index"] = intval($data['error']["$index"]);
  296. $data['size']["$index"] = intval($data['size']["$index"]);
  297. }
  298. }
  299. else
  300. {
  301. $data['name'] = trim(strval($data['name']));
  302. $data['type'] = trim(strval($data['type']));
  303. $data['tmp_name'] = trim(strval($data['tmp_name']));
  304. $data['error'] = intval($data['error']);
  305. $data['size'] = intval($data['size']);
  306. }
  307. }
  308. else
  309. {
  310. $data = array(
  311. 'name' => '',
  312. 'type' => '',
  313. 'tmp_name' => '',
  314. 'error' => 0,
  315. 'size' => 4, // UPLOAD_ERR_NO_FILE
  316. );
  317. }
  318. break;
  319. }
  320. case self::TYPE_UNIXTIME:
  321. {
  322. if (is_array($data))
  323. {
  324. $data = $this->clean($data,vB_Cleaner::TYPE_ARRAY_UINT);
  325. if ($data['month'] AND $data['day'] AND $data['year'])
  326. {
  327. require_once(DIR . '/includes/functions_misc.php');
  328. $data = vbmktime($data['hour'], $data['minute'], $data['second'], $data['month'], $data['day'], $data['year']);
  329. }
  330. else
  331. {
  332. $data = 0;
  333. }
  334. }
  335. else
  336. {
  337. $data = ($data = intval($data)) < 0 ? 0 : $data;
  338. }
  339. break;
  340. }
  341. // null actions should be deifned here so we can still catch typos below
  342. case self::TYPE_NOCLEAN:
  343. {
  344. break;
  345. }
  346. default:
  347. {
  348. if (($config = vB::getConfig()) AND $config['Misc']['debug'])
  349. {
  350. trigger_error('vB_Cleaner::doClean() Invalid data type specified', E_USER_WARNING);
  351. }
  352. }
  353. }
  354. // strip out characters that really have no business being in non-binary data
  355. switch ($type)
  356. {
  357. case self::TYPE_STR:
  358. case self::TYPE_NOTRIM:
  359. case self::TYPE_NOHTML:
  360. case self::TYPE_NOHTMLCOND:
  361. $data = str_replace(chr(0), '', $data);
  362. }
  363. return $data;
  364. }
  365. /**
  366. * Removes HTML characters and potentially unsafe scripting words from a string
  367. *
  368. * @param string The variable we want to make safe
  369. *
  370. * @return string
  371. */
  372. public function xssClean($var)
  373. {
  374. static $preg_find = array('#^javascript#i', '#^vbscript#i');
  375. static $preg_replace = array('java script', 'vb script');
  376. return preg_replace($preg_find, $preg_replace, htmlspecialchars(trim($var)));
  377. }
  378. /**
  379. * Removes HTML characters and potentially unsafe scripting words from a URL
  380. * Note: The query string is preserved.
  381. *
  382. * @param string The url to clean
  383. * @return string
  384. */
  385. public function xssCleanUrl($url)
  386. {
  387. if ($query = vB_String::parseUrl($url, PHP_URL_QUERY))
  388. {
  389. $url = substr($url, 0, strpos($url, '?'));
  390. $url = $this->xssClean($url);
  391. return $url . '?' . $query;
  392. }
  393. return $this->xssClean($url);
  394. }
  395. }