[ Index ]

PHP Cross Reference of Joomla 1.5.26 DE

title

Body

[close]

/libraries/phpinputfilter/ -> inputfilter.php (source)

   1  <?php
   2  /**
   3   *  @class: InputFilter (PHP4 & PHP5, with comments)
   4   * @project: PHP Input Filter
   5   * @date: 10-05-2005
   6   * @version: 1.2.2_php4/php5
   7   * @author: Daniel Morris
   8   * @contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris
   9   * Tobin and Andrew Eddie.
  10   *
  11   * Modification by Louis Landry
  12   *
  13   * @copyright: Daniel Morris
  14   * @email: dan@rootcube.com
  15   * @license: GNU General Public License (GPL)
  16   */
  17  class InputFilter
  18  {
  19      var $tagsArray; // default = empty array
  20      var $attrArray; // default = empty array
  21  
  22      var $tagsMethod; // default = 0
  23      var $attrMethod; // default = 0
  24  
  25      var $xssAuto; // default = 1
  26      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');
  27      var $attrBlacklist = array ('action', 'background', 'codebase', 'dynsrc', 'lowsrc'); // also will strip ALL event handlers
  28  
  29      /**
  30       * Constructor for inputFilter class. Only first parameter is required.
  31       *
  32       * @access    protected
  33       * @param    array    $tagsArray    list of user-defined tags
  34       * @param    array    $attrArray    list of user-defined attributes
  35       * @param    int        $tagsMethod    WhiteList method = 0, BlackList method = 1
  36       * @param    int        $attrMethod    WhiteList method = 0, BlackList method = 1
  37       * @param    int        $xssAuto    Only auto clean essentials = 0, Allow clean
  38       * blacklisted tags/attr = 1
  39       */
  40  	function inputFilter($tagsArray = array (), $attrArray = array (), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1)
  41      {
  42          /*
  43           * Make sure user defined arrays are in lowercase
  44           */
  45          $tagsArray = array_map('strtolower', (array) $tagsArray);
  46          $attrArray = array_map('strtolower', (array) $attrArray);
  47  
  48          /*
  49           * Assign member variables
  50           */
  51          $this->tagsArray    = $tagsArray;
  52          $this->attrArray    = $attrArray;
  53          $this->tagsMethod    = $tagsMethod;
  54          $this->attrMethod    = $attrMethod;
  55          $this->xssAuto        = $xssAuto;
  56      }
  57  
  58      /**
  59       * Method to be called by another php script. Processes for XSS and
  60       * specified bad code.
  61       *
  62       * @access    public
  63       * @param    mixed    $source    Input string/array-of-string to be 'cleaned'
  64       * @return mixed    $source    'cleaned' version of input parameter
  65       */
  66  	function process($source)
  67      {
  68          /*
  69           * Are we dealing with an array?
  70           */
  71          if (is_array($source))
  72          {
  73              foreach ($source as $key => $value)
  74              {
  75                  // filter element for XSS and other 'bad' code etc.
  76                  if (is_string($value))
  77                  {
  78                      $source[$key] = $this->remove($this->decode($value));
  79                  }
  80              }
  81              return $source;
  82          } else
  83              /*
  84               * Or a string?
  85               */
  86              if (is_string($source) && !empty ($source))
  87              {
  88                  // filter source for XSS and other 'bad' code etc.
  89                  return $this->remove($this->decode($source));
  90              } else
  91              {
  92                  /*
  93                   * Not an array or string.. return the passed parameter
  94                   */
  95                  return $source;
  96              }
  97      }
  98  
  99      /**
 100       * Internal method to iteratively remove all unwanted tags and attributes
 101       *
 102       * @access    protected
 103       * @param    string    $source    Input string to be 'cleaned'
 104       * @return    string    $source    'cleaned' version of input parameter
 105       */
 106  	function remove($source)
 107      {
 108          $loopCounter = 0;
 109          /*
 110           * Iteration provides nested tag protection
 111           */
 112          while ($source != $this->filterTags($source))
 113          {
 114              $source = $this->filterTags($source);
 115              $loopCounter ++;
 116          }
 117          return $source;
 118      }
 119  
 120      /**
 121       * Internal method to strip a string of certain tags
 122       *
 123       * @access    protected
 124       * @param    string    $source    Input string to be 'cleaned'
 125       * @return    string    $source    'cleaned' version of input parameter
 126       */
 127  	function filterTags($source)
 128      {
 129          /*
 130           * In the beginning we don't really have a tag, so everything is
 131           * postTag
 132           */
 133          $preTag        = null;
 134          $postTag    = $source;
 135  
 136          /*
 137           * Is there a tag? If so it will certainly start with a '<'
 138           */
 139          $tagOpen_start    = strpos($source, '<');
 140  
 141          while ($tagOpen_start !== false)
 142          {
 143  
 144              /*
 145               * Get some information about the tag we are processing
 146               */
 147              $preTag           .= substr($postTag, 0, $tagOpen_start);
 148              $postTag        = substr($postTag, $tagOpen_start);
 149              $fromTagOpen    = substr($postTag, 1);
 150              $tagOpen_end    = strpos($fromTagOpen, '>');
 151  
 152              /*
 153               * Let's catch any non-terminated tags and skip over them
 154               */
 155              if ($tagOpen_end === false)
 156              {
 157                  $postTag        = substr($postTag, $tagOpen_start +1);
 158                  $tagOpen_start    = strpos($postTag, '<');
 159                  continue;
 160              }
 161  
 162              /*
 163               * Do we have a nested tag?
 164               */
 165              $tagOpen_nested = strpos($fromTagOpen, '<');
 166              $tagOpen_nested_end    = strpos(substr($postTag, $tagOpen_end), '>');
 167              if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end))
 168              {
 169                  $preTag           .= substr($postTag, 0, ($tagOpen_nested +1));
 170                  $postTag        = substr($postTag, ($tagOpen_nested +1));
 171                  $tagOpen_start    = strpos($postTag, '<');
 172                  continue;
 173              }
 174  
 175  
 176              /*
 177               * Lets get some information about our tag and setup attribute pairs
 178               */
 179              $tagOpen_nested    = (strpos($fromTagOpen, '<') + $tagOpen_start +1);
 180              $currentTag        = substr($fromTagOpen, 0, $tagOpen_end);
 181              $tagLength        = strlen($currentTag);
 182              $tagLeft        = $currentTag;
 183              $attrSet        = array ();
 184              $currentSpace    = strpos($tagLeft, ' ');
 185  
 186              /*
 187               * Are we an open tag or a close tag?
 188               */
 189              if (substr($currentTag, 0, 1) == "/")
 190              {
 191                  // Close Tag
 192                  $isCloseTag        = true;
 193                  list ($tagName)    = explode(' ', $currentTag);
 194                  $tagName        = substr($tagName, 1);
 195              } else
 196              {
 197                  // Open Tag
 198                  $isCloseTag        = false;
 199                  list ($tagName)    = explode(' ', $currentTag);
 200              }
 201  
 202              /*
 203               * Exclude all "non-regular" tagnames
 204               * OR no tagname
 205               * OR remove if xssauto is on and tag is blacklisted
 206               */
 207              if ((!preg_match("/^[a-z][a-z0-9]*$/i", $tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto)))
 208              {
 209                  $postTag        = substr($postTag, ($tagLength +2));
 210                  $tagOpen_start    = strpos($postTag, '<');
 211                  // Strip tag
 212                  continue;
 213              }
 214  
 215              /*
 216               * Time to grab any attributes from the tag... need this section in
 217               * case attributes have spaces in the values.
 218               */
 219              while ($currentSpace !== false)
 220              {
 221                  $fromSpace        = substr($tagLeft, ($currentSpace +1));
 222                  $nextSpace        = strpos($fromSpace, ' ');
 223                  $openQuotes        = strpos($fromSpace, '"');
 224                  $closeQuotes    = strpos(substr($fromSpace, ($openQuotes +1)), '"') + $openQuotes +1;
 225  
 226                  /*
 227                   * Do we have an attribute to process? [check for equal sign]
 228                   */
 229                  if (strpos($fromSpace, '=') !== false)
 230                  {
 231                      /*
 232                       * If the attribute value is wrapped in quotes we need to
 233                       * grab the substring from the closing quote, otherwise grab
 234                       * till the next space
 235                       */
 236                      if (($openQuotes !== false) && (strpos(substr($fromSpace, ($openQuotes +1)), '"') !== false))
 237                      {
 238                          $attr = substr($fromSpace, 0, ($closeQuotes +1));
 239                      } else
 240                      {
 241                          $attr = substr($fromSpace, 0, $nextSpace);
 242                      }
 243                  } else
 244                  {
 245                      /*
 246                       * No more equal signs so add any extra text in the tag into
 247                       * the attribute array [eg. checked]
 248                       */
 249                      $attr = substr($fromSpace, 0, $nextSpace);
 250                  }
 251  
 252                  // Last Attribute Pair
 253                  if (!$attr)
 254                  {
 255                      $attr = $fromSpace;
 256                  }
 257  
 258                  /*
 259                   * Add attribute pair to the attribute array
 260                   */
 261                  $attrSet[] = $attr;
 262  
 263                  /*
 264                   * Move search point and continue iteration
 265                   */
 266                  $tagLeft        = substr($fromSpace, strlen($attr));
 267                  $currentSpace    = strpos($tagLeft, ' ');
 268              }
 269  
 270              /*
 271               * Is our tag in the user input array?
 272               */
 273              $tagFound = in_array(strtolower($tagName), $this->tagsArray);
 274  
 275              /*
 276               * If the tag is allowed lets append it to the output string
 277               */
 278              if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod))
 279              {
 280                  /*
 281                   * Reconstruct tag with allowed attributes
 282                   */
 283                  if (!$isCloseTag)
 284                  {
 285                      // Open or Single tag
 286                      $attrSet = $this->filterAttr($attrSet);
 287                      $preTag .= '<'.$tagName;
 288                      for ($i = 0; $i < count($attrSet); $i ++)
 289                      {
 290                          $preTag .= ' '.$attrSet[$i];
 291                      }
 292  
 293                      /*
 294                       * Reformat single tags to XHTML
 295                       */
 296                      if (strpos($fromTagOpen, "</".$tagName))
 297                      {
 298                          $preTag .= '>';
 299                      } else
 300                      {
 301                          $preTag .= ' />';
 302                      }
 303                  } else
 304                  {
 305                      // Closing Tag
 306                      $preTag .= '</'.$tagName.'>';
 307                  }
 308              }
 309  
 310              /*
 311               * Find next tag's start and continue iteration
 312               */
 313              $postTag        = substr($postTag, ($tagLength +2));
 314              $tagOpen_start    = strpos($postTag, '<');
 315          }
 316  
 317          /*
 318           * Append any code after the end of tags and return
 319           */
 320          if ($postTag != '<')
 321          {
 322              $preTag .= $postTag;
 323          }
 324          return $preTag;
 325      }
 326  
 327      /**
 328       * Internal method to strip a tag of certain attributes
 329       *
 330       * @access    protected
 331       * @param    array    $attrSet    Array of attribute pairs to filter
 332       * @return    array    $newSet        Filtered array of attribute pairs
 333       */
 334  	function filterAttr($attrSet)
 335      {
 336          /*
 337           * Initialize variables
 338           */
 339          $newSet = array ();
 340  
 341          /*
 342           * Iterate through attribute pairs
 343           */
 344          for ($i = 0; $i < count($attrSet); $i ++)
 345          {
 346              /*
 347               * Skip blank spaces
 348               */
 349              if (!$attrSet[$i])
 350              {
 351                  continue;
 352              }
 353  
 354              /*
 355               * Split into name/value pairs
 356               */
 357              $attrSubSet = explode('=', trim($attrSet[$i]), 2);
 358              list ($attrSubSet[0]) = explode(' ', $attrSubSet[0]);
 359  
 360              /*
 361               * Remove all "non-regular" attribute names
 362               * AND blacklisted attributes
 363               */
 364              if ((!preg_match("#^[a-z]*$#i", $attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on'))))
 365              {
 366                  continue;
 367              }
 368  
 369              /*
 370               * XSS attribute value filtering
 371               */
 372              if ($attrSubSet[1])
 373              {
 374                  // strips unicode, hex, etc
 375                  $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
 376                  // strip normal newline within attr value
 377                  $attrSubSet[1] = preg_replace('/\s+/', '', $attrSubSet[1]);
 378                  // strip double quotes
 379                  $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);
 380                  // [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value)
 381                  if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'"))
 382                  {
 383                      $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
 384                  }
 385                  // strip slashes
 386                  $attrSubSet[1] = stripslashes($attrSubSet[1]);
 387              }
 388  
 389              /*
 390               * Autostrip script tags
 391               */
 392              if (InputFilter::badAttributeValue($attrSubSet))
 393              {
 394                  continue;
 395              }
 396  
 397              /*
 398               * Is our attribute in the user input array?
 399               */
 400              $attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);
 401  
 402              /*
 403               * If the tag is allowed lets keep it
 404               */
 405              if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod))
 406              {
 407                  /*
 408                   * Does the attribute have a value?
 409                   */
 410                  if ($attrSubSet[1])
 411                  {
 412                      $newSet[] = $attrSubSet[0].'="'.$attrSubSet[1].'"';
 413                  }
 414                  elseif ($attrSubSet[1] == "0")
 415                  {
 416                      /*
 417                       * Special Case
 418                       * Is the value 0?
 419                       */
 420                      $newSet[] = $attrSubSet[0].'="0"';
 421                  } else
 422                  {
 423                      $newSet[] = $attrSubSet[0].'="'.$attrSubSet[0].'"';
 424                  }
 425              }
 426          }
 427          return $newSet;
 428      }
 429  
 430      /**
 431       * Function to determine if contents of an attribute is safe
 432       *
 433       * @access    protected
 434       * @param    array    $attrSubSet    A 2 element array for attributes name,value
 435       * @return    boolean True if bad code is detected
 436       */
 437  	function badAttributeValue($attrSubSet)
 438      {
 439          $attrSubSet[0] = strtolower($attrSubSet[0]);
 440          $attrSubSet[1] = strtolower($attrSubSet[1]);
 441          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));
 442      }
 443  
 444      /**
 445       * Try to convert to plaintext
 446       *
 447       * @access    protected
 448       * @param    string    $source
 449       * @return    string    Plaintext string
 450       */
 451  	function decode($source)
 452      {
 453          // url decode
 454          $source = html_entity_decode($source, ENT_QUOTES, "ISO-8859-1");
 455          // convert decimal
 456          $source = preg_replace('/&#(\d+);/me', "chr(\\1)", $source); // decimal notation
 457          // convert hex
 458          $source = preg_replace('/&#x([a-f0-9]+);/mei', "chr(0x\\1)", $source); // hex notation
 459          return $source;
 460      }
 461  
 462      /**
 463       * Method to be called by another php script. Processes for SQL injection
 464       *
 465       * @access    public
 466       * @param    mixed        $source    input string/array-of-string to be 'cleaned'
 467       * @param    resource    $connection - An open MySQL connection
 468       * @return    string        'cleaned' version of input parameter
 469       */
 470  	function safeSQL($source, & $connection)
 471      {
 472          // clean all elements in this array
 473          if (is_array($source))
 474          {
 475              foreach ($source as $key => $value)
 476              {
 477                  // filter element for SQL injection
 478                  if (is_string($value))
 479                  {
 480                      $source[$key] = $this->quoteSmart($this->decode($value), $connection);
 481                  }
 482              }
 483              return $source;
 484              // clean this string
 485          } else
 486              if (is_string($source))
 487              {
 488                  // filter source for SQL injection
 489                  if (is_string($source))
 490                  {
 491                      return $this->quoteSmart($this->decode($source), $connection);
 492                  }
 493                  // return parameter as given
 494              } else
 495              {
 496                  return $source;
 497              }
 498      }
 499  
 500      /**
 501       * Method to escape a string
 502       *
 503       * @author    Chris Tobin
 504       * @author    Daniel Morris
 505       *
 506       * @access    protected
 507       * @param    string        $source
 508       * @param    resource    $connection        An open MySQL connection
 509       * @return    string        Escaped string
 510       */
 511  	function quoteSmart($source, & $connection)
 512      {
 513          /*
 514           * Strip escaping slashes if necessary
 515           */
 516          if (get_magic_quotes_gpc())
 517          {
 518              $source = stripslashes($source);
 519          }
 520  
 521          /*
 522           * Escape numeric and text values
 523           */
 524          $source = $this->escapeString($source, $connection);
 525          return $source;
 526      }
 527  
 528      /**
 529       * @author    Chris Tobin
 530       * @author    Daniel Morris
 531       *
 532       * @access    protected
 533       * @param    string        $source
 534       * @param    resource    $connection        An open MySQL connection
 535       * @return    string        Escaped string
 536       */
 537  	function escapeString($string, & $connection) {
 538          /*
 539          * Use the appropriate escape string depending upon which version of php
 540          * you are running
 541          */
 542          if (version_compare(phpversion(), '4.3.0', '<')) {
 543              $string = mysql_escape_string($string);
 544          } else     {
 545              $string = mysql_real_escape_string($string);
 546          }
 547  
 548          return $string;
 549      }
 550  }
 551  ?>


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