| [ Index ] |
PHP Cross Reference of Joomla 1.5.26 DE |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * @version $Id: filterinput.php 19339 2010-11-03 14:52:38Z ian $ 4 * @package Joomla.Framework 5 * @subpackage Filter 6 * @copyright Copyright (C) 2005 - 2010 Open Source Matters. All rights reserved. 7 * @license GNU/GPL, see LICENSE.php 8 * Joomla! is free software. This version may have been modified pursuant to the 9 * GNU General Public License, and as distributed it includes or is derivative 10 * of works licensed under the GNU General Public License or other free or open 11 * source software licenses. See COPYRIGHT.php for copyright notices and 12 * details. 13 */ 14 15 // Check to ensure this file is within the rest of the framework 16 defined('JPATH_BASE') or die(); 17 18 /** 19 * JFilterInput is a class for filtering input from any data source 20 * 21 * Forked from the php input filter library by: Daniel Morris <dan@rootcube.com> 22 * Original Contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie. 23 * 24 * @package Joomla.Framework 25 * @subpackage Filter 26 * @since 1.5 27 */ 28 class JFilterInput extends JObject 29 { 30 var $tagsArray; // default = empty array 31 var $attrArray; // default = empty array 32 33 var $tagsMethod; // default = 0 34 var $attrMethod; // default = 0 35 36 var $xssAuto; // default = 1 37 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'); 38 var $attrBlacklist = array ('action', 'background', 'codebase', 'dynsrc', 'lowsrc'); // also will strip ALL event handlers 39 40 /** 41 * Constructor for inputFilter class. Only first parameter is required. 42 * 43 * @access protected 44 * @param array $tagsArray list of user-defined tags 45 * @param array $attrArray list of user-defined attributes 46 * @param int $tagsMethod WhiteList method = 0, BlackList method = 1 47 * @param int $attrMethod WhiteList method = 0, BlackList method = 1 48 * @param int $xssAuto Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1 49 * @since 1.5 50 */ 51 function __construct($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1) 52 { 53 // Make sure user defined arrays are in lowercase 54 $tagsArray = array_map('strtolower', (array) $tagsArray); 55 $attrArray = array_map('strtolower', (array) $attrArray); 56 57 // Assign member variables 58 $this->tagsArray = $tagsArray; 59 $this->attrArray = $attrArray; 60 $this->tagsMethod = $tagsMethod; 61 $this->attrMethod = $attrMethod; 62 $this->xssAuto = $xssAuto; 63 } 64 65 /** 66 * Returns a reference to an input filter object, only creating it if it doesn't already exist. 67 * 68 * This method must be invoked as: 69 * <pre> $filter = & JFilterInput::getInstance();</pre> 70 * 71 * @static 72 * @param array $tagsArray list of user-defined tags 73 * @param array $attrArray list of user-defined attributes 74 * @param int $tagsMethod WhiteList method = 0, BlackList method = 1 75 * @param int $attrMethod WhiteList method = 0, BlackList method = 1 76 * @param int $xssAuto Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1 77 * @return object The JFilterInput object. 78 * @since 1.5 79 */ 80 function & getInstance($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1) 81 { 82 static $instances; 83 84 $sig = md5(serialize(array($tagsArray,$attrArray,$tagsMethod,$attrMethod,$xssAuto))); 85 86 if (!isset ($instances)) { 87 $instances = array(); 88 } 89 90 if (empty ($instances[$sig])) { 91 $instances[$sig] = new JFilterInput($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto); 92 } 93 94 return $instances[$sig]; 95 } 96 97 /** 98 * Method to be called by another php script. Processes for XSS and 99 * specified bad code. 100 * 101 * @access public 102 * @param mixed $source Input string/array-of-string to be 'cleaned' 103 * @param string $type Return type for the variable (INT, FLOAT, BOOLEAN, WORD, ALNUM, CMD, BASE64, STRING, ARRAY, PATH, NONE) 104 * @return mixed 'Cleaned' version of input parameter 105 * @since 1.5 106 * @static 107 */ 108 function clean($source, $type='string') 109 { 110 // Handle the type constraint 111 switch (strtoupper($type)) 112 { 113 case 'INT' : 114 case 'INTEGER' : 115 // Only use the first integer value 116 preg_match('/-?[0-9]+/', (string) $source, $matches); 117 $result = @ (int) $matches[0]; 118 break; 119 120 case 'FLOAT' : 121 case 'DOUBLE' : 122 // Only use the first floating point value 123 preg_match('/-?[0-9]+(\.[0-9]+)?/', (string) $source, $matches); 124 $result = @ (float) $matches[0]; 125 break; 126 127 case 'BOOL' : 128 case 'BOOLEAN' : 129 $result = (bool) $source; 130 break; 131 132 case 'WORD' : 133 $result = (string) preg_replace( '/[^A-Z_]/i', '', $source ); 134 break; 135 136 case 'ALNUM' : 137 $result = (string) preg_replace( '/[^A-Z0-9]/i', '', $source ); 138 break; 139 140 case 'CMD' : 141 $result = (string) preg_replace( '/[^A-Z0-9_\.-]/i', '', $source ); 142 $result = ltrim($result, '.'); 143 break; 144 145 case 'BASE64' : 146 $result = (string) preg_replace( '/[^A-Z0-9\/+=]/i', '', $source ); 147 break; 148 149 case 'STRING' : 150 // Check for static usage and assign $filter the proper variable 151 if(isset($this) && is_a( $this, 'JFilterInput' )) { 152 $filter =& $this; 153 } else { 154 $filter =& JFilterInput::getInstance(); 155 } 156 $result = (string) $filter->_remove($filter->_decode((string) $source)); 157 break; 158 159 case 'ARRAY' : 160 $result = (array) $source; 161 break; 162 163 case 'PATH' : 164 $pattern = '/^[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*([\\\\\/][A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$/'; 165 preg_match($pattern, (string) $source, $matches); 166 $result = @ (string) $matches[0]; 167 break; 168 169 case 'USERNAME' : 170 $result = (string) preg_replace( '/[\x00-\x1F\x7F<>"\'%&]/', '', $source ); 171 break; 172 173 case 'MENUTYPE': 174 $result = str_replace('-', ' ', $source); 175 $lang = &JFactory::getLanguage(); 176 $result = $lang->transliterate($result); 177 $result = (string) preg_replace( 178 array('/\s+/','/[^A-Za-z0-9\-\_ ]/'), array('-',''), $result 179 ); 180 $result = trim($result); 181 break; 182 183 184 default : 185 // Check for static usage and assign $filter the proper variable 186 if(is_object($this) && get_class($this) == 'JFilterInput') { 187 $filter =& $this; 188 } else { 189 $filter =& JFilterInput::getInstance(); 190 } 191 // Are we dealing with an array? 192 if (is_array($source)) { 193 foreach ($source as $key => $value) 194 { 195 // filter element for XSS and other 'bad' code etc. 196 if (is_string($value)) { 197 $source[$key] = $filter->_remove($filter->_decode($value)); 198 } 199 } 200 $result = $source; 201 } else { 202 // Or a string? 203 if (is_string($source) && !empty ($source)) { 204 // filter source for XSS and other 'bad' code etc. 205 $result = $filter->_remove($filter->_decode($source)); 206 } else { 207 // Not an array or string.. return the passed parameter 208 $result = $source; 209 } 210 } 211 break; 212 } 213 return $result; 214 } 215 216 /** 217 * Function to determine if contents of an attribute is safe 218 * 219 * @static 220 * @param array $attrSubSet A 2 element array for attributes name,value 221 * @return boolean True if bad code is detected 222 * @since 1.5 223 */ 224 function checkAttribute($attrSubSet) 225 { 226 $attrSubSet[0] = strtolower($attrSubSet[0]); 227 $attrSubSet[1] = strtolower($attrSubSet[1]); 228 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)); 229 } 230 231 /** 232 * Internal method to iteratively remove all unwanted tags and attributes 233 * 234 * @access protected 235 * @param string $source Input string to be 'cleaned' 236 * @return string 'Cleaned' version of input parameter 237 * @since 1.5 238 */ 239 function _remove($source) 240 { 241 $loopCounter = 0; 242 243 // Iteration provides nested tag protection 244 while ($source != $this->_cleanTags($source)) 245 { 246 $source = $this->_cleanTags($source); 247 $loopCounter ++; 248 } 249 return $source; 250 } 251 252 /** 253 * Internal method to strip a string of certain tags 254 * 255 * @access protected 256 * @param string $source Input string to be 'cleaned' 257 * @return string 'Cleaned' version of input parameter 258 * @since 1.5 259 */ 260 function _cleanTags($source) 261 { 262 /* 263 * In the beginning we don't really have a tag, so everything is 264 * postTag 265 */ 266 $preTag = null; 267 $postTag = $source; 268 $currentSpace = false; 269 $attr = ''; // moffats: setting to null due to issues in migration system - undefined variable errors 270 271 // Is there a tag? If so it will certainly start with a '<' 272 $tagOpen_start = strpos($source, '<'); 273 274 while ($tagOpen_start !== false) 275 { 276 // Get some information about the tag we are processing 277 $preTag .= substr($postTag, 0, $tagOpen_start); 278 $postTag = substr($postTag, $tagOpen_start); 279 $fromTagOpen = substr($postTag, 1); 280 $tagOpen_end = strpos($fromTagOpen, '>'); 281 282 // Let's catch any non-terminated tags and skip over them 283 if ($tagOpen_end === false) { 284 $postTag = substr($postTag, $tagOpen_start +1); 285 $tagOpen_start = strpos($postTag, '<'); 286 continue; 287 } 288 289 // Do we have a nested tag? 290 $tagOpen_nested = strpos($fromTagOpen, '<'); 291 $tagOpen_nested_end = strpos(substr($postTag, $tagOpen_end), '>'); 292 if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end)) { 293 $preTag .= substr($postTag, 0, ($tagOpen_nested +1)); 294 $postTag = substr($postTag, ($tagOpen_nested +1)); 295 $tagOpen_start = strpos($postTag, '<'); 296 continue; 297 } 298 299 // Lets get some information about our tag and setup attribute pairs 300 $tagOpen_nested = (strpos($fromTagOpen, '<') + $tagOpen_start +1); 301 $currentTag = substr($fromTagOpen, 0, $tagOpen_end); 302 $tagLength = strlen($currentTag); 303 $tagLeft = $currentTag; 304 $attrSet = array (); 305 $currentSpace = strpos($tagLeft, ' '); 306 307 // Are we an open tag or a close tag? 308 if (substr($currentTag, 0, 1) == '/') { 309 // Close Tag 310 $isCloseTag = true; 311 list ($tagName) = explode(' ', $currentTag); 312 $tagName = substr($tagName, 1); 313 } else { 314 // Open Tag 315 $isCloseTag = false; 316 list ($tagName) = explode(' ', $currentTag); 317 } 318 319 /* 320 * Exclude all "non-regular" tagnames 321 * OR no tagname 322 * OR remove if xssauto is on and tag is blacklisted 323 */ 324 if ((!preg_match("/^[a-z][a-z0-9]*$/i", $tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto))) { 325 $postTag = substr($postTag, ($tagLength +2)); 326 $tagOpen_start = strpos($postTag, '<'); 327 // Strip tag 328 continue; 329 } 330 331 /* 332 * Time to grab any attributes from the tag... need this section in 333 * case attributes have spaces in the values. 334 */ 335 while ($currentSpace !== false) 336 { 337 $attr = ''; 338 $fromSpace = substr($tagLeft, ($currentSpace +1)); 339 $nextSpace = strpos($fromSpace, ' '); 340 $openQuotes = strpos($fromSpace, '"'); 341 $closeQuotes = strpos(substr($fromSpace, ($openQuotes +1)), '"') + $openQuotes +1; 342 343 // Do we have an attribute to process? [check for equal sign] 344 if (strpos($fromSpace, '=') !== false) { 345 /* 346 * If the attribute value is wrapped in quotes we need to 347 * grab the substring from the closing quote, otherwise grab 348 * till the next space 349 */ 350 if (($openQuotes !== false) && (strpos(substr($fromSpace, ($openQuotes +1)), '"') !== false)) { 351 $attr = substr($fromSpace, 0, ($closeQuotes +1)); 352 } else { 353 $attr = substr($fromSpace, 0, $nextSpace); 354 } 355 } else { 356 /* 357 * No more equal signs so add any extra text in the tag into 358 * the attribute array [eg. checked] 359 */ 360 if ($fromSpace != '/') { 361 $attr = substr($fromSpace, 0, $nextSpace); 362 } 363 } 364 365 // Last Attribute Pair 366 if (!$attr && $fromSpace != '/') { 367 $attr = $fromSpace; 368 } 369 370 // Add attribute pair to the attribute array 371 $attrSet[] = $attr; 372 373 // Move search point and continue iteration 374 $tagLeft = substr($fromSpace, strlen($attr)); 375 $currentSpace = strpos($tagLeft, ' '); 376 } 377 378 // Is our tag in the user input array? 379 $tagFound = in_array(strtolower($tagName), $this->tagsArray); 380 381 // If the tag is allowed lets append it to the output string 382 if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod)) { 383 384 // Reconstruct tag with allowed attributes 385 if (!$isCloseTag) { 386 // Open or Single tag 387 $attrSet = $this->_cleanAttributes($attrSet); 388 $preTag .= '<'.$tagName; 389 for ($i = 0; $i < count($attrSet); $i ++) 390 { 391 $preTag .= ' '.$attrSet[$i]; 392 } 393 394 // Reformat single tags to XHTML 395 if (strpos($fromTagOpen, '</'.$tagName)) { 396 $preTag .= '>'; 397 } else { 398 $preTag .= ' />'; 399 } 400 } else { 401 // Closing Tag 402 $preTag .= '</'.$tagName.'>'; 403 } 404 } 405 406 // Find next tag's start and continue iteration 407 $postTag = substr($postTag, ($tagLength +2)); 408 $tagOpen_start = strpos($postTag, '<'); 409 } 410 411 // Append any code after the end of tags and return 412 if ($postTag != '<') { 413 $preTag .= $postTag; 414 } 415 return $preTag; 416 } 417 418 /** 419 * Internal method to strip a tag of certain attributes 420 * 421 * @access protected 422 * @param array $attrSet Array of attribute pairs to filter 423 * @return array Filtered array of attribute pairs 424 * @since 1.5 425 */ 426 function _cleanAttributes($attrSet) 427 { 428 // Initialize variables 429 $newSet = array(); 430 431 // Iterate through attribute pairs 432 for ($i = 0; $i < count($attrSet); $i ++) 433 { 434 // Skip blank spaces 435 if (!$attrSet[$i]) { 436 continue; 437 } 438 439 // Split into name/value pairs 440 $attrSubSet = explode('=', trim($attrSet[$i]), 2); 441 list ($attrSubSet[0]) = explode(' ', $attrSubSet[0]); 442 443 /* 444 * Remove all "non-regular" attribute names 445 * AND blacklisted attributes 446 */ 447 if ((!preg_match('/[a-z]*$/i', $attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on')))) { 448 continue; 449 } 450 451 // XSS attribute value filtering 452 if ($attrSubSet[1]) { 453 // strips unicode, hex, etc 454 $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]); 455 // strip normal newline within attr value 456 $attrSubSet[1] = preg_replace('/[\n\r]/', '', $attrSubSet[1]); 457 // strip double quotes 458 $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]); 459 // convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value) 460 if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'")) { 461 $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2)); 462 } 463 // strip slashes 464 $attrSubSet[1] = stripslashes($attrSubSet[1]); 465 } 466 467 // Autostrip script tags 468 if (JFilterInput::checkAttribute($attrSubSet)) { 469 continue; 470 } 471 472 // Is our attribute in the user input array? 473 $attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray); 474 475 // If the tag is allowed lets keep it 476 if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod)) { 477 478 // Does the attribute have a value? 479 if ($attrSubSet[1]) { 480 $newSet[] = $attrSubSet[0].'="'.$attrSubSet[1].'"'; 481 } elseif ($attrSubSet[1] == "0") { 482 /* 483 * Special Case 484 * Is the value 0? 485 */ 486 $newSet[] = $attrSubSet[0].'="0"'; 487 } else { 488 $newSet[] = $attrSubSet[0].'="'.$attrSubSet[0].'"'; 489 } 490 } 491 } 492 return $newSet; 493 } 494 495 /** 496 * Try to convert to plaintext 497 * 498 * @access protected 499 * @param string $source 500 * @return string Plaintext string 501 * @since 1.5 502 */ 503 function _decode($source) 504 { 505 // entity decode 506 $trans_tbl = get_html_translation_table(HTML_ENTITIES); 507 foreach($trans_tbl as $k => $v) { 508 $ttr[$v] = utf8_encode($k); 509 } 510 $source = strtr($source, $ttr); 511 // convert decimal 512 $source = preg_replace('/&#(\d+);/me', "utf8_encode(chr(\\1))", $source); // decimal notation 513 // convert hex 514 $source = preg_replace('/&#x([a-f0-9]+);/mei', "utf8_encode(chr(0x\\1))", $source); // hex notation 515 return $source; 516 } 517 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Mar 28 15:54:07 2012 | Cross-referenced by PHPXref 0.7.1 |