[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Part of the Joomla Framework Filter Package 4 * 5 * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE 7 */ 8 9 namespace Joomla\Filter; 10 11 use Joomla\String\StringHelper; 12 13 /** 14 * InputFilter is a class for filtering input from any data source 15 * 16 * Forked from the php input filter library by: Daniel Morris <[email protected]> 17 * Original Contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie. 18 * 19 * @since 1.0 20 */ 21 class InputFilter 22 { 23 /** 24 * Defines the InputFilter instance should only allow the supplied list of HTML tags. 25 * 26 * @var integer 27 * @since 1.4.0 28 */ 29 const ONLY_ALLOW_DEFINED_TAGS = 0; 30 31 /** 32 * Defines the InputFilter instance should block the defined list of HTML tags and allow all others. 33 * 34 * @var integer 35 * @since 1.4.0 36 */ 37 const ONLY_BLOCK_DEFINED_TAGS = 1; 38 39 /** 40 * Defines the InputFilter instance should only allow the supplied list of attributes. 41 * 42 * @var integer 43 * @since 1.4.0 44 */ 45 const ONLY_ALLOW_DEFINED_ATTRIBUTES = 0; 46 47 /** 48 * Defines the InputFilter instance should block the defined list of attributes and allow all others. 49 * 50 * @var integer 51 * @since 1.4.0 52 */ 53 const ONLY_BLOCK_DEFINED_ATTRIBUTES = 1; 54 55 /** 56 * The array of permitted tags. 57 * 58 * @var array 59 * @since 1.0 60 */ 61 public $tagsArray; 62 63 /** 64 * The array of permitted tag attributes. 65 * 66 * @var array 67 * @since 1.0 68 */ 69 public $attrArray; 70 71 /** 72 * The method for sanitising tags 73 * 74 * @var integer 75 * @since 1.0 76 */ 77 public $tagsMethod; 78 79 /** 80 * The method for sanitising attributes 81 * 82 * @var integer 83 * @since 1.0 84 */ 85 public $attrMethod; 86 87 /** 88 * A flag for XSS checks. Only auto clean essentials = 0, Allow clean blocked tags/attr = 1 89 * 90 * @var integer 91 * @since 1.0 92 */ 93 public $xssAuto; 94 95 /** 96 * The list the blocked tags for the instance. 97 * 98 * @var string[] 99 * @since 1.0 100 */ 101 public $blockedTags = [ 102 'applet', 103 'body', 104 'bgsound', 105 'base', 106 'basefont', 107 'canvas', 108 'embed', 109 'frame', 110 'frameset', 111 'head', 112 'html', 113 'id', 114 'iframe', 115 'ilayer', 116 'layer', 117 'link', 118 'meta', 119 'name', 120 'object', 121 'script', 122 'style', 123 'title', 124 'xml', 125 ]; 126 127 /** 128 * The list of blocked tag attributes for the instance. 129 * 130 * @var string[] 131 * @since 1.0 132 */ 133 public $blockedAttributes = [ 134 'action', 135 'background', 136 'codebase', 137 'dynsrc', 138 'formaction', 139 'lowsrc', 140 ]; 141 142 /** 143 * A special list of blocked characters. 144 * 145 * @var string[] 146 * @since 1.3.3 147 */ 148 private $blockedChars = [ 149 '&tab;', 150 '&space;', 151 ':', 152 '&column;', 153 ]; 154 155 /** 156 * Constructor for InputFilter class. 157 * 158 * @param array $tagsArray List of permitted HTML tags 159 * @param array $attrArray List of permitted HTML tag attributes 160 * @param integer $tagsMethod Method for filtering tags, should be one of the `ONLY_*_DEFINED_TAGS` constants 161 * @param integer $attrMethod Method for filtering attributes, should be one of the `ONLY_*_DEFINED_ATTRIBUTES` constants 162 * @param integer $xssAuto Only auto clean essentials = 0, Allow clean blocked tags/attributes = 1 163 * 164 * @since 1.0 165 */ 166 public function __construct(array $tagsArray = [], array $attrArray = [], $tagsMethod = self::ONLY_ALLOW_DEFINED_TAGS, 167 $attrMethod = self::ONLY_ALLOW_DEFINED_ATTRIBUTES, $xssAuto = 1 168 ) 169 { 170 // Make sure user defined arrays are in lowercase 171 $tagsArray = array_map('strtolower', (array) $tagsArray); 172 $attrArray = array_map('strtolower', (array) $attrArray); 173 174 // Assign member variables 175 $this->tagsArray = $tagsArray; 176 $this->attrArray = $attrArray; 177 $this->tagsMethod = $tagsMethod; 178 $this->attrMethod = $attrMethod; 179 $this->xssAuto = $xssAuto; 180 } 181 182 /** 183 * Cleans the given input source based on the instance configuration and specified data type 184 * 185 * @param string|string[]|object $source Input string/array-of-string/object to be 'cleaned' 186 * @param string $type The return type for the variable: 187 * INT: An integer 188 * UINT: An unsigned integer 189 * FLOAT: A floating point number 190 * BOOLEAN: A boolean value 191 * WORD: A string containing A-Z or underscores only (not case sensitive) 192 * ALNUM: A string containing A-Z or 0-9 only (not case sensitive) 193 * CMD: A string containing A-Z, 0-9, underscores, periods or hyphens (not case 194 * sensitive) 195 * BASE64: A string containing A-Z, 0-9, forward slashes, plus or equals (not case 196 * sensitive) 197 * STRING: A fully decoded and sanitised string (default) 198 * HTML: A sanitised string 199 * ARRAY: An array 200 * PATH: A sanitised file path 201 * TRIM: A string trimmed from normal, non-breaking and multibyte spaces 202 * USERNAME: Do not use (use an application specific filter) 203 * RAW: The raw string is returned with no filtering 204 * unknown: An unknown filter will act like STRING. If the input is an array it will 205 * return an array of fully decoded and sanitised strings. 206 * 207 * @return mixed 'Cleaned' version of the `$source` parameter 208 * 209 * @since 1.0 210 */ 211 public function clean($source, $type = 'string') 212 { 213 $type = ucfirst(strtolower($type)); 214 215 if ($type === 'Array') 216 { 217 return (array) $source; 218 } 219 220 if ($type === 'Raw') 221 { 222 return $source; 223 } 224 225 if (\is_array($source)) 226 { 227 $result = []; 228 229 foreach ($source as $key => $value) 230 { 231 $result[$key] = $this->clean($value, $type); 232 } 233 234 return $result; 235 } 236 237 if (\is_object($source)) 238 { 239 foreach (get_object_vars($source) as $key => $value) 240 { 241 $source->$key = $this->clean($value, $type); 242 } 243 244 return $source; 245 } 246 247 $method = 'clean' . $type; 248 249 if (method_exists($this, $method)) 250 { 251 return $this->$method((string) $source); 252 } 253 254 // Unknown filter method 255 if (\is_string($source) && !empty($source)) 256 { 257 // Filter source for XSS and other 'bad' code etc. 258 return $this->cleanString($source); 259 } 260 261 // Not an array or string... return the passed parameter 262 return $source; 263 } 264 265 /** 266 * Function to determine if contents of an attribute are safe 267 * 268 * @param array $attrSubSet A 2 element array for attribute's name, value 269 * 270 * @return boolean True if bad code is detected 271 * 272 * @since 1.0 273 */ 274 public static function checkAttribute($attrSubSet) 275 { 276 $attrSubSet[0] = strtolower($attrSubSet[0]); 277 $attrSubSet[1] = html_entity_decode(strtolower($attrSubSet[1]), ENT_QUOTES | ENT_HTML401, 'UTF-8'); 278 279 return (strpos($attrSubSet[1], 'expression') !== false && $attrSubSet[0] === 'style') 280 || preg_match('/(?:(?:java|vb|live)script|behaviour|mocha)(?::|:|&column;)/', $attrSubSet[1]) !== 0; 281 } 282 283 /** 284 * Internal method to iteratively remove all unwanted tags and attributes 285 * 286 * @param string $source Input string to be 'cleaned' 287 * 288 * @return string 'Cleaned' version of input parameter 289 * 290 * @since 1.0 291 */ 292 protected function remove($source) 293 { 294 // Iteration provides nested tag protection 295 do 296 { 297 $temp = $source; 298 $source = $this->cleanTags($source); 299 } 300 while ($temp !== $source); 301 302 return $source; 303 } 304 305 /** 306 * Internal method to strip a string of disallowed tags 307 * 308 * @param string $source Input string to be 'cleaned' 309 * 310 * @return string 'Cleaned' version of input parameter 311 * 312 * @since 1.0 313 */ 314 protected function cleanTags($source) 315 { 316 // First, pre-process this for illegal characters inside attribute values 317 $source = $this->escapeAttributeValues($source); 318 319 // In the beginning we don't really have a tag, so everything is postTag 320 $preTag = null; 321 $postTag = $source; 322 $currentSpace = false; 323 324 // Setting to null to deal with undefined variables 325 $attr = ''; 326 327 // Is there a tag? If so it will certainly start with a '<'. 328 $tagOpenStart = StringHelper::strpos($source, '<'); 329 330 while ($tagOpenStart !== false) 331 { 332 // Get some information about the tag we are processing 333 $preTag .= StringHelper::substr($postTag, 0, $tagOpenStart); 334 $postTag = StringHelper::substr($postTag, $tagOpenStart); 335 $fromTagOpen = StringHelper::substr($postTag, 1); 336 $tagOpenEnd = StringHelper::strpos($fromTagOpen, '>'); 337 338 // Check for mal-formed tag where we have a second '<' before the first '>' 339 $nextOpenTag = (StringHelper::strlen($postTag) > $tagOpenStart) ? StringHelper::strpos($postTag, '<', $tagOpenStart + 1) : false; 340 341 if (($nextOpenTag !== false) && ($nextOpenTag < $tagOpenEnd)) 342 { 343 // At this point we have a mal-formed tag -- remove the offending open 344 $postTag = StringHelper::substr($postTag, 0, $tagOpenStart) . StringHelper::substr($postTag, $tagOpenStart + 1); 345 $tagOpenStart = StringHelper::strpos($postTag, '<'); 346 347 continue; 348 } 349 350 // Let's catch any non-terminated tags and skip over them 351 if ($tagOpenEnd === false) 352 { 353 $postTag = StringHelper::substr($postTag, $tagOpenStart + 1); 354 $tagOpenStart = StringHelper::strpos($postTag, '<'); 355 356 continue; 357 } 358 359 // Do we have a nested tag? 360 $tagOpenNested = StringHelper::strpos($fromTagOpen, '<'); 361 362 if (($tagOpenNested !== false) && ($tagOpenNested < $tagOpenEnd)) 363 { 364 $preTag .= StringHelper::substr($postTag, 1, $tagOpenNested); 365 $postTag = StringHelper::substr($postTag, ($tagOpenNested + 1)); 366 $tagOpenStart = StringHelper::strpos($postTag, '<'); 367 368 continue; 369 } 370 371 // Let's get some information about our tag and setup attribute pairs 372 $tagOpenNested = (StringHelper::strpos($fromTagOpen, '<') + $tagOpenStart + 1); 373 $currentTag = StringHelper::substr($fromTagOpen, 0, $tagOpenEnd); 374 $tagLength = StringHelper::strlen($currentTag); 375 $tagLeft = $currentTag; 376 $attrSet = []; 377 $currentSpace = StringHelper::strpos($tagLeft, ' '); 378 379 // Are we an open tag or a close tag? 380 if (StringHelper::substr($currentTag, 0, 1) === '/') 381 { 382 // Close Tag 383 $isCloseTag = true; 384 list($tagName) = explode(' ', $currentTag); 385 $tagName = StringHelper::substr($tagName, 1); 386 } 387 else 388 { 389 // Open Tag 390 $isCloseTag = false; 391 list($tagName) = explode(' ', $currentTag); 392 } 393 394 /* 395 * Exclude all "non-regular" tagnames 396 * OR no tagname 397 * OR remove if xssauto is on and tag is blocked 398 */ 399 if ((!preg_match('/^[a-z][a-z0-9]*$/i', $tagName)) 400 || (!$tagName) 401 || ((\in_array(strtolower($tagName), $this->blockedTags)) && $this->xssAuto)) 402 { 403 $postTag = StringHelper::substr($postTag, ($tagLength + 2)); 404 $tagOpenStart = StringHelper::strpos($postTag, '<'); 405 406 // Strip tag 407 continue; 408 } 409 410 /* 411 * Time to grab any attributes from the tag... need this section in 412 * case attributes have spaces in the values. 413 */ 414 while ($currentSpace !== false) 415 { 416 $attr = ''; 417 $fromSpace = StringHelper::substr($tagLeft, ($currentSpace + 1)); 418 $nextEqual = StringHelper::strpos($fromSpace, '='); 419 $nextSpace = StringHelper::strpos($fromSpace, ' '); 420 $openQuotes = StringHelper::strpos($fromSpace, '"'); 421 $closeQuotes = StringHelper::strpos(StringHelper::substr($fromSpace, ($openQuotes + 1)), '"') + $openQuotes + 1; 422 423 $startAtt = ''; 424 $startAttPosition = 0; 425 426 // Find position of equal and open quotes ignoring 427 if (preg_match('#\s*=\s*\"#', $fromSpace, $matches, \PREG_OFFSET_CAPTURE)) 428 { 429 // We have found an attribute, convert its byte position to a UTF-8 string length, using non-multibyte substr() 430 $stringBeforeAttr = substr($fromSpace, 0, $matches[0][1]); 431 $startAttPosition = StringHelper::strlen($stringBeforeAttr); 432 $startAtt = $matches[0][0]; 433 $closeQuotePos = StringHelper::strpos( 434 StringHelper::substr($fromSpace, ($startAttPosition + StringHelper::strlen($startAtt))), '"' 435 ); 436 $closeQuotes = $closeQuotePos + $startAttPosition + StringHelper::strlen($startAtt); 437 $nextEqual = $startAttPosition + StringHelper::strpos($startAtt, '='); 438 $openQuotes = $startAttPosition + StringHelper::strpos($startAtt, '"'); 439 $nextSpace = StringHelper::strpos(StringHelper::substr($fromSpace, $closeQuotes), ' ') + $closeQuotes; 440 } 441 442 // Do we have an attribute to process? [check for equal sign] 443 if ($fromSpace !== '/' && (($nextEqual && $nextSpace && $nextSpace < $nextEqual) || !$nextEqual)) 444 { 445 if (!$nextEqual) 446 { 447 $attribEnd = StringHelper::strpos($fromSpace, '/') - 1; 448 } 449 else 450 { 451 $attribEnd = $nextSpace - 1; 452 } 453 454 // If there is an ending, use this, if not, do not worry. 455 if ($attribEnd > 0) 456 { 457 $fromSpace = StringHelper::substr($fromSpace, $attribEnd + 1); 458 } 459 } 460 461 if (StringHelper::strpos($fromSpace, '=') !== false) 462 { 463 /* 464 * If the attribute value is wrapped in quotes we need to grab the substring from the closing quote, 465 * otherwise grab until the next space. 466 */ 467 if (($openQuotes !== false) 468 && (StringHelper::strpos(StringHelper::substr($fromSpace, ($openQuotes + 1)), '"') !== false)) 469 { 470 $attr = StringHelper::substr($fromSpace, 0, ($closeQuotes + 1)); 471 } 472 else 473 { 474 $attr = StringHelper::substr($fromSpace, 0, $nextSpace); 475 } 476 } 477 else 478 { 479 // No more equal signs so add any extra text in the tag into the attribute array [eg. checked] 480 if ($fromSpace !== '/') 481 { 482 $attr = StringHelper::substr($fromSpace, 0, $nextSpace); 483 } 484 } 485 486 // Last Attribute Pair 487 if (!$attr && $fromSpace !== '/') 488 { 489 $attr = $fromSpace; 490 } 491 492 // Add attribute pair to the attribute array 493 $attrSet[] = $attr; 494 495 // Move search point and continue iteration 496 $tagLeft = StringHelper::substr($fromSpace, StringHelper::strlen($attr)); 497 $currentSpace = StringHelper::strpos($tagLeft, ' '); 498 } 499 500 // Is our tag in the user input array? 501 $tagFound = \in_array(strtolower($tagName), $this->tagsArray); 502 503 // If the tag is allowed let's append it to the output string. 504 if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod)) 505 { 506 // Reconstruct tag with allowed attributes 507 if (!$isCloseTag) 508 { 509 // Open or single tag 510 $attrSet = $this->cleanAttributes($attrSet); 511 $preTag .= '<' . $tagName; 512 513 for ($i = 0, $count = \count($attrSet); $i < $count; $i++) 514 { 515 $preTag .= ' ' . $attrSet[$i]; 516 } 517 518 // Reformat single tags to XHTML 519 if (StringHelper::strpos($fromTagOpen, '</' . $tagName)) 520 { 521 $preTag .= '>'; 522 } 523 else 524 { 525 $preTag .= ' />'; 526 } 527 } 528 else 529 { 530 // Closing tag 531 $preTag .= '</' . $tagName . '>'; 532 } 533 } 534 535 // Find next tag's start and continue iteration 536 $postTag = StringHelper::substr($postTag, ($tagLength + 2)); 537 $tagOpenStart = StringHelper::strpos($postTag, '<'); 538 } 539 540 // Append any code after the end of tags and return 541 if ($postTag !== '<') 542 { 543 $preTag .= $postTag; 544 } 545 546 return $preTag; 547 } 548 549 /** 550 * Internal method to strip a tag of disallowed attributes 551 * 552 * @param array $attrSet Array of attribute pairs to filter 553 * 554 * @return array Filtered array of attribute pairs 555 * 556 * @since 1.0 557 */ 558 protected function cleanAttributes(array $attrSet) 559 { 560 $newSet = []; 561 562 $count = \count($attrSet); 563 564 // Iterate through attribute pairs 565 for ($i = 0; $i < $count; $i++) 566 { 567 // Skip blank spaces 568 if (!$attrSet[$i]) 569 { 570 continue; 571 } 572 573 // Split into name/value pairs 574 $attrSubSet = explode('=', trim($attrSet[$i]), 2); 575 576 // Take the last attribute in case there is an attribute with no value 577 $attrSubSet0 = explode(' ', trim($attrSubSet[0])); 578 $attrSubSet[0] = array_pop($attrSubSet0); 579 580 $attrSubSet[0] = strtolower($attrSubSet[0]); 581 $quoteStyle = \ENT_QUOTES | \ENT_HTML401; 582 583 // Remove all spaces as valid attributes does not have spaces. 584 $attrSubSet[0] = html_entity_decode($attrSubSet[0], $quoteStyle, 'UTF-8'); 585 $attrSubSet[0] = preg_replace('/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $attrSubSet[0]); 586 $attrSubSet[0] = preg_replace('/\s+/u', '', $attrSubSet[0]); 587 588 // Remove blocked chars from the attribute name 589 foreach ($this->blockedChars as $blockedChar) 590 { 591 $attrSubSet[0] = str_ireplace($blockedChar, '', $attrSubSet[0]); 592 } 593 594 // Remove all symbols 595 $attrSubSet[0] = preg_replace('/[^\p{L}\p{N}\-\s]/u', '', $attrSubSet[0]); 596 597 // Remove all "non-regular" attribute names 598 // AND blocked attributes 599 if ((!preg_match('/[a-z]*$/i', $attrSubSet[0])) 600 || ($this->xssAuto && ((\in_array(strtolower($attrSubSet[0]), $this->blockedAttributes)) 601 || substr($attrSubSet[0], 0, 2) == 'on'))) 602 { 603 continue; 604 } 605 606 // XSS attribute value filtering 607 if (!isset($attrSubSet[1])) 608 { 609 continue; 610 } 611 612 // Remove blocked chars from the attribute value 613 foreach ($this->blockedChars as $blockedChar) 614 { 615 $attrSubSet[1] = str_ireplace($blockedChar, '', $attrSubSet[1]); 616 } 617 618 // Trim leading and trailing spaces 619 $attrSubSet[1] = trim($attrSubSet[1]); 620 621 // Strips unicode, hex, etc 622 $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]); 623 624 // Strip normal newline within attr value 625 $attrSubSet[1] = preg_replace('/[\n\r]/', '', $attrSubSet[1]); 626 627 // Strip double quotes 628 $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]); 629 630 // Convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr values) 631 if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (\strlen($attrSubSet[1]) - 1), 1) == "'")) 632 { 633 $attrSubSet[1] = substr($attrSubSet[1], 1, (\strlen($attrSubSet[1]) - 2)); 634 } 635 636 // Strip slashes 637 $attrSubSet[1] = stripslashes($attrSubSet[1]); 638 639 // Autostrip script tags 640 if (static::checkAttribute($attrSubSet)) 641 { 642 continue; 643 } 644 645 // Is our attribute in the user input array? 646 $attrFound = \in_array(strtolower($attrSubSet[0]), $this->attrArray); 647 648 // If the tag is allowed lets keep it 649 if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod)) 650 { 651 // Does the attribute have a value? 652 if (empty($attrSubSet[1]) === false) 653 { 654 $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[1] . '"'; 655 } 656 elseif ($attrSubSet[1] === '0') 657 { 658 // Special Case 659 // Is the value 0? 660 $newSet[] = $attrSubSet[0] . '="0"'; 661 } 662 else 663 { 664 // Leave empty attributes alone 665 $newSet[] = $attrSubSet[0] . '=""'; 666 } 667 } 668 } 669 670 return $newSet; 671 } 672 673 /** 674 * Try to convert to plaintext 675 * 676 * @param string $source The source string. 677 * 678 * @return string Plaintext string 679 * 680 * @since 1.0 681 * @deprecated This method will be removed once support for PHP 5.3 is discontinued. 682 */ 683 protected function decode($source) 684 { 685 return html_entity_decode($source, \ENT_QUOTES, 'UTF-8'); 686 } 687 688 /** 689 * Escape < > and " inside attribute values 690 * 691 * @param string $source The source string. 692 * 693 * @return string Filtered string 694 * 695 * @since 1.0 696 */ 697 protected function escapeAttributeValues($source) 698 { 699 $alreadyFiltered = ''; 700 $remainder = $source; 701 $badChars = ['<', '"', '>']; 702 $escapedChars = ['<', '"', '>']; 703 704 // Process each portion based on presence of =" and "<space>, "/>, or "> 705 // See if there are any more attributes to process 706 while (preg_match('#<[^>]*?=\s*?(\"|\')#s', $remainder, $matches, \PREG_OFFSET_CAPTURE)) 707 { 708 // We have found a tag with an attribute, convert its byte position to a UTF-8 string length, using non-multibyte substr() 709 $stringBeforeTag = substr($remainder, 0, $matches[0][1]); 710 $tagPosition = StringHelper::strlen($stringBeforeTag); 711 712 // Get the character length before the attribute value 713 $nextBefore = $tagPosition + StringHelper::strlen($matches[0][0]); 714 715 // Figure out if we have a single or double quote and look for the matching closing quote 716 // Closing quote should be "/>, ">, "<space>, or " at the end of the string 717 $quote = StringHelper::substr($matches[0][0], -1); 718 $pregMatch = ($quote == '"') ? '#(\"\s*/\s*>|\"\s*>|\"\s+|\"$)#' : "#(\'\s*/\s*>|\'\s*>|\'\s+|\'$)#"; 719 720 // Get the portion after attribute value 721 $attributeValueRemainder = StringHelper::substr($remainder, $nextBefore); 722 723 if (preg_match($pregMatch, $attributeValueRemainder, $matches, \PREG_OFFSET_CAPTURE)) 724 { 725 // We have a closing quote, convert its byte position to a UTF-8 string length, using non-multibyte substr() 726 $stringBeforeQuote = substr($attributeValueRemainder, 0, $matches[0][1]); 727 $closeQuoteChars = StringHelper::strlen($stringBeforeQuote); 728 $nextAfter = $nextBefore + $matches[0][1]; 729 } 730 else 731 { 732 // No closing quote 733 $nextAfter = StringHelper::strlen($remainder); 734 } 735 736 // Get the actual attribute value 737 $attributeValue = StringHelper::substr($remainder, $nextBefore, $nextAfter - $nextBefore); 738 739 // Escape bad chars 740 $attributeValue = str_replace($badChars, $escapedChars, $attributeValue); 741 $attributeValue = $this->stripCssExpressions($attributeValue); 742 $alreadyFiltered .= StringHelper::substr($remainder, 0, $nextBefore) . $attributeValue . $quote; 743 $remainder = StringHelper::substr($remainder, $nextAfter + 1); 744 } 745 746 // At this point, we just have to return the $alreadyFiltered and the $remainder 747 return $alreadyFiltered . $remainder; 748 } 749 750 /** 751 * Remove CSS Expressions in the form of <property>:expression(...) 752 * 753 * @param string $source The source string. 754 * 755 * @return string Filtered string 756 * 757 * @since 1.0 758 */ 759 protected function stripCssExpressions($source) 760 { 761 // Strip any comments out (in the form of /*...*/) 762 $test = preg_replace('#\/\*.*\*\/#U', '', $source); 763 764 // Test for :expression 765 if (!stripos($test, ':expression')) 766 { 767 // Not found, so we are done 768 return $source; 769 } 770 771 // At this point, we have stripped out the comments and have found :expression 772 // Test stripped string for :expression followed by a '(' 773 if (preg_match_all('#:expression\s*\(#', $test, $matches)) 774 { 775 // If found, remove :expression 776 return str_ireplace(':expression', '', $test); 777 } 778 779 return $source; 780 } 781 782 /** 783 * Integer filter 784 * 785 * @param string $source The string to be filtered 786 * 787 * @return integer The filtered value 788 */ 789 private function cleanInt($source) 790 { 791 $pattern = '/[-+]?[0-9]+/'; 792 793 preg_match($pattern, $source, $matches); 794 795 return isset($matches[0]) ? (int) $matches[0] : 0; 796 } 797 798 /** 799 * Alias for cleanInt() 800 * 801 * @param string $source The string to be filtered 802 * 803 * @return integer The filtered value 804 */ 805 private function cleanInteger($source) 806 { 807 return $this->cleanInt($source); 808 } 809 810 /** 811 * Unsigned integer filter 812 * 813 * @param string $source The string to be filtered 814 * 815 * @return integer The filtered value 816 */ 817 private function cleanUint($source) 818 { 819 $pattern = '/[-+]?[0-9]+/'; 820 821 preg_match($pattern, $source, $matches); 822 823 return isset($matches[0]) ? abs((int) $matches[0]) : 0; 824 } 825 826 /** 827 * Float filter 828 * 829 * @param string $source The string to be filtered 830 * 831 * @return float The filtered value 832 */ 833 private function cleanFloat($source) 834 { 835 $pattern = '/[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?/'; 836 837 preg_match($pattern, $source, $matches); 838 839 return isset($matches[0]) ? (float) $matches[0] : 0.0; 840 } 841 842 /** 843 * Alias for cleanFloat() 844 * 845 * @param string $source The string to be filtered 846 * 847 * @return float The filtered value 848 */ 849 private function cleanDouble($source) 850 { 851 return $this->cleanFloat($source); 852 } 853 854 /** 855 * Boolean filter 856 * 857 * @param string $source The string to be filtered 858 * 859 * @return boolean The filtered value 860 */ 861 private function cleanBool($source) 862 { 863 return (bool) $source; 864 } 865 866 /** 867 * Alias for cleanBool() 868 * 869 * @param string $source The string to be filtered 870 * 871 * @return boolean The filtered value 872 */ 873 private function cleanBoolean($source) 874 { 875 return $this->cleanBool($source); 876 } 877 878 /** 879 * Word filter 880 * 881 * @param string $source The string to be filtered 882 * 883 * @return string The filtered string 884 */ 885 private function cleanWord($source) 886 { 887 $pattern = '/[^A-Z_]/i'; 888 889 return preg_replace($pattern, '', $source); 890 } 891 892 /** 893 * Alphanumerical filter 894 * 895 * @param string $source The string to be filtered 896 * 897 * @return string The filtered string 898 */ 899 private function cleanAlnum($source) 900 { 901 $pattern = '/[^A-Z0-9]/i'; 902 903 return preg_replace($pattern, '', $source); 904 } 905 906 /** 907 * Command filter 908 * 909 * @param string $source The string to be filtered 910 * 911 * @return string The filtered string 912 */ 913 private function cleanCmd($source) 914 { 915 $pattern = '/[^A-Z0-9_\.-]/i'; 916 917 $result = preg_replace($pattern, '', $source); 918 $result = ltrim($result, '.'); 919 920 return $result; 921 } 922 923 /** 924 * Base64 filter 925 * 926 * @param string $source The string to be filtered 927 * 928 * @return string The filtered string 929 */ 930 private function cleanBase64($source) 931 { 932 $pattern = '/[^A-Z0-9\/+=]/i'; 933 934 return preg_replace($pattern, '', $source); 935 } 936 937 /** 938 * String filter 939 * 940 * @param string $source The string to be filtered 941 * 942 * @return string The filtered string 943 */ 944 private function cleanString($source) 945 { 946 return $this->remove($this->decode($source)); 947 } 948 949 /** 950 * HTML filter 951 * 952 * @param string $source The string to be filtered 953 * 954 * @return string The filtered string 955 */ 956 private function cleanHtml($source) 957 { 958 return $this->remove($source); 959 } 960 961 /** 962 * Path filter 963 * 964 * @param string $source The string to be filtered 965 * 966 * @return string The filtered string 967 */ 968 private function cleanPath($source) 969 { 970 $linuxPattern = '/^[A-Za-z0-9_\/-]+[A-Za-z0-9_\.-]*([\\\\\/]+[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$/'; 971 972 if (preg_match($linuxPattern, $source)) 973 { 974 return preg_replace('~/+~', '/', $source); 975 } 976 977 $windowsPattern = '/^([A-Za-z]:(\\\\|\/))?[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*((\\\\|\/)+[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$/'; 978 979 if (preg_match($windowsPattern, $source)) 980 { 981 return preg_replace('~(\\\\|\/)+~', '\\', $source); 982 } 983 984 return ''; 985 } 986 987 /** 988 * Trim filter 989 * 990 * @param string $source The string to be filtered 991 * 992 * @return string The filtered string 993 */ 994 private function cleanTrim($source) 995 { 996 $result = trim($source); 997 $result = StringHelper::trim($result, \chr(0xE3) . \chr(0x80) . \chr(0x80)); 998 $result = StringHelper::trim($result, \chr(0xC2) . \chr(0xA0)); 999 1000 return $result; 1001 } 1002 1003 /** 1004 * Username filter 1005 * 1006 * @param string $source The string to be filtered 1007 * 1008 * @return string The filtered string 1009 */ 1010 private function cleanUsername($source) 1011 { 1012 $pattern = '/[\x00-\x1F\x7F<>"\'%&]/'; 1013 1014 return preg_replace($pattern, '', $source); 1015 } 1016 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |