PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/includes/phpInputFilter/class.inputfilter.php

https://bitbucket.org/dgough/annamaria-daneswood-25102012
PHP | 552 lines | 262 code | 41 blank | 249 comment | 61 complexity | 61a9a3fd6f24e9a552f0fe77d05edff9 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  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. //print "T: $preTag\n";
  291. }
  292. /*
  293. * Append any code after the end of tags and return
  294. */
  295. if ($postTag != '<')
  296. {
  297. $preTag .= $postTag;
  298. }
  299. return $preTag;
  300. }
  301. /**
  302. * Internal method to strip a tag of certain attributes
  303. *
  304. * @access protected
  305. * @param array $attrSet Array of attribute pairs to filter
  306. * @return array $newSet Filtered array of attribute pairs
  307. */
  308. function filterAttr($attrSet)
  309. {
  310. /*
  311. * Initialize variables
  312. */
  313. $newSet = array ();
  314. /*
  315. * Iterate through attribute pairs
  316. */
  317. for ($i = 0; $i < count($attrSet); $i ++)
  318. {
  319. /*
  320. * Skip blank spaces
  321. */
  322. if (!$attrSet[$i])
  323. {
  324. continue;
  325. }
  326. /*
  327. * Split into name/value pairs
  328. */
  329. $attrSubSet = explode('=', trim($attrSet[$i]), 2);
  330. list ($attrSubSet[0]) = explode(' ', $attrSubSet[0]);
  331. /*
  332. * Remove all "non-regular" attribute names
  333. * AND blacklisted attributes
  334. */
  335. if ((!eregi("^[a-z]*$", $attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr(strtolower($attrSubSet[0]), 0, 2) == 'on'))))
  336. {
  337. continue;
  338. }
  339. /*
  340. * XSS attribute value filtering
  341. */
  342. if ($attrSubSet[1])
  343. {
  344. // strips unicode, hex, etc
  345. $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
  346. // strip normal newline within attr value
  347. $attrSubSet[1] = preg_replace('/\s+/', '', $attrSubSet[1]);
  348. // strip double quotes
  349. $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);
  350. // [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value)
  351. if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'"))
  352. {
  353. $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
  354. }
  355. // strip slashes
  356. $attrSubSet[1] = stripslashes($attrSubSet[1]);
  357. }
  358. /*
  359. * Autostrip script tags
  360. */
  361. if (InputFilter :: badAttributeValue($attrSubSet))
  362. {
  363. continue;
  364. }
  365. /*
  366. * Is our attribute in the user input array?
  367. */
  368. $attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);
  369. /*
  370. * If the tag is allowed lets keep it
  371. */
  372. if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod))
  373. {
  374. /*
  375. * Does the attribute have a value?
  376. */
  377. if ($attrSubSet[1])
  378. {
  379. $newSet[] = $attrSubSet[0].'="'.$attrSubSet[1].'"';
  380. }
  381. elseif ($attrSubSet[1] == "0")
  382. {
  383. /*
  384. * Special Case
  385. * Is the value 0?
  386. */
  387. $newSet[] = $attrSubSet[0].'="0"';
  388. } else
  389. {
  390. $newSet[] = $attrSubSet[0].'="'.$attrSubSet[0].'"';
  391. }
  392. }
  393. }
  394. return $newSet;
  395. }
  396. /**
  397. * Function to determine if contents of an attribute is safe
  398. *
  399. * @access protected
  400. * @param array $attrSubSet A 2 element array for attributes name,value
  401. * @return boolean True if bad code is detected
  402. */
  403. function badAttributeValue($attrSubSet)
  404. {
  405. $attrSubSet[0] = strtolower($attrSubSet[0]);
  406. $attrSubSet[1] = strtolower($attrSubSet[1]);
  407. 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));
  408. }
  409. /**
  410. * Try to convert to plaintext
  411. *
  412. * @access protected
  413. * @param string $source
  414. * @return string Plaintext string
  415. */
  416. function decode($source)
  417. {
  418. // url decode
  419. $source = html_entity_decode($source, ENT_QUOTES, "ISO-8859-1");
  420. // convert decimal
  421. $source = preg_replace('/&#(\d+);/me', "chr(\\1)", $source); // decimal notation
  422. // convert hex
  423. $source = preg_replace('/&#x([a-f0-9]+);/mei', "chr(0x\\1)", $source); // hex notation
  424. return $source;
  425. }
  426. /**
  427. * Method to be called by another php script. Processes for SQL injection
  428. *
  429. * @access public
  430. * @param mixed $source input string/array-of-string to be 'cleaned'
  431. * @param resource $connection - An open MySQL connection
  432. * @return string 'cleaned' version of input parameter
  433. */
  434. function safeSQL($source, & $connection)
  435. {
  436. // clean all elements in this array
  437. if (is_array($source))
  438. {
  439. foreach ($source as $key => $value)
  440. {
  441. // filter element for SQL injection
  442. if (is_string($value))
  443. {
  444. $source[$key] = $this->quoteSmart($this->decode($value), $connection);
  445. }
  446. }
  447. return $source;
  448. // clean this string
  449. } else
  450. if (is_string($source))
  451. {
  452. // filter source for SQL injection
  453. if (is_string($source))
  454. {
  455. return $this->quoteSmart($this->decode($source), $connection);
  456. }
  457. // return parameter as given
  458. } else
  459. {
  460. return $source;
  461. }
  462. }
  463. /**
  464. * Method to escape a string
  465. *
  466. * @author Chris Tobin
  467. * @author Daniel Morris
  468. *
  469. * @access protected
  470. * @param string $source
  471. * @param resource $connection An open MySQL connection
  472. * @return string Escaped string
  473. */
  474. function quoteSmart($source, & $connection)
  475. {
  476. /*
  477. * Strip escaping slashes if necessary
  478. */
  479. if (get_magic_quotes_gpc())
  480. {
  481. $source = stripslashes($source);
  482. }
  483. /*
  484. * Escape numeric and text values
  485. */
  486. $source = $this->escapeString($source, $connection);
  487. return $source;
  488. }
  489. /**
  490. * @author Chris Tobin
  491. * @author Daniel Morris
  492. *
  493. * @access protected
  494. * @param string $source
  495. * @param resource $connection An open MySQL connection
  496. * @return string Escaped string
  497. */
  498. function escapeString($string, & $connection) {
  499. /*
  500. * Use the appropriate escape string depending upon which version of php
  501. * you are running
  502. */
  503. if (version_compare(phpversion(), '4.3.0', '<')) {
  504. $string = mysql_escape_string($string);
  505. } else {
  506. $string = mysql_real_escape_string($string);
  507. }
  508. return $string;
  509. }
  510. }
  511. ?>