phpinputfilter.inputfilter.php
上传用户:stephen_wu
上传日期:2008-07-05
资源大小:1757k
文件大小:13k
源码类别:

网络

开发平台:

Unix_Linux

  1. <?php
  2. /** @class: InputFilter (PHP4 & PHP5, with comments)
  3.   * @project: PHP Input Filter
  4.   * @date: 10-05-2005
  5.   * @version: 1.2.2_php4/php5
  6.   * @author: Daniel Morris
  7.   * @contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie.
  8.   * Beat from joomlapolis.com: tightened multi-level arrays: see //BB below
  9.   * @copyright: Daniel Morris
  10.   * @email: dan@rootcube.com
  11.   * @license: GNU General Public License (GPL) (v2 at time of publishing and usage in CB)
  12.   */
  13. class CBInputFilter {
  14. var $tagsArray; // default = empty array
  15. var $attrArray; // default = empty array
  16. var $tagsMethod; // default = 0
  17. var $attrMethod; // default = 0
  18. var $xssAuto;           // default = 1
  19. 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');
  20. var $attrBlacklist = array('action', 'background', 'codebase', 'dynsrc', 'lowsrc');  // also will strip ALL event handlers
  21. /** 
  22.   * Constructor for inputFilter class. Only first parameter is required.
  23.   * @access constructor
  24.   * @param Array $tagsArray - list of user-defined tags
  25.   * @param Array $attrArray - list of user-defined attributes
  26.   * @param int $tagsMethod - 0= allow just user-defined, 1= allow all but user-defined
  27.   * @param int $attrMethod - 0= allow just user-defined, 1= allow all but user-defined
  28.   * @param int $xssAuto - 0= only auto clean essentials, 1= allow clean blacklisted tags/attr
  29.   */
  30. function CBInputFilter($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1) {
  31. // make sure user defined arrays are in lowercase
  32. for ($i = 0; $i < count($tagsArray); $i++) $tagsArray[$i] = strtolower($tagsArray[$i]);
  33. for ($i = 0; $i < count($attrArray); $i++) $attrArray[$i] = strtolower($attrArray[$i]);
  34. // assign to member vars
  35. $this->tagsArray = (array) $tagsArray;
  36. $this->attrArray = (array) $attrArray;
  37. $this->tagsMethod = $tagsMethod;
  38. $this->attrMethod = $attrMethod;
  39. $this->xssAuto = $xssAuto;
  40. }
  41. /** 
  42.   * Method to be called by another php script. Processes for XSS and specified bad code.
  43.   * @access public
  44.   * @param Mixed $source - input string/array-of-string to be 'cleaned'
  45.   * @return String $source - 'cleaned' version of input parameter
  46.   */
  47. function process($source) {
  48. // clean all elements in this array
  49. if (is_array($source)) {
  50. foreach($source as $key => $value)
  51. // filter element for XSS and other 'bad' code etc.
  52. //BB: if (is_string($value)) $source[$key] = $this->remove($this->decode($value));
  53. $source[$key] = $this->process( $value ); //BB changed line before with this line to take in account multi-level arrays
  54. return $source;
  55. // clean this string
  56. } else if (is_string($source) && ( $source !== '' ) ) {
  57. // filter source for XSS and other 'bad' code etc.
  58. return $this->remove($this->decode($source));
  59. // return parameter as given
  60. } else return $source;
  61. }
  62. /** 
  63.   * Internal method to iteratively remove all unwanted tags and attributes
  64.   * @access protected
  65.   * @param String $source - input string to be 'cleaned'
  66.   * @return String $source - 'cleaned' version of input parameter
  67.   */
  68. function remove($source) {
  69. $loopCounter=0;
  70. // provides nested-tag protection
  71. while($source != $this->filterTags($source)) {
  72. $source = $this->filterTags($source);
  73. $loopCounter++;
  74. }
  75. return $source;
  76. }
  77. /** 
  78.   * Internal method to strip a string of certain tags
  79.   * @access protected
  80.   * @param String $source - input string to be 'cleaned'
  81.   * @return String $source - 'cleaned' version of input parameter
  82.   */
  83. function filterTags($source) {
  84. // filter pass setup
  85. $preTag = NULL;
  86. $postTag = $source;
  87. // find initial tag's position
  88. $tagOpen_start = strpos($source, '<');
  89. // interate through string until no tags left
  90. while($tagOpen_start !== FALSE) {
  91. // process tag interatively
  92. $preTag .= substr($postTag, 0, $tagOpen_start);
  93. $postTag = substr($postTag, $tagOpen_start);
  94. $fromTagOpen = substr($postTag, 1);
  95. // end of tag
  96. $tagOpen_end = strpos($fromTagOpen, '>');
  97. if ($tagOpen_end === false) {
  98. //- break;
  99. $postTag = substr( $postTag, $tagOpen_start + 1 ); //+
  100. $tagOpen_start = strpos( $postTag, '<' ); //+
  101. continue; //+
  102. }
  103. // next start of tag (for nested tag assessment)
  104. $tagOpen_nested = strpos($fromTagOpen, '<');
  105. if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end)) {
  106. $preTag .= substr($postTag, 0, ($tagOpen_nested+1));
  107. $postTag = substr($postTag, ($tagOpen_nested+1));
  108. $tagOpen_start = strpos($postTag, '<');
  109. continue;
  110. $tagOpen_nested = (strpos($fromTagOpen, '<') + $tagOpen_start + 1);
  111. $currentTag = substr($fromTagOpen, 0, $tagOpen_end);
  112. $tagLength = strlen($currentTag);
  113. //- if (!$tagOpen_end) {
  114. //- $preTag .= $postTag;
  115. //- $tagOpen_start = strpos($postTag, '<');
  116. //- }
  117. // iterate through tag finding attribute pairs - setup
  118. $tagLeft = $currentTag;
  119. $attrSet = array();
  120. $currentSpace = strpos($tagLeft, ' ');
  121. // is end tag
  122. if (substr($currentTag, 0, 1) == "/") {
  123. $isCloseTag = TRUE;
  124. list($tagName) = explode(' ', $currentTag);
  125. $tagName = substr($tagName, 1);
  126. // is start tag
  127. } else {
  128. $isCloseTag = FALSE;
  129. list($tagName) = explode(' ', $currentTag);
  130. }
  131. // excludes all "non-regular" tagnames OR no tagname OR remove if xssauto is on and tag is blacklisted
  132. if ((!preg_match("/^[a-z][a-z0-9]*$/i",$tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto))) { 
  133. $postTag = substr($postTag, ($tagLength + 2));
  134. $tagOpen_start = strpos($postTag, '<');
  135. // don't append this tag
  136. continue;
  137. }
  138. // this while is needed to support attribute values with spaces in!
  139. while ($currentSpace !== FALSE) {
  140. $fromSpace = substr($tagLeft, ($currentSpace+1));
  141. $nextSpace = strpos($fromSpace, ' ');
  142. $openQuotes = strpos($fromSpace, '"');
  143. $closeQuotes = strpos(substr($fromSpace, ($openQuotes+1)), '"') + $openQuotes + 1;
  144. // another equals exists
  145. if (strpos($fromSpace, '=') !== FALSE) {
  146. // opening and closing quotes exists
  147. if (($openQuotes !== FALSE) && (strpos(substr($fromSpace, ($openQuotes+1)), '"') !== FALSE))
  148. $attr = substr($fromSpace, 0, ($closeQuotes+1));
  149. // one or neither exist
  150. else $attr = substr($fromSpace, 0, $nextSpace);
  151. // no more equals exist
  152. } else $attr = substr($fromSpace, 0, $nextSpace);
  153. // last attr pair
  154. if (!$attr) $attr = $fromSpace;
  155. // add to attribute pairs array
  156. $attrSet[] = $attr;
  157. // next inc
  158. $tagLeft = substr($fromSpace, strlen($attr));
  159. $currentSpace = strpos($tagLeft, ' ');
  160. }
  161. // appears in array specified by user
  162. $tagFound = in_array(strtolower($tagName), $this->tagsArray);
  163. // remove this tag on condition
  164. if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod)) {
  165. // reconstruct tag with allowed attributes
  166. if (!$isCloseTag) {
  167. $attrSet = $this->filterAttr($attrSet);
  168. $preTag .= '<' . $tagName;
  169. for ($i = 0; $i < count($attrSet); $i++)
  170. $preTag .= ' ' . $attrSet[$i];
  171. // reformat single tags to XHTML
  172. if (strpos($fromTagOpen, "</" . $tagName)) $preTag .= '>';
  173. else $preTag .= ' />';
  174. // just the tagname
  175.     } else $preTag .= '</' . $tagName . '>';
  176. }
  177. // find next tag's start
  178. $postTag = substr($postTag, ($tagLength + 2));
  179. $tagOpen_start = strpos($postTag, '<');
  180. }
  181. // append any code after end of tags
  182. if ($postTag != '<') { //+
  183. $preTag .= $postTag;
  184. }
  185. return $preTag;
  186. }
  187. /** 
  188.   * Internal method to strip a tag of certain attributes
  189.   * @access protected
  190.   * @param Array $attrSet
  191.   * @return Array $newSet
  192.   */
  193. function filterAttr($attrSet) {
  194. $newSet = array();
  195. // process attributes
  196. for ($i = 0; $i <count($attrSet); $i++) {
  197. // skip blank spaces in tag
  198. if (!$attrSet[$i]) continue;
  199. // split into attr name and value
  200. $attrSubSet = explode('=', trim($attrSet[$i]), 2 ); //+ ',2'
  201. list($attrSubSet[0]) = explode(' ', $attrSubSet[0]);
  202. // removes all "non-regular" attr names AND also attr blacklisted
  203. if (( ! preg_match( "/^[a-zA-Z]*$/", $attrSubSet[0] ) ) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on'))) || ! isset($attrSubSet[1] )) //BB replaced eregi by pregmatch added last ! isset($attrSubSet[1]
  204. continue;
  205. // xss attr value filtering
  206. if ($attrSubSet[1]) {
  207. // strips unicode, hex, etc
  208. $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
  209. // strip normal newline within attr value
  210. $attrSubSet[1] = preg_replace('/s+/', '', $attrSubSet[1]);
  211. // strip double quotes
  212. $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);
  213. // [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value)
  214. if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'"))
  215. $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
  216. // strip slashes
  217. $attrSubSet[1] = stripslashes($attrSubSet[1]);
  218. }
  219. // auto strip attr's with "javascript:
  220. if ( ((strpos(strtolower($attrSubSet[1]), 'expression') !== false) && (strtolower($attrSubSet[0]) == 'style')) ||
  221. (strpos(strtolower($attrSubSet[1]), 'javascript:') !== false) ||
  222. (strpos(strtolower($attrSubSet[1]), 'behaviour:') !== false) ||
  223. (strpos(strtolower($attrSubSet[1]), 'vbscript:') !== false) ||
  224. (strpos(strtolower($attrSubSet[1]), 'mocha:') !== false) ||
  225. (strpos(strtolower($attrSubSet[1]), 'livescript:') !== false) 
  226. ) continue;
  227. // if matches user defined array
  228. $attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);
  229. // keep this attr on condition
  230. if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod)) {
  231. // attr has value
  232. if ($attrSubSet[1]) $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[1] . '"';
  233. // attr has decimal zero as value
  234. else if ($attrSubSet[1] == "0") $newSet[] = $attrSubSet[0] . '="0"';
  235. // reformat single attributes to XHTML
  236. else $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[0] . '"';
  237. }
  238. }
  239. return $newSet;
  240. }
  241. /** 
  242.   * Try to convert to plaintext
  243.   * @access protected
  244.   * @param String $source
  245.   * @return String $source
  246.   */
  247. function decode($source) {
  248. // url decode
  249. $source = html_entity_decode($source, ENT_QUOTES, "ISO-8859-1");
  250. // convert decimal
  251. $source = preg_replace('/&#(d+);/me',"chr(\1)", $source); // decimal notation
  252. // convert hex
  253. $source = preg_replace('/&#x([a-f0-9]+);/mei',"chr(0x\1)", $source); // hex notation
  254. return $source;
  255. }
  256. /** 
  257.   * Method to be called by another php script. Processes for SQL injection
  258.   * @access public
  259.   * @param Mixed $source - input string/array-of-string to be 'cleaned'
  260.   * @param Buffer $connection - An open MySQL connection
  261.   * @return String $source - 'cleaned' version of input parameter
  262.   */
  263. function safeSQL($source, &$connection) {
  264. // clean all elements in this array
  265. if (is_array($source)) {
  266. foreach($source as $key => $value)
  267. // filter element for SQL injection
  268. if (is_string($value)) $source[$key] = $this->quoteSmart($this->decode($value), $connection);
  269. return $source;
  270. // clean this string
  271. } else if (is_string($source)) {
  272. // filter source for SQL injection
  273. if (is_string($source)) return $this->quoteSmart($this->decode($source), $connection);
  274. // return parameter as given
  275. } else return $source;
  276. }
  277. /** 
  278.   * @author Chris Tobin
  279.   * @author Daniel Morris
  280.   * @access protected
  281.   * @param String $source
  282.   * @param Resource $connection - An open MySQL connection
  283.   * @return String $source
  284.   */
  285. function quoteSmart($source, &$connection) {
  286. // strip slashes
  287. if (get_magic_quotes_gpc()) $source = stripslashes($source);
  288. // quote both numeric and text
  289. $source = $this->escapeString($source, $connection);
  290. return $source;
  291. }
  292. /** 
  293.   * @author Chris Tobin
  294.   * @author Daniel Morris
  295.   * @access protected
  296.   * @param String $source
  297.   * @param Resource $connection - An open MySQL connection
  298.   * @return String $source
  299.   */
  300. function escapeString($string, &$connection) {
  301. // depreciated function
  302. if (version_compare(phpversion(),"4.3.0", "<")) mysql_escape_string($string);
  303. // current function
  304. else mysql_real_escape_string($string);
  305. return $string;
  306. }
  307. }
  308. ?>