Support Joomla!

Joomla! 1.5 Documentation

Packages

Package: Joomla-Framework

Developer Network License

The Joomla! Developer Network content is © copyright 2006 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution- NonCommercial- ShareAlike 2.5
Source code for file /joomla/filter/input.php

Documentation is available at input.php

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

Documentation generated on Mon, 05 Mar 2007 21:08:05 +0000 by phpDocumentor 1.3.1