Source for file input.php

Documentation is available at input.php

  1. <?php
  2. /**
  3.  * @package     Joomla.Platform
  4.  * @subpackage  Filter
  5.  *
  6.  * @copyright   Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  7.  * @license     GNU General Public License version 2 or later; see LICENSE
  8.  */
  9.  
  10. defined('JPATH_PLATFORM'or die;
  11.  
  12. /**
  13.  * JFilterInput is a class for filtering input from any data source
  14.  *
  15.  * Forked from the php input filter library by: Daniel Morris <dan@rootcube.com>
  16.  * Original Contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie.
  17.  *
  18.  * @package     Joomla.Platform
  19.  * @subpackage  Filter
  20.  * @since       11.1
  21.  */
  22. {
  23.     /**
  24.      * A container for JFilterInput instances.
  25.      *
  26.      * @var    array 
  27.      * @since  11.3
  28.      */
  29.     protected static $instances array();
  30.  
  31.     /**
  32.      * The array of permitted tags (white list).
  33.      *
  34.      * @var    array 
  35.      * @since  11.1
  36.      */
  37.     public $tagsArray;
  38.  
  39.     /**
  40.      * The array of permitted tag attributes (white list).
  41.      *
  42.      * @var    array 
  43.      * @since  11.1
  44.      */
  45.     public $attrArray;
  46.  
  47.     /**
  48.      * The method for sanitising tags: WhiteList method = 0 (default), BlackList method = 1
  49.      *
  50.      * @var    integer 
  51.      * @since  11.1
  52.      */
  53.     public $tagsMethod;
  54.  
  55.     /**
  56.      * The method for sanitising attributes: WhiteList method = 0 (default), BlackList method = 1
  57.      *
  58.      * @var    integer 
  59.      * @since  11.1
  60.      */
  61.     public $attrMethod;
  62.  
  63.     /**
  64.      * A flag for XSS checks. Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
  65.      *
  66.      * @var    integer 
  67.      * @since  11.1
  68.      */
  69.     public $xssAuto;
  70.  
  71.     /**
  72.      * The list of the default blacklisted tags.
  73.      *
  74.      * @var    array 
  75.      * @since  11.1
  76.      */
  77.     public $tagBlacklist = array(
  78.         'applet',
  79.         'body',
  80.         'bgsound',
  81.         'base',
  82.         'basefont',
  83.         'embed',
  84.         'frame',
  85.         'frameset',
  86.         'head',
  87.         'html',
  88.         'id',
  89.         'iframe',
  90.         'ilayer',
  91.         'layer',
  92.         'link',
  93.         'meta',
  94.         'name',
  95.         'object',
  96.         'script',
  97.         'style',
  98.         'title',
  99.         'xml'
  100.     );
  101.  
  102.     /**
  103.      * The list of the default blacklisted tag attributes. All event handlers implicit.
  104.      *
  105.      * @var    array 
  106.      * @since   11.1
  107.      */
  108.     public $attrBlacklist = array(
  109.         'action',
  110.         'background',
  111.         'codebase',
  112.         'dynsrc',
  113.         'lowsrc'
  114.     );
  115.  
  116.     /**
  117.      * Constructor for inputFilter class. Only first parameter is required.
  118.      *
  119.      * @param   array    $tagsArray   List of user-defined tags
  120.      * @param   array    $attrArray   List of user-defined attributes
  121.      * @param   integer  $tagsMethod  WhiteList method = 0, BlackList method = 1
  122.      * @param   integer  $attrMethod  WhiteList method = 0, BlackList method = 1
  123.      * @param   integer  $xssAuto     Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
  124.      *
  125.      * @since   11.1
  126.      */
  127.     public function __construct($tagsArray array()$attrArray array()$tagsMethod 0$attrMethod 0$xssAuto 1)
  128.     {
  129.         // Make sure user defined arrays are in lowercase
  130.         $tagsArray array_map('strtolower'(array) $tagsArray);
  131.         $attrArray array_map('strtolower'(array) $attrArray);
  132.  
  133.         // Assign member variables
  134.         $this->tagsArray = $tagsArray;
  135.         $this->attrArray = $attrArray;
  136.         $this->tagsMethod = $tagsMethod;
  137.         $this->attrMethod = $attrMethod;
  138.         $this->xssAuto = $xssAuto;
  139.     }
  140.  
  141.     /**
  142.      * Returns an input filter object, only creating it if it doesn't already exist.
  143.      *
  144.      * @param   array    $tagsArray   List of user-defined tags
  145.      * @param   array    $attrArray   List of user-defined attributes
  146.      * @param   integer  $tagsMethod  WhiteList method = 0, BlackList method = 1
  147.      * @param   integer  $attrMethod  WhiteList method = 0, BlackList method = 1
  148.      * @param   integer  $xssAuto     Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
  149.      *
  150.      * @return  JFilterInput  The JFilterInput object.
  151.      *
  152.      * @since   11.1
  153.      */
  154.     public static function &getInstance($tagsArray array()$attrArray array()$tagsMethod 0$attrMethod 0$xssAuto 1)
  155.     {
  156.         $sig md5(serialize(array($tagsArray$attrArray$tagsMethod$attrMethod$xssAuto)));
  157.  
  158.         if (empty(self::$instances[$sig]))
  159.         {
  160.             self::$instances[$signew JFilterInput($tagsArray$attrArray$tagsMethod$attrMethod$xssAuto);
  161.         }
  162.  
  163.         return self::$instances[$sig];
  164.     }
  165.  
  166.     /**
  167.      * Method to be called by another php script. Processes for XSS and
  168.      * specified bad code.
  169.      *
  170.      * @param   mixed   $source  Input string/array-of-string to be 'cleaned'
  171.      * @param   string  $type    The return type for the variable:
  172.      *                            INT:       An integer,
  173.      *                            UINT:      An unsigned integer,
  174.      *                            FLOAT:     A floating point number,
  175.      *                            BOOLEAN:   A boolean value,
  176.      *                            WORD:      A string containing A-Z or underscores only (not case sensitive),
  177.      *                            ALNUM:     A string containing A-Z or 0-9 only (not case sensitive),
  178.      *                            CMD:       A string containing A-Z, 0-9, underscores, periods or hyphens (not case sensitive),
  179.      *                            BASE64:    A string containing A-Z, 0-9, forward slashes, plus or equals (not case sensitive),
  180.      *                            STRING:    A fully decoded and sanitised string (default),
  181.      *                            HTML:      A sanitised string,
  182.      *                            ARRAY:     An array,
  183.      *                            PATH:      A sanitised file path,
  184.      *                            USERNAME:  Do not use (use an application specific filter),
  185.      *                            RAW:       The raw string is returned with no filtering,
  186.      *                            unknown:   An unknown filter will act like STRING. If the input is an array it will return an
  187.      *                                       array of fully decoded and sanitised strings.
  188.      *
  189.      * @return  mixed  'Cleaned' version of input parameter
  190.      *
  191.      * @since   11.1
  192.      */
  193.     public function clean($source$type 'string')
  194.     {
  195.         // Handle the type constraint
  196.         switch (strtoupper($type))
  197.         {
  198.             case 'INT':
  199.             case 'INTEGER':
  200.                 // Only use the first integer value
  201.                 preg_match('/-?[0-9]+/'(string) $source$matches);
  202.                 $result (int) $matches[0];
  203.                 break;
  204.  
  205.             case 'UINT':
  206.                 // Only use the first integer value
  207.                 preg_match('/-?[0-9]+/'(string) $source$matches);
  208.                 $result abs((int) $matches[0]);
  209.                 break;
  210.  
  211.             case 'FLOAT':
  212.             case 'DOUBLE':
  213.                 // Only use the first floating point value
  214.                 preg_match('/-?[0-9]+(\.[0-9]+)?/'(string) $source$matches);
  215.                 $result (float) $matches[0];
  216.                 break;
  217.  
  218.             case 'BOOL':
  219.             case 'BOOLEAN':
  220.                 $result = (bool) $source;
  221.                 break;
  222.  
  223.             case 'WORD':
  224.                 $result = (string) preg_replace('/[^A-Z_]/i'''$source);
  225.                 break;
  226.  
  227.             case 'ALNUM':
  228.                 $result = (string) preg_replace('/[^A-Z0-9]/i'''$source);
  229.                 break;
  230.  
  231.             case 'CMD':
  232.                 $result = (string) preg_replace('/[^A-Z0-9_\.-]/i'''$source);
  233.                 $result ltrim($result'.');
  234.                 break;
  235.  
  236.             case 'BASE64':
  237.                 $result = (string) preg_replace('/[^A-Z0-9\/+=]/i'''$source);
  238.                 break;
  239.  
  240.             case 'STRING':
  241.                 $result = (string) $this->_remove($this->_decode((string) $source));
  242.                 break;
  243.  
  244.             case 'HTML':
  245.                 $result = (string) $this->_remove((string) $source);
  246.                 break;
  247.  
  248.             case 'ARRAY':
  249.                 $result = (array) $source;
  250.                 break;
  251.  
  252.             case 'PATH':
  253.                 $pattern '/^[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*([\\\\\/][A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$/';
  254.                 preg_match($pattern(string) $source$matches);
  255.                 $result (string) $matches[0];
  256.                 break;
  257.  
  258.             case 'USERNAME':
  259.                 $result = (string) preg_replace('/[\x00-\x1F\x7F<>"\'%&]/'''$source);
  260.                 break;
  261.  
  262.             case 'RAW':
  263.                 $result $source;
  264.                 break;
  265.  
  266.             default:
  267.                 // Are we dealing with an array?
  268.                 if (is_array($source))
  269.                 {
  270.                     foreach ($source as $key => $value)
  271.                     {
  272.                         // Filter element for XSS and other 'bad' code etc.
  273.                         if (is_string($value))
  274.                         {
  275.                             $source[$key$this->_remove($this->_decode($value));
  276.                         }
  277.                     }
  278.                     $result $source;
  279.                 }
  280.                 else
  281.                 {
  282.                     // Or a string?
  283.                     if (is_string($source&& !empty($source))
  284.                     {
  285.                         // Filter source for XSS and other 'bad' code etc.
  286.                         $result $this->_remove($this->_decode($source));
  287.                     }
  288.                     else
  289.                     {
  290.                         // Not an array or string.. return the passed parameter
  291.                         $result $source;
  292.                     }
  293.                 }
  294.                 break;
  295.         }
  296.  
  297.         return $result;
  298.     }
  299.  
  300.     /**
  301.      * Function to determine if contents of an attribute are safe
  302.      *
  303.      * @param   array  $attrSubSet  A 2 element array for attribute's name, value
  304.      *
  305.      * @return  boolean  True if bad code is detected
  306.      *
  307.      * @since   11.1
  308.      */
  309.     public static function checkAttribute($attrSubSet)
  310.     {
  311.         $attrSubSet[0strtolower($attrSubSet[0]);
  312.         $attrSubSet[1strtolower($attrSubSet[1]);
  313.  
  314.         return (((strpos($attrSubSet[1]'expression'!== false&& ($attrSubSet[0]== 'style'|| (strpos($attrSubSet[1]'javascript:'!== false||
  315.             (strpos($attrSubSet[1]'behaviour:'!== false|| (strpos($attrSubSet[1]'vbscript:'!== false||
  316.             (strpos($attrSubSet[1]'mocha:'!== false|| (strpos($attrSubSet[1]'livescript:'!== false));
  317.     }
  318.  
  319.     /**
  320.      * Internal method to iteratively remove all unwanted tags and attributes
  321.      *
  322.      * @param   string  $source  Input string to be 'cleaned'
  323.      *
  324.      * @return  string  'Cleaned' version of input parameter
  325.      *
  326.      * @since   11.1
  327.      */
  328.     protected function _remove($source)
  329.     {
  330.         $loopCounter 0;
  331.  
  332.         // Iteration provides nested tag protection
  333.         while ($source != $this->_cleanTags($source))
  334.         {
  335.             $source $this->_cleanTags($source);
  336.             $loopCounter++;
  337.         }
  338.  
  339.         return $source;
  340.     }
  341.  
  342.     /**
  343.      * Internal method to strip a string of certain tags
  344.      *
  345.      * @param   string  $source  Input string to be 'cleaned'
  346.      *
  347.      * @return  string  'Cleaned' version of input parameter
  348.      *
  349.      * @since   11.1
  350.      */
  351.     protected function _cleanTags($source)
  352.     {
  353.         // First, pre-process this for illegal characters inside attribute values
  354.         $source $this->_escapeAttributeValues($source);
  355.  
  356.         // In the beginning we don't really have a tag, so everything is postTag
  357.         $preTag null;
  358.         $postTag $source;
  359.         $currentSpace false;
  360.  
  361.         // Setting to null to deal with undefined variables
  362.         $attr '';
  363.  
  364.         // Is there a tag? If so it will certainly start with a '<'.
  365.         $tagOpen_start strpos($source'<');
  366.  
  367.         while ($tagOpen_start !== false)
  368.         {
  369.             // Get some information about the tag we are processing
  370.             $preTag .= substr($postTag0$tagOpen_start);
  371.             $postTag substr($postTag$tagOpen_start);
  372.             $fromTagOpen substr($postTag1);
  373.             $tagOpen_end strpos($fromTagOpen'>');
  374.  
  375.             // Check for mal-formed tag where we have a second '<' before the first '>'
  376.             $nextOpenTag (strlen($postTag$tagOpen_startstrpos($postTag'<'$tagOpen_start 1false;
  377.  
  378.             if (($nextOpenTag !== false&& ($nextOpenTag $tagOpen_end))
  379.             {
  380.                 // At this point we have a mal-formed tag -- remove the offending open
  381.                 $postTag substr($postTag0$tagOpen_startsubstr($postTag$tagOpen_start 1);
  382.                 $tagOpen_start strpos($postTag'<');
  383.                 continue;
  384.             }
  385.  
  386.             // Let's catch any non-terminated tags and skip over them
  387.             if ($tagOpen_end === false)
  388.             {
  389.                 $postTag substr($postTag$tagOpen_start 1);
  390.                 $tagOpen_start strpos($postTag'<');
  391.                 continue;
  392.             }
  393.  
  394.             // Do we have a nested tag?
  395.             $tagOpen_nested strpos($fromTagOpen'<');
  396.  
  397.             if (($tagOpen_nested !== false&& ($tagOpen_nested $tagOpen_end))
  398.             {
  399.                 $preTag .= substr($postTag0($tagOpen_nested 1));
  400.                 $postTag substr($postTag($tagOpen_nested 1));
  401.                 $tagOpen_start strpos($postTag'<');
  402.                 continue;
  403.             }
  404.  
  405.             // Let's get some information about our tag and setup attribute pairs
  406.             $tagOpen_nested (strpos($fromTagOpen'<'$tagOpen_start 1);
  407.             $currentTag substr($fromTagOpen0$tagOpen_end);
  408.             $tagLength strlen($currentTag);
  409.             $tagLeft $currentTag;
  410.             $attrSet array();
  411.             $currentSpace strpos($tagLeft' ');
  412.  
  413.             // Are we an open tag or a close tag?
  414.             if (substr($currentTag01== '/')
  415.             {
  416.                 // Close Tag
  417.                 $isCloseTag true;
  418.                 list ($tagNameexplode(' '$currentTag);
  419.                 $tagName substr($tagName1);
  420.             }
  421.             else
  422.             {
  423.                 // Open Tag
  424.                 $isCloseTag false;
  425.                 list ($tagNameexplode(' '$currentTag);
  426.             }
  427.  
  428.             /*
  429.              * Exclude all "non-regular" tagnames
  430.              * OR no tagname
  431.              * OR remove if xssauto is on and tag is blacklisted
  432.              */
  433.             if ((!preg_match("/^[a-z][a-z0-9]*$/i"$tagName)) || (!$tagName|| ((in_array(strtolower($tagName)$this->tagBlacklist)) && ($this->xssAuto)))
  434.             {
  435.                 $postTag substr($postTag($tagLength 2));
  436.                 $tagOpen_start strpos($postTag'<');
  437.  
  438.                 // Strip tag
  439.                 continue;
  440.             }
  441.  
  442.             /*
  443.              * Time to grab any attributes from the tag... need this section in
  444.              * case attributes have spaces in the values.
  445.              */
  446.             while ($currentSpace !== false)
  447.             {
  448.                 $attr '';
  449.                 $fromSpace substr($tagLeft($currentSpace 1));
  450.                 $nextEqual strpos($fromSpace'=');
  451.                 $nextSpace strpos($fromSpace' ');
  452.                 $openQuotes strpos($fromSpace'"');
  453.                 $closeQuotes strpos(substr($fromSpace($openQuotes 1))'"'$openQuotes 1;
  454.  
  455.                 $startAtt '';
  456.                 $startAttPosition 0;
  457.  
  458.                 // Find position of equal and open quotes ignoring
  459.                 if (preg_match('#\s*=\s*\"#'$fromSpace$matchesPREG_OFFSET_CAPTURE))
  460.                 {
  461.                     $startAtt $matches[0][0];
  462.                     $startAttPosition $matches[0][1];
  463.                     $closeQuotes strpos(substr($fromSpace($startAttPosition strlen($startAtt)))'"'$startAttPosition strlen($startAtt);
  464.                     $nextEqual $startAttPosition strpos($startAtt'=');
  465.                     $openQuotes $startAttPosition strpos($startAtt'"');
  466.                     $nextSpace strpos(substr($fromSpace$closeQuotes)' '$closeQuotes;
  467.                 }
  468.  
  469.                 // Do we have an attribute to process? [check for equal sign]
  470.                 if ($fromSpace != '/' && (($nextEqual && $nextSpace && $nextSpace $nextEqual|| !$nextEqual))
  471.                 {
  472.                     if (!$nextEqual)
  473.                     {
  474.                         $attribEnd strpos($fromSpace'/'1;
  475.                     }
  476.                     else
  477.                     {
  478.                         $attribEnd $nextSpace 1;
  479.                     }
  480.                     // If there is an ending, use this, if not, do not worry.
  481.                     if ($attribEnd 0)
  482.                     {
  483.                         $fromSpace substr($fromSpace$attribEnd 1);
  484.                     }
  485.                 }
  486.                 if (strpos($fromSpace'='!== false)
  487.                 {
  488.                     // If the attribute value is wrapped in quotes we need to grab the substring from
  489.                     // the closing quote, otherwise grab until the next space.
  490.                     if (($openQuotes !== false&& (strpos(substr($fromSpace($openQuotes 1))'"'!== false))
  491.                     {
  492.                         $attr substr($fromSpace0($closeQuotes 1));
  493.                     }
  494.                     else
  495.                     {
  496.                         $attr substr($fromSpace0$nextSpace);
  497.                     }
  498.                 }
  499.                 // No more equal signs so add any extra text in the tag into the attribute array [eg. checked]
  500.                 else
  501.                 {
  502.                     if ($fromSpace != '/')
  503.                     {
  504.                         $attr substr($fromSpace0$nextSpace);
  505.                     }
  506.                 }
  507.  
  508.                 // Last Attribute Pair
  509.                 if (!$attr && $fromSpace != '/')
  510.                 {
  511.                     $attr $fromSpace;
  512.                 }
  513.  
  514.                 // Add attribute pair to the attribute array
  515.                 $attrSet[$attr;
  516.  
  517.                 // Move search point and continue iteration
  518.                 $tagLeft substr($fromSpacestrlen($attr));
  519.                 $currentSpace strpos($tagLeft' ');
  520.             }
  521.  
  522.             // Is our tag in the user input array?
  523.             $tagFound in_array(strtolower($tagName)$this->tagsArray);
  524.  
  525.             // If the tag is allowed let's append it to the output string.
  526.             if ((!$tagFound && $this->tagsMethod|| ($tagFound && !$this->tagsMethod))
  527.             {
  528.                 // Reconstruct tag with allowed attributes
  529.                 if (!$isCloseTag)
  530.                 {
  531.                     // Open or single tag
  532.                     $attrSet $this->_cleanAttributes($attrSet);
  533.                     $preTag .= '<' $tagName;
  534.  
  535.                     for ($i 0$count count($attrSet)$i $count$i++)
  536.                     {
  537.                         $preTag .= ' ' $attrSet[$i];
  538.                     }
  539.  
  540.                     // Reformat single tags to XHTML
  541.                     if (strpos($fromTagOpen'</' $tagName))
  542.                     {
  543.                         $preTag .= '>';
  544.                     }
  545.                     else
  546.                     {
  547.                         $preTag .= ' />';
  548.                     }
  549.                 }
  550.                 // Closing tag
  551.                 else
  552.                 {
  553.                     $preTag .= '</' $tagName '>';
  554.                 }
  555.             }
  556.  
  557.             // Find next tag's start and continue iteration
  558.             $postTag substr($postTag($tagLength 2));
  559.             $tagOpen_start strpos($postTag'<');
  560.         }
  561.  
  562.         // Append any code after the end of tags and return
  563.         if ($postTag != '<')
  564.         {
  565.             $preTag .= $postTag;
  566.         }
  567.  
  568.         return $preTag;
  569.     }
  570.  
  571.     /**
  572.      * Internal method to strip a tag of certain attributes
  573.      *
  574.      * @param   array  $attrSet  Array of attribute pairs to filter
  575.      *
  576.      * @return  array  Filtered array of attribute pairs
  577.      *
  578.      * @since   11.1
  579.      */
  580.     protected function _cleanAttributes($attrSet)
  581.     {
  582.         $newSet array();
  583.  
  584.         $count count($attrSet);
  585.  
  586.         // Iterate through attribute pairs
  587.         for ($i 0$i $count$i++)
  588.         {
  589.             // Skip blank spaces
  590.             if (!$attrSet[$i])
  591.             {
  592.                 continue;
  593.             }
  594.  
  595.             // Split into name/value pairs
  596.             $attrSubSet explode('='trim($attrSet[$i])2);
  597.  
  598.             // Take the last attribute in case there is an attribute with no value
  599.             $attrSubSet[0array_pop(explode(' 'trim($attrSubSet[0])));
  600.  
  601.             // Remove all "non-regular" attribute names
  602.             // AND blacklisted attributes
  603.  
  604.             if ((!preg_match('/[a-z]*$/i'$attrSubSet[0]))
  605.                 || (($this->xssAuto&& ((in_array(strtolower($attrSubSet[0])$this->attrBlacklist))
  606.                 || (substr($attrSubSet[0]02== 'on'))))
  607.             {
  608.                 continue;
  609.             }
  610.  
  611.             // XSS attribute value filtering
  612.             if (isset($attrSubSet[1]))
  613.             {
  614.                 // Trim leading and trailing spaces
  615.                 $attrSubSet[1trim($attrSubSet[1]);
  616.  
  617.                 // Strips unicode, hex, etc
  618.                 $attrSubSet[1str_replace('&#'''$attrSubSet[1]);
  619.  
  620.                 // Strip normal newline within attr value
  621.                 $attrSubSet[1preg_replace('/[\n\r]/'''$attrSubSet[1]);
  622.  
  623.                 // Strip double quotes
  624.                 $attrSubSet[1str_replace('"'''$attrSubSet[1]);
  625.  
  626.                 // Convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr values)
  627.                 if ((substr($attrSubSet[1]01== "'"&& (substr($attrSubSet[1](strlen($attrSubSet[1]1)1== "'"))
  628.                 {
  629.                     $attrSubSet[1substr($attrSubSet[1]1(strlen($attrSubSet[1]2));
  630.                 }
  631.                 // Strip slashes
  632.                 $attrSubSet[1stripslashes($attrSubSet[1]);
  633.             }
  634.             else
  635.             {
  636.                 continue;
  637.             }
  638.  
  639.             // Autostrip script tags
  640.             if (self::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   11.1
  681.      */
  682.     protected function _decode($source)
  683.     {
  684.         static $ttr;
  685.  
  686.         if (!is_array($ttr))
  687.         {
  688.             // Entity decode
  689.             if (version_compare(PHP_VERSION'5.3.4''>='))
  690.             {
  691.                 $trans_tbl get_html_translation_table(HTML_ENTITIESENT_COMPAT'ISO-8859-1');
  692.             }
  693.             else
  694.             {
  695.                 $trans_tbl get_html_translation_table(HTML_ENTITIESENT_COMPAT);
  696.             }
  697.  
  698.             foreach ($trans_tbl as $k => $v)
  699.             {
  700.                 $ttr[$vutf8_encode($k);
  701.             }
  702.         }
  703.  
  704.         $source strtr($source$ttr);
  705.  
  706.         // Convert decimal
  707.         $source preg_replace_callback('/&#(\d+);/m'function($m)
  708.         {
  709.             return utf8_encode(chr($m[1]));
  710.         }$source
  711.         );
  712.  
  713.         // Convert hex
  714.         $source preg_replace_callback('/&#x([a-f0-9]+);/mi'function($m)
  715.         {
  716.             return utf8_encode(chr('0x' $m[1]));
  717.         }$source
  718.         );
  719.  
  720.         return $source;
  721.     }
  722.  
  723.     /**
  724.      * Escape < > and " inside attribute values
  725.      *
  726.      * @param   string  $source  The source string.
  727.      *
  728.      * @return  string  Filtered string
  729.      *
  730.      * @since    11.1
  731.      */
  732.     protected function _escapeAttributeValues($source)
  733.     {
  734.         $alreadyFiltered '';
  735.         $remainder $source;
  736.         $badChars array('<''"''>');
  737.         $escapedChars array('&lt;''&quot;''&gt;');
  738.  
  739.         // Process each portion based on presence of =" and "<space>, "/>, or ">
  740.         // See if there are any more attributes to process
  741.         while (preg_match('#<[^>]*?=\s*?(\"|\')#s'$remainder$matchesPREG_OFFSET_CAPTURE))
  742.         {
  743.             // Get the portion before the attribute value
  744.             $quotePosition $matches[0][1];
  745.             $nextBefore $quotePosition strlen($matches[0][0]);
  746.  
  747.             // Figure out if we have a single or double quote and look for the matching closing quote
  748.             // Closing quote should be "/>, ">, "<space>, or " at the end of the string
  749.             $quote substr($matches[0][0]-1);
  750.             $pregMatch ($quote == '"''#(\"\s*/\s*>|\"\s*>|\"\s+|\"$)#' "#(\'\s*/\s*>|\'\s*>|\'\s+|\'$)#";
  751.  
  752.             // Get the portion after attribute value
  753.             if (preg_match($pregMatchsubstr($remainder$nextBefore)$matchesPREG_OFFSET_CAPTURE))
  754.             {
  755.                 // We have a closing quote
  756.                 $nextAfter $nextBefore $matches[0][1];
  757.             }
  758.             else
  759.             {
  760.                 // No closing quote
  761.                 $nextAfter strlen($remainder);
  762.             }
  763.  
  764.             // Get the actual attribute value
  765.             $attributeValue substr($remainder$nextBefore$nextAfter $nextBefore);
  766.  
  767.             // Escape bad chars
  768.             $attributeValue str_replace($badChars$escapedChars$attributeValue);
  769.             $attributeValue $this->_stripCSSExpressions($attributeValue);
  770.             $alreadyFiltered .= substr($remainder0$nextBefore$attributeValue $quote;
  771.             $remainder substr($remainder$nextAfter 1);
  772.         }
  773.  
  774.         // At this point, we just have to return the $alreadyFiltered and the $remainder
  775.         return $alreadyFiltered $remainder;
  776.     }
  777.  
  778.     /**
  779.      * Remove CSS Expressions in the form of <property>:expression(...)
  780.      *
  781.      * @param   string  $source  The source string.
  782.      *
  783.      * @return  string  Filtered string
  784.      *
  785.      * @since   11.1
  786.      */
  787.     protected function _stripCSSExpressions($source)
  788.     {
  789.         // Strip any comments out (in the form of /*...*/)
  790.         $test preg_replace('#\/\*.*\*\/#U'''$source);
  791.  
  792.         // Test for :expression
  793.         if (!stripos($test':expression'))
  794.         {
  795.             // Not found, so we are done
  796.             $return $source;
  797.         }
  798.         else
  799.         {
  800.             // At this point, we have stripped out the comments and have found :expression
  801.             // Test stripped string for :expression followed by a '('
  802.             if (preg_match_all('#:expression\s*\(#'$test$matches))
  803.             {
  804.                 // If found, remove :expression
  805.                 $test str_ireplace(':expression'''$test);
  806.                 $return $test;
  807.             }
  808.         }
  809.  
  810.         return $return;
  811.     }
  812. }

Documentation generated on Tue, 19 Nov 2013 15:05:35 +0100 by phpDocumentor 1.4.3