[ Index ]

PHP Cross Reference of Joomla 1.5.26 DE

title

Body

[close]

/libraries/joomla/filter/ -> filterinput.php (source)

   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  }


Generated: Wed Mar 28 15:54:07 2012 Cross-referenced by PHPXref 0.7.1