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

/libraries/phpinputfilter/inputfilter.php

https://gitlab.com/endomorphosis/OLAAaction
PHP | 551 lines | 262 code | 41 blank | 248 comment | 57 complexity | e8da34006e37abc253a2185861cd1601 MD5 | raw file
  1. <?php
  2. /**
  3. * @class: InputFilter (PHP4 & PHP5, with comments)
  4. * @project: PHP Input Filter
  5. * @date: 10-05-2005
  6. * @version: 1.2.2_php4/php5
  7. * @author: Daniel Morris
  8. * @contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris
  9. * Tobin and Andrew Eddie.
  10. *
  11. * Modification by Louis Landry
  12. *
  13. * @copyright: Daniel Morris
  14. * @email: dan@rootcube.com
  15. * @license: GNU General Public License (GPL)
  16. */
  17. class InputFilter
  18. {
  19. var $tagsArray; // default = empty array
  20. var $attrArray; // default = empty array
  21. var $tagsMethod; // default = 0
  22. var $attrMethod; // default = 0
  23. var $xssAuto; // default = 1
  24. var $tagBlacklist = array ('applet', 'body', 'bgsound', 'base', 'basefont', 'embed', 'frame', 'frameset', 'head', 'html', 'id', 'iframe', 'ilayer', 'layer', 'link', 'meta', 'name', 'object', 'script', 'style', 'title', 'xml');
  25. var $attrBlacklist = array ('action', 'background', 'codebase', 'dynsrc', 'lowsrc'); // also will strip ALL event handlers
  26. /**
  27. * Constructor for inputFilter class. Only first parameter is required.
  28. *
  29. * @access protected
  30. * @param array $tagsArray list of user-defined tags
  31. * @param array $attrArray list of user-defined attributes
  32. * @param int $tagsMethod WhiteList method = 0, BlackList method = 1
  33. * @param int $attrMethod WhiteList method = 0, BlackList method = 1
  34. * @param int $xssAuto Only auto clean essentials = 0, Allow clean
  35. * blacklisted tags/attr = 1
  36. */
  37. function inputFilter($tagsArray = array (), $attrArray = array (), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1)
  38. {
  39. /*
  40. * Make sure user defined arrays are in lowercase
  41. */
  42. $tagsArray = array_map('strtolower', (array) $tagsArray);
  43. $attrArray = array_map('strtolower', (array) $attrArray);
  44. /*
  45. * Assign member variables
  46. */
  47. $this->tagsArray = $tagsArray;
  48. $this->attrArray = $attrArray;
  49. $this->tagsMethod = $tagsMethod;
  50. $this->attrMethod = $attrMethod;
  51. $this->xssAuto = $xssAuto;
  52. }
  53. /**
  54. * Method to be called by another php script. Processes for XSS and
  55. * specified bad code.
  56. *
  57. * @access public
  58. * @param mixed $source Input string/array-of-string to be 'cleaned'
  59. * @return mixed $source 'cleaned' version of input parameter
  60. */
  61. function process($source)
  62. {
  63. /*
  64. * Are we dealing with an array?
  65. */
  66. if (is_array($source))
  67. {
  68. foreach ($source as $key => $value)
  69. {
  70. // filter element for XSS and other 'bad' code etc.
  71. if (is_string($value))
  72. {
  73. $source[$key] = $this->remove($this->decode($value));
  74. }
  75. }
  76. return $source;
  77. } else
  78. /*
  79. * Or a string?
  80. */
  81. if (is_string($source) && !empty ($source))
  82. {
  83. // filter source for XSS and other 'bad' code etc.
  84. return $this->remove($this->decode($source));
  85. } else
  86. {
  87. /*
  88. * Not an array or string.. return the passed parameter
  89. */
  90. return $source;
  91. }
  92. }
  93. /**
  94. * Internal method to iteratively remove all unwanted tags and attributes
  95. *
  96. * @access protected
  97. * @param string $source Input string to be 'cleaned'
  98. * @return string $source 'cleaned' version of input parameter
  99. */
  100. function remove($source)
  101. {
  102. $loopCounter = 0;
  103. /*
  104. * Iteration provides nested tag protection
  105. */
  106. while ($source != $this->filterTags($source))
  107. {
  108. $source = $this->filterTags($source);
  109. $loopCounter ++;
  110. }
  111. return $source;
  112. }
  113. /**
  114. * Internal method to strip a string of certain tags
  115. *
  116. * @access protected
  117. * @param string $source Input string to be 'cleaned'
  118. * @return string $source 'cleaned' version of input parameter
  119. */
  120. function filterTags($source)
  121. {
  122. /*
  123. * In the beginning we don't really have a tag, so everything is
  124. * postTag
  125. */
  126. $preTag = null;
  127. $postTag = $source;
  128. /*
  129. * Is there a tag? If so it will certainly start with a '<'
  130. */
  131. $tagOpen_start = strpos($source, '<');
  132. while ($tagOpen_start !== false)
  133. {
  134. /*
  135. * Get some information about the tag we are processing
  136. */
  137. $preTag .= substr($postTag, 0, $tagOpen_start);
  138. $postTag = substr($postTag, $tagOpen_start);
  139. $fromTagOpen = substr($postTag, 1);
  140. $tagOpen_end = strpos($fromTagOpen, '>');
  141. /*
  142. * Let's catch any non-terminated tags and skip over them
  143. */
  144. if ($tagOpen_end === false)
  145. {
  146. $postTag = substr($postTag, $tagOpen_start +1);
  147. $tagOpen_start = strpos($postTag, '<');
  148. continue;
  149. }
  150. /*
  151. * Do we have a nested tag?
  152. */
  153. $tagOpen_nested = strpos($fromTagOpen, '<');
  154. $tagOpen_nested_end = strpos(substr($postTag, $tagOpen_end), '>');
  155. if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end))
  156. {
  157. $preTag .= substr($postTag, 0, ($tagOpen_nested +1));
  158. $postTag = substr($postTag, ($tagOpen_nested +1));
  159. $tagOpen_start = strpos($postTag, '<');
  160. continue;
  161. }
  162. /*
  163. * Lets get some information about our tag and setup attribute pairs
  164. */
  165. $tagOpen_nested = (strpos($fromTagOpen, '<') + $tagOpen_start +1);
  166. $currentTag = substr($fromTagOpen, 0, $tagOpen_end);
  167. $tagLength = strlen($currentTag);
  168. $tagLeft = $currentTag;
  169. $attrSet = array ();
  170. $currentSpace = strpos($tagLeft, ' ');
  171. /*
  172. * Are we an open tag or a close tag?
  173. */
  174. if (substr($currentTag, 0, 1) == "/")
  175. {
  176. // Close Tag
  177. $isCloseTag = true;
  178. list ($tagName) = explode(' ', $currentTag);
  179. $tagName = substr($tagName, 1);
  180. } else
  181. {
  182. // Open Tag
  183. $isCloseTag = false;
  184. list ($tagName) = explode(' ', $currentTag);
  185. }
  186. /*
  187. * Exclude all "non-regular" tagnames
  188. * OR no tagname
  189. * OR remove if xssauto is on and tag is blacklisted
  190. */
  191. if ((!preg_match("/^[a-z][a-z0-9]*$/i", $tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto)))
  192. {
  193. $postTag = substr($postTag, ($tagLength +2));
  194. $tagOpen_start = strpos($postTag, '<');
  195. // Strip tag
  196. continue;
  197. }
  198. /*
  199. * Time to grab any attributes from the tag... need this section in
  200. * case attributes have spaces in the values.
  201. */
  202. while ($currentSpace !== false)
  203. {
  204. $fromSpace = substr($tagLeft, ($currentSpace +1));
  205. $nextSpace = strpos($fromSpace, ' ');
  206. $openQuotes = strpos($fromSpace, '"');
  207. $closeQuotes = strpos(substr($fromSpace, ($openQuotes +1)), '"') + $openQuotes +1;
  208. /*
  209. * Do we have an attribute to process? [check for equal sign]
  210. */
  211. if (strpos($fromSpace, '=') !== false)
  212. {
  213. /*
  214. * If the attribute value is wrapped in quotes we need to
  215. * grab the substring from the closing quote, otherwise grab
  216. * till the next space
  217. */
  218. if (($openQuotes !== false) && (strpos(substr($fromSpace, ($openQuotes +1)), '"') !== false))
  219. {
  220. $attr = substr($fromSpace, 0, ($closeQuotes +1));
  221. } else
  222. {
  223. $attr = substr($fromSpace, 0, $nextSpace);
  224. }
  225. } else
  226. {
  227. /*
  228. * No more equal signs so add any extra text in the tag into
  229. * the attribute array [eg. checked]
  230. */
  231. $attr = substr($fromSpace, 0, $nextSpace);
  232. }
  233. // Last Attribute Pair
  234. if (!$attr)
  235. {
  236. $attr = $fromSpace;
  237. }
  238. /*
  239. * Add attribute pair to the attribute array
  240. */
  241. $attrSet[] = $attr;
  242. /*
  243. * Move search point and continue iteration
  244. */
  245. $tagLeft = substr($fromSpace, strlen($attr));
  246. $currentSpace = strpos($tagLeft, ' ');
  247. }
  248. /*
  249. * Is our tag in the user input array?
  250. */
  251. $tagFound = in_array(strtolower($tagName), $this->tagsArray);
  252. /*
  253. * If the tag is allowed lets append it to the output string
  254. */
  255. if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod))
  256. {
  257. /*
  258. * Reconstruct tag with allowed attributes
  259. */
  260. if (!$isCloseTag)
  261. {
  262. // Open or Single tag
  263. $attrSet = $this->filterAttr($attrSet);
  264. $preTag .= '<'.$tagName;
  265. for ($i = 0; $i < count($attrSet); $i ++)
  266. {
  267. $preTag .= ' '.$attrSet[$i];
  268. }
  269. /*
  270. * Reformat single tags to XHTML
  271. */
  272. if (strpos($fromTagOpen, "</".$tagName))
  273. {
  274. $preTag .= '>';
  275. } else
  276. {
  277. $preTag .= ' />';
  278. }
  279. } else
  280. {
  281. // Closing Tag
  282. $preTag .= '</'.$tagName.'>';
  283. }
  284. }
  285. /*
  286. * Find next tag's start and continue iteration
  287. */
  288. $postTag = substr($postTag, ($tagLength +2));
  289. $tagOpen_start = strpos($postTag, '<');
  290. }
  291. /*
  292. * Append any code after the end of tags and return
  293. */
  294. if ($postTag != '<')
  295. {
  296. $preTag .= $postTag;
  297. }
  298. return $preTag;
  299. }
  300. /**
  301. * Internal method to strip a tag of certain attributes
  302. *
  303. * @access protected
  304. * @param array $attrSet Array of attribute pairs to filter
  305. * @return array $newSet Filtered array of attribute pairs
  306. */
  307. function filterAttr($attrSet)
  308. {
  309. /*
  310. * Initialize variables
  311. */
  312. $newSet = array ();
  313. /*
  314. * Iterate through attribute pairs
  315. */
  316. for ($i = 0; $i < count($attrSet); $i ++)
  317. {
  318. /*
  319. * Skip blank spaces
  320. */
  321. if (!$attrSet[$i])
  322. {
  323. continue;
  324. }
  325. /*
  326. * Split into name/value pairs
  327. */
  328. $attrSubSet = explode('=', trim($attrSet[$i]), 2);
  329. list ($attrSubSet[0]) = explode(' ', $attrSubSet[0]);
  330. /*
  331. * Remove all "non-regular" attribute names
  332. * AND blacklisted attributes
  333. */
  334. if ((!preg_match("#^[a-z]*$#i", $attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on'))))
  335. {
  336. continue;
  337. }
  338. /*
  339. * XSS attribute value filtering
  340. */
  341. if ($attrSubSet[1])
  342. {
  343. // strips unicode, hex, etc
  344. $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
  345. // strip normal newline within attr value
  346. $attrSubSet[1] = preg_replace('/\s+/', '', $attrSubSet[1]);
  347. // strip double quotes
  348. $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);
  349. // [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value)
  350. if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'"))
  351. {
  352. $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
  353. }
  354. // strip slashes
  355. $attrSubSet[1] = stripslashes($attrSubSet[1]);
  356. }
  357. /*
  358. * Autostrip script tags
  359. */
  360. if (InputFilter::badAttributeValue($attrSubSet))
  361. {
  362. continue;
  363. }
  364. /*
  365. * Is our attribute in the user input array?
  366. */
  367. $attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);
  368. /*
  369. * If the tag is allowed lets keep it
  370. */
  371. if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod))
  372. {
  373. /*
  374. * Does the attribute have a value?
  375. */
  376. if ($attrSubSet[1])
  377. {
  378. $newSet[] = $attrSubSet[0].'="'.$attrSubSet[1].'"';
  379. }
  380. elseif ($attrSubSet[1] == "0")
  381. {
  382. /*
  383. * Special Case
  384. * Is the value 0?
  385. */
  386. $newSet[] = $attrSubSet[0].'="0"';
  387. } else
  388. {
  389. $newSet[] = $attrSubSet[0].'="'.$attrSubSet[0].'"';
  390. }
  391. }
  392. }
  393. return $newSet;
  394. }
  395. /**
  396. * Function to determine if contents of an attribute is safe
  397. *
  398. * @access protected
  399. * @param array $attrSubSet A 2 element array for attributes name,value
  400. * @return boolean True if bad code is detected
  401. */
  402. function badAttributeValue($attrSubSet)
  403. {
  404. $attrSubSet[0] = strtolower($attrSubSet[0]);
  405. $attrSubSet[1] = strtolower($attrSubSet[1]);
  406. return (((strpos($attrSubSet[1], 'expression') !== false) && ($attrSubSet[0]) == 'style') || (strpos($attrSubSet[1], 'javascript:') !== false) || (strpos($attrSubSet[1], 'behaviour:') !== false) || (strpos($attrSubSet[1], 'vbscript:') !== false) || (strpos($attrSubSet[1], 'mocha:') !== false) || (strpos($attrSubSet[1], 'livescript:') !== false));
  407. }
  408. /**
  409. * Try to convert to plaintext
  410. *
  411. * @access protected
  412. * @param string $source
  413. * @return string Plaintext string
  414. */
  415. function decode($source)
  416. {
  417. // url decode
  418. $source = html_entity_decode($source, ENT_QUOTES, "ISO-8859-1");
  419. // convert decimal
  420. $source = preg_replace('/&#(\d+);/me', "chr(\\1)", $source); // decimal notation
  421. // convert hex
  422. $source = preg_replace('/&#x([a-f0-9]+);/mei', "chr(0x\\1)", $source); // hex notation
  423. return $source;
  424. }
  425. /**
  426. * Method to be called by another php script. Processes for SQL injection
  427. *
  428. * @access public
  429. * @param mixed $source input string/array-of-string to be 'cleaned'
  430. * @param resource $connection - An open MySQL connection
  431. * @return string 'cleaned' version of input parameter
  432. */
  433. function safeSQL($source, & $connection)
  434. {
  435. // clean all elements in this array
  436. if (is_array($source))
  437. {
  438. foreach ($source as $key => $value)
  439. {
  440. // filter element for SQL injection
  441. if (is_string($value))
  442. {
  443. $source[$key] = $this->quoteSmart($this->decode($value), $connection);
  444. }
  445. }
  446. return $source;
  447. // clean this string
  448. } else
  449. if (is_string($source))
  450. {
  451. // filter source for SQL injection
  452. if (is_string($source))
  453. {
  454. return $this->quoteSmart($this->decode($source), $connection);
  455. }
  456. // return parameter as given
  457. } else
  458. {
  459. return $source;
  460. }
  461. }
  462. /**
  463. * Method to escape a string
  464. *
  465. * @author Chris Tobin
  466. * @author Daniel Morris
  467. *
  468. * @access protected
  469. * @param string $source
  470. * @param resource $connection An open MySQL connection
  471. * @return string Escaped string
  472. */
  473. function quoteSmart($source, & $connection)
  474. {
  475. /*
  476. * Strip escaping slashes if necessary
  477. */
  478. if (get_magic_quotes_gpc())
  479. {
  480. $source = stripslashes($source);
  481. }
  482. /*
  483. * Escape numeric and text values
  484. */
  485. $source = $this->escapeString($source, $connection);
  486. return $source;
  487. }
  488. /**
  489. * @author Chris Tobin
  490. * @author Daniel Morris
  491. *
  492. * @access protected
  493. * @param string $source
  494. * @param resource $connection An open MySQL connection
  495. * @return string Escaped string
  496. */
  497. function escapeString($string, & $connection) {
  498. /*
  499. * Use the appropriate escape string depending upon which version of php
  500. * you are running
  501. */
  502. if (version_compare(phpversion(), '4.3.0', '<')) {
  503. $string = mysql_escape_string($string);
  504. } else {
  505. $string = mysql_real_escape_string($string);
  506. }
  507. return $string;
  508. }
  509. }
  510. ?>