Support Joomla!

Packages

Package: Joomla-Framework

License

Content on this site is copyright © 2005 - 2008 Open Source Matters Inc and can be used in accordance with the Joomla! Electronic Documentation License. Some parts of this website may be subject to other licenses.
Source code for file /joomla/filter/filterinput.php

Documentation is available at filterinput.php

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

Documentation generated on Sat, 14 Nov 2009 11:13:03 +0000 by phpDocumentor 1.3.1