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

/administrator/components/com_virtuemart/classes/phpInputFilter/class.inputfilter.php

https://bitbucket.org/dgough/annamaria-daneswood-25102012
PHP | 629 lines | 310 code | 58 blank | 261 comment | 72 complexity | 3b2f7c8737e9cc2f3b9e8f8a1ab19cf4 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. if( !defined( '_VALID_MOS' ) && !defined( '_JEXEC' ) ) die( 'Direct Access to '.basename(__FILE__).' is not allowed.' );
  3. /**
  4. * @version $Id: class.inputfilter.php 1444 2008-07-01 19:42:37Z soeren_nb $
  5. * @package VirtueMart
  6. * @subpackage phpInputFilter
  7. * @copyright Copyright (C) 2004-2008 soeren - All rights reserved.
  8. * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.php
  9. * VirtueMart is free software. This version may have been modified pursuant
  10. * to the GNU General Public License, and as distributed it includes or
  11. * is derivative of works licensed under the GNU General Public License or
  12. * other free or open source software licenses.
  13. * See /administrator/components/com_virtuemart/COPYRIGHT.php for copyright notices and details.
  14. *
  15. * http://virtuemart.net
  16. */
  17. /** @class: InputFilter (PHP4 & PHP5, with comments)
  18. * @project: PHP Input Filter
  19. * @date: 10-05-2005
  20. * @version: 1.2.2_php4/php5
  21. * @author: Daniel Morris
  22. * @contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie.
  23. * @copyright: Daniel Morris
  24. * @email: dan@rootcube.com
  25. * @license: GNU General Public License (GPL)
  26. */
  27. class vmInputFilter {
  28. var $tagsArray; // default = empty array
  29. var $safehtmlTags = array('a','abbr','acronym','address','b','bdo','big','blockquote','br','button','caption','center','cite','code','col','colgroup','dd','del','dfn','dir','div','dl','dt','em','fieldset','font','form','h1','h2','h3','h4','h5','h6','hr','i','iframe','img','input','ins','isindex','kbd','label','legend','li','link','map','menu','ol','optgroup','option','p','pre','q','s','samp','select','small','span','strike','strong','style','sub','sup','table','tbody','td','textarea','tfoot','th','thead','title','tr','tt','u','ul','var');
  30. var $attrArray; // default = empty array
  31. var $tagsMethod; // default = 0
  32. var $attrMethod; // default = 0
  33. var $xssAuto; // default = 1
  34. 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');
  35. var $attrBlacklist = array('action', 'background', 'codebase', 'dynsrc', 'lowsrc'); // also will strip ALL event handlers
  36. /**
  37. * Constructor for inputFilter class. Only first parameter is required.
  38. * @access constructor
  39. * @param Array $tagsArray - list of user-defined tags
  40. * @param Array $attrArray - list of user-defined attributes
  41. * @param int $tagsMethod - 0= allow just user-defined, 1= allow all but user-defined
  42. * @param int $attrMethod - 0= allow just user-defined, 1= allow all but user-defined
  43. * @param int $xssAuto - 0= only auto clean essentials, 1= allow clean blacklisted tags/attr
  44. */
  45. function vmInputFilter($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1) {
  46. // make sure user defined arrays are in lowercase
  47. for ($i = 0; $i < count($tagsArray); $i++) $tagsArray[$i] = strtolower($tagsArray[$i]);
  48. for ($i = 0; $i < count($attrArray); $i++) $attrArray[$i] = strtolower($attrArray[$i]);
  49. // assign to member vars
  50. $this->tagsArray = (array) $tagsArray;
  51. $this->attrArray = (array) $attrArray;
  52. $this->tagsMethod = $tagsMethod;
  53. $this->attrMethod = $attrMethod;
  54. $this->xssAuto = $xssAuto;
  55. }
  56. /**
  57. * Returns a reference to an input filter object, only creating it if it doesn't already exist.
  58. *
  59. * This method must be invoked as:
  60. * <pre> $filter = & vmInputFilter::getInstance();</pre>
  61. *
  62. * @static
  63. * @param array $tagsArray list of user-defined tags
  64. * @param array $attrArray list of user-defined attributes
  65. * @param int $tagsMethod WhiteList method = 0, BlackList method = 1
  66. * @param int $attrMethod WhiteList method = 0, BlackList method = 1
  67. * @param int $xssAuto Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
  68. * @return object The JFilterInput object.
  69. * @since 1.1
  70. */
  71. function & getInstance($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1) {
  72. static $instances;
  73. $sig = md5(serialize(array($tagsArray,$attrArray,$tagsMethod,$attrMethod,$xssAuto)));
  74. if (!isset ($instances)) {
  75. $instances = array();
  76. }
  77. if (empty ($instances[$sig])) {
  78. $instances[$sig] = new vmInputFilter($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto);
  79. }
  80. return $instances[$sig];
  81. }
  82. /**
  83. * Method to be called by another php script. Processes for XSS and
  84. * specified bad code.
  85. *
  86. * @access public
  87. * @param mixed $source Input string/array-of-string to be 'cleaned'
  88. * @param string $type Return type for the variable (INT, FLOAT, BOOLEAN, WORD, ALNUM, CMD, BASE64, STRING, ARRAY, PATH, NONE)
  89. * @return mixed 'Cleaned' version of input parameter
  90. * @since 1.5
  91. */
  92. function clean($source, $type='string')
  93. {
  94. // Handle the type constraint
  95. switch (strtoupper($type))
  96. {
  97. case 'INT' :
  98. case 'INTEGER' :
  99. // Only use the first integer value
  100. preg_match('/-?[0-9]+/', (string) $source, $matches);
  101. $result = @ (int) $matches[0];
  102. break;
  103. case 'FLOAT' :
  104. case 'DOUBLE' :
  105. // Only use the first floating point value
  106. preg_match('/-?[0-9]+(\.[0-9]+)?/', (string) $source, $matches);
  107. $result = @ (float) $matches[0];
  108. break;
  109. case 'BOOL' :
  110. case 'BOOLEAN' :
  111. $result = (bool) $source;
  112. break;
  113. case 'WORD' :
  114. $result = (string) preg_replace( '/[^A-Z_]/i', '', $source );
  115. break;
  116. case 'ALNUM' :
  117. $result = (string) preg_replace( '/[^A-Z0-9]/i', '', $source );
  118. break;
  119. case 'CMD' :
  120. $result = (string) preg_replace( '/[^A-Z0-9_\.-]/i', '', $source );
  121. $result = ltrim($result, '.');
  122. break;
  123. case 'BASE64' :
  124. $result = (string) preg_replace( '/[^A-Z0-9\/+=]/i', '', $source );
  125. break;
  126. case 'STRING' :
  127. $result = (string) $this->remove($this->decode((string) $source));
  128. break;
  129. case 'ARRAY' :
  130. $result = (array) $source;
  131. break;
  132. case 'PATH' :
  133. $pattern = '/^[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*([\\\\\/][A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$/';
  134. preg_match($pattern, (string) $source, $matches);
  135. $result = @ (string) $matches[0];
  136. break;
  137. case 'USERNAME' :
  138. $result = (string) preg_replace( '/[\x00-\x1F\x7F<>"\'%&]/', '', $source );
  139. break;
  140. default :
  141. // Are we dealing with an array?
  142. if (is_array($source)) {
  143. foreach ($source as $key => $value)
  144. {
  145. // filter element for XSS and other 'bad' code etc.
  146. if (is_string($value)) {
  147. $source[$key] = $this->remove($this->decode($value));
  148. }
  149. }
  150. $result = $source;
  151. } else {
  152. // Or a string?
  153. if (is_string($source) && !empty ($source)) {
  154. // filter source for XSS and other 'bad' code etc.
  155. $result = $this->remove($this->decode($source));
  156. } else {
  157. // Not an array or string.. return the passed parameter
  158. $result = $source;
  159. }
  160. }
  161. break;
  162. }
  163. return $result;
  164. }
  165. /**
  166. * Method to be called by another php script. Processes for XSS and specified bad code.
  167. * @access public
  168. * @param Mixed $source - input string/array-of-string to be 'cleaned'
  169. * @return String $source - 'cleaned' version of input parameter
  170. */
  171. function process($source) {
  172. // clean all elements in this array
  173. if (is_array($source)) {
  174. foreach($source as $key => $value)
  175. // filter element for XSS and other 'bad' code etc.
  176. if (is_string($value)) $source[$key] = $this->remove($this->decode($value));
  177. return $source;
  178. // clean this string
  179. } else if (is_string($source)) {
  180. // filter source for XSS and other 'bad' code etc.
  181. return $this->remove($this->decode($source));
  182. // return parameter as given
  183. } else return $source;
  184. }
  185. /**
  186. * Internal method to iteratively remove all unwanted tags and attributes
  187. *
  188. * @access protected
  189. * @param string $source Input string to be 'cleaned'
  190. * @return string $source 'cleaned' version of input parameter
  191. */
  192. function remove($source)
  193. {
  194. $loopCounter = 0;
  195. /*
  196. * Iteration provides nested tag protection
  197. */
  198. while ($source != $this->filterTags($source))
  199. {
  200. $source = $this->filterTags($source);
  201. $loopCounter ++;
  202. }
  203. return $source;
  204. }
  205. /**
  206. * Internal method to strip a string of certain tags
  207. *
  208. * @access protected
  209. * @param string $source Input string to be 'cleaned'
  210. * @return string $source 'cleaned' version of input parameter
  211. */
  212. function filterTags($source)
  213. {
  214. /*
  215. * In the beginning we don't really have a tag, so everything is
  216. * postTag
  217. */
  218. $preTag = null;
  219. $postTag = $source;
  220. /*
  221. * Is there a tag? If so it will certainly start with a '<'
  222. */
  223. $tagOpen_start = strpos($source, '<');
  224. while ($tagOpen_start !== false)
  225. {
  226. /*
  227. * Get some information about the tag we are processing
  228. */
  229. $preTag .= substr($postTag, 0, $tagOpen_start);
  230. $postTag = substr($postTag, $tagOpen_start);
  231. $fromTagOpen = substr($postTag, 1);
  232. $tagOpen_end = strpos($fromTagOpen, '>');
  233. /*
  234. * Let's catch any non-terminated tags and skip over them
  235. */
  236. if ($tagOpen_end === false)
  237. {
  238. $postTag = substr($postTag, $tagOpen_start +1);
  239. $tagOpen_start = strpos($postTag, '<');
  240. continue;
  241. }
  242. /*
  243. * Do we have a nested tag?
  244. */
  245. $tagOpen_nested = strpos($fromTagOpen, '<');
  246. //$tagOpen_nested_end = strpos(substr($postTag, $tagOpen_end), '>');
  247. if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end))
  248. {
  249. $preTag .= substr($postTag, 0, ($tagOpen_nested +1));
  250. $postTag = substr($postTag, ($tagOpen_nested +1));
  251. $tagOpen_start = strpos($postTag, '<');
  252. continue;
  253. }
  254. /*
  255. * Lets get some information about our tag and setup attribute pairs
  256. */
  257. $tagOpen_nested = (strpos($fromTagOpen, '<') + $tagOpen_start +1);
  258. $currentTag = substr($fromTagOpen, 0, $tagOpen_end);
  259. $tagLength = strlen($currentTag);
  260. $tagLeft = $currentTag;
  261. $attrSet = array ();
  262. $currentSpace = strpos($tagLeft, ' ');
  263. /*
  264. * Are we an open tag or a close tag?
  265. */
  266. if (substr($currentTag, 0, 1) == "/")
  267. {
  268. // Close Tag
  269. $isCloseTag = true;
  270. list ($tagName) = explode(' ', $currentTag);
  271. $tagName = substr($tagName, 1);
  272. } else
  273. {
  274. // Open Tag
  275. $isCloseTag = false;
  276. list ($tagName) = explode(' ', $currentTag);
  277. }
  278. /*
  279. * Exclude all "non-regular" tagnames
  280. * OR no tagname
  281. * OR remove if xssauto is on and tag is blacklisted
  282. */
  283. if ((!preg_match("/^[a-z][a-z0-9]*$/i", $tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto)))
  284. {
  285. $postTag = substr($postTag, ($tagLength +2));
  286. $tagOpen_start = strpos($postTag, '<');
  287. // Strip tag
  288. continue;
  289. }
  290. /*
  291. * Time to grab any attributes from the tag... need this section in
  292. * case attributes have spaces in the values.
  293. */
  294. while ($currentSpace !== false)
  295. {
  296. $fromSpace = substr($tagLeft, ($currentSpace +1));
  297. $nextSpace = strpos($fromSpace, ' ');
  298. $openQuotes = strpos($fromSpace, '"');
  299. $closeQuotes = strpos(substr($fromSpace, ($openQuotes +1)), '"') + $openQuotes +1;
  300. /*
  301. * Do we have an attribute to process? [check for equal sign]
  302. */
  303. if (strpos($fromSpace, '=') !== false)
  304. {
  305. /*
  306. * If the attribute value is wrapped in quotes we need to
  307. * grab the substring from the closing quote, otherwise grab
  308. * till the next space
  309. */
  310. if (($openQuotes !== false) && (strpos(substr($fromSpace, ($openQuotes +1)), '"') !== false))
  311. {
  312. $attr = substr($fromSpace, 0, ($closeQuotes +1));
  313. } else
  314. {
  315. $attr = substr($fromSpace, 0, $nextSpace);
  316. }
  317. } else
  318. {
  319. /*
  320. * No more equal signs so add any extra text in the tag into
  321. * the attribute array [eg. checked]
  322. */
  323. $attr = substr($fromSpace, 0, $nextSpace);
  324. }
  325. // Last Attribute Pair
  326. if (!$attr)
  327. {
  328. $attr = $fromSpace;
  329. }
  330. /*
  331. * Add attribute pair to the attribute array
  332. */
  333. $attrSet[] = $attr;
  334. /*
  335. * Move search point and continue iteration
  336. */
  337. $tagLeft = substr($fromSpace, strlen($attr));
  338. $currentSpace = strpos($tagLeft, ' ');
  339. }
  340. /*
  341. * Is our tag in the user input array?
  342. */
  343. $tagFound = in_array(strtolower($tagName), $this->tagsArray);
  344. /*
  345. * If the tag is allowed lets append it to the output string
  346. */
  347. if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod))
  348. {
  349. /*
  350. * Reconstruct tag with allowed attributes
  351. */
  352. if (!$isCloseTag)
  353. {
  354. // Open or Single tag
  355. $attrSet = $this->filterAttr($attrSet);
  356. $preTag .= '<'.$tagName;
  357. for ($i = 0; $i < count($attrSet); $i ++)
  358. {
  359. $preTag .= ' '.$attrSet[$i];
  360. }
  361. /*
  362. * Reformat single tags to XHTML
  363. */
  364. if (strpos($fromTagOpen, "</".$tagName))
  365. {
  366. $preTag .= '>';
  367. } else
  368. {
  369. $preTag .= ' />';
  370. }
  371. } else
  372. {
  373. // Closing Tag
  374. $preTag .= '</'.$tagName.'>';
  375. }
  376. }
  377. /*
  378. * Find next tag's start and continue iteration
  379. */
  380. $postTag = substr($postTag, ($tagLength +2));
  381. $tagOpen_start = strpos($postTag, '<');
  382. }
  383. /*
  384. * Append any code after the end of tags and return
  385. */
  386. if ($postTag != '<')
  387. {
  388. $preTag .= $postTag;
  389. }
  390. return $preTag;
  391. }
  392. /**
  393. * Internal method to strip a tag of certain attributes
  394. *
  395. * @access protected
  396. * @param array $attrSet Array of attribute pairs to filter
  397. * @return array $newSet Filtered array of attribute pairs
  398. */
  399. function filterAttr($attrSet)
  400. {
  401. /*
  402. * Initialize variables
  403. */
  404. $newSet = array ();
  405. /*
  406. * Iterate through attribute pairs
  407. */
  408. for ($i = 0; $i < count($attrSet); $i ++)
  409. {
  410. /*
  411. * Skip blank spaces
  412. */
  413. if (!$attrSet[$i])
  414. {
  415. continue;
  416. }
  417. /*
  418. * Split into name/value pairs
  419. */
  420. $attrSubSet = explode('=', trim($attrSet[$i]), 2);
  421. list ($attrSubSet[0]) = explode(' ', $attrSubSet[0]);
  422. /*
  423. * Remove all "non-regular" attribute names
  424. * AND blacklisted attributes
  425. */
  426. if ((!eregi("^[a-z]*$", $attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on'))))
  427. {
  428. continue;
  429. }
  430. /*
  431. * XSS attribute value filtering
  432. */
  433. if ($attrSubSet[1])
  434. {
  435. // strips unicode, hex, etc
  436. $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
  437. // strip normal newline and multiple space chars within attr value
  438. $attrSubSet[1] = str_replace(array("\r\n", "\r", "\n"), array(' ', ' ',' '), $attrSubSet[1]);
  439. $attrSubSet[1] = preg_replace('/\s\s+/', ' ', $attrSubSet[1]);
  440. // strip slashes
  441. $attrSubSet[1] = stripslashes($attrSubSet[1]);
  442. // strip double quotes
  443. $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);
  444. // [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value)
  445. if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'"))
  446. {
  447. $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
  448. }
  449. }
  450. /*
  451. * Autostrip script tags
  452. */
  453. if (vmInputFilter::badAttributeValue($attrSubSet))
  454. {
  455. continue;
  456. }
  457. /*
  458. * Is our attribute in the user input array?
  459. */
  460. $attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);
  461. /*
  462. * If the tag is allowed lets keep it
  463. */
  464. if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod))
  465. {
  466. /*
  467. * Does the attribute have a value?
  468. */
  469. if ($attrSubSet[1])
  470. {
  471. $newSet[] = $attrSubSet[0].'="'.$attrSubSet[1].'"';
  472. }
  473. elseif ($attrSubSet[1] == "0")
  474. {
  475. /*
  476. * Special Case
  477. * Is the value 0?
  478. */
  479. $newSet[] = $attrSubSet[0].'="0"';
  480. } else
  481. {
  482. $newSet[] = $attrSubSet[0].'="'.$attrSubSet[0].'"';
  483. }
  484. }
  485. }
  486. return $newSet;
  487. }
  488. /**
  489. * Function to determine if contents of an attribute is safe
  490. * @param Array A 2 element array for attribute [name] and [value]
  491. * @return Boolean True if bad code is detected
  492. */
  493. function badAttributeValue( $attrSubSet ) {
  494. $attrSubSet[0] = strtolower( $attrSubSet[0] );
  495. $attrSubSet[1] = strtolower( $attrSubSet[1] );
  496. return (
  497. ((strpos($attrSubSet[1], 'expression') !== false) && ($attrSubSet[0]) == 'style') ||
  498. (strpos($attrSubSet[1], 'javascript:') !== false) ||
  499. (strpos($attrSubSet[1], 'behaviour:') !== false) ||
  500. (strpos($attrSubSet[1], 'vbscript:') !== false) ||
  501. (strpos($attrSubSet[1], 'mocha:') !== false) ||
  502. (strpos($attrSubSet[1], 'livescript:') !== false)
  503. );
  504. }
  505. /**
  506. * Try to convert to plaintext
  507. * @access protected
  508. * @param String $source
  509. * @return String $source
  510. */
  511. function decode($source) {
  512. if( $source != "" ) { //bypass php html_entity_decode bug # 21338 on systems where unable to upgrade php
  513. // url decode
  514. $source = @html_entity_decode($source, ENT_QUOTES, vmGetCharset() );
  515. // convert decimal
  516. $source = preg_replace('/&#(\d+);/me',"chr(\\1)", $source); // decimal notation
  517. // convert hex
  518. $source = preg_replace('/&#x([a-f0-9]+);/mei',"chr(0x\\1)", $source); // hex notation
  519. }
  520. return $source;
  521. }
  522. /**
  523. * Method to be called by another php script. Processes for SQL injection
  524. * @access public
  525. * @param Mixed $source - input string/array-of-string to be 'cleaned'
  526. * @param Buffer $connection - An open MySQL connection
  527. * @return String $source - 'cleaned' version of input parameter
  528. */
  529. function safeSQL($source) {
  530. // clean all elements in this array
  531. if (is_array($source)) {
  532. foreach($source as $key => $value)
  533. // filter element for SQL injection
  534. if (is_string($value)) $source[$key] = $this->quoteSmart($this->decode($value));
  535. return $source;
  536. // clean this string
  537. } else if (is_string($source)) {
  538. // filter source for SQL injection
  539. if (is_string($source)) return $this->quoteSmart($this->decode($source));
  540. // return parameter as given
  541. } else return $source;
  542. }
  543. /**
  544. * @author Chris Tobin
  545. * @author Daniel Morris
  546. * @access protected
  547. * @param String $source
  548. * @param Resource $connection - An open MySQL connection
  549. * @return String $source
  550. */
  551. function quoteSmart($source) {
  552. // strip slashes
  553. if (get_magic_quotes_gpc()) $source = stripslashes($source);
  554. // quote both numeric and text
  555. $source = $this->escapeString($source);
  556. return $source;
  557. }
  558. /**
  559. * @author Chris Tobin
  560. * @author Daniel Morris
  561. * @access protected
  562. * @param String $source
  563. * @param Resource $connection - An open MySQL connection
  564. * @return String $source
  565. */
  566. function escapeString($string) {
  567. global $database;
  568. return $database->getEscaped( $string );
  569. }
  570. }
  571. ?>