Source for file Input.php

Documentation is available at Input.php

  1. <?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
  2. /**
  3.  * Code Igniter
  4.  *
  5.  * An open source application development framework for PHP 4.3.2 or newer
  6.  *
  7.  * @package        CodeIgniter
  8.  * @author        Rick Ellis
  9.  * @copyright    Copyright (c) 2006, pMachine, Inc.
  10.  * @license        http://www.codeignitor.com/user_guide/license.html
  11.  * @link        http://www.codeigniter.com
  12.  * @since        Version 1.0
  13.  * @filesource
  14.  */
  15.  
  16. // ------------------------------------------------------------------------
  17.  
  18. /**
  19.  * Input Class
  20.  * 
  21.  * Pre-processes global input data for security
  22.  *
  23.  * @package        CodeIgniter
  24.  * @subpackage    Libraries
  25.  * @category    Input
  26.  * @author        Rick Ellis
  27.  * @link        http://www.codeigniter.com/user_guide/libraries/input.html
  28.  */
  29. class CI_Input {
  30.     var $use_xss_clean        = FALSE;
  31.     var $ip_address            = FALSE;
  32.     var $user_agent            = FALSE;
  33.     var $allow_get_array    = FALSE;
  34.     
  35.     /**
  36.      * Constructor
  37.      *
  38.      * Sets whether to globally enable the XSS processing
  39.      * and whether to allow the $_GET array
  40.      *
  41.      * @access    public
  42.      */    
  43.     function CI_Input()
  44.     {    
  45.         $CFG =_load_class('CI_Config');
  46.         $this->use_xss_clean    = ($CFG->item('global_xss_filtering'=== TRUETRUE FALSE;
  47.         $this->allow_get_array    = ($CFG->item('enable_query_strings'=== TRUETRUE FALSE;
  48.         
  49.         log_message('debug'"Input Class Initialized");
  50.         $this->_sanitize_globals();
  51.     }
  52.     // END CI_Input()
  53.     
  54.     // --------------------------------------------------------------------
  55.     
  56.         
  57.     /**
  58.      * Sanitize Globals
  59.      *
  60.      * This function does the folowing:
  61.      *
  62.      * Unsets $_GET data (if query strings are not enabled)
  63.      *
  64.      * Unsets all globals if register_globals is enabled
  65.      *
  66.      * Standardizes newline characters to \n
  67.      *
  68.      * @access    private
  69.      * @return    void 
  70.      */
  71.     function _sanitize_globals()
  72.     {
  73.         // Unset globals. This is effectively the same as register_globals = off
  74.         foreach (array($_GET$_POST$_COOKIEas $global)
  75.         {
  76.             if is_array($global))
  77.             {
  78.                 unset($$global);
  79.             }
  80.             else
  81.             {
  82.                 foreach ($global as $key => $val)
  83.                 {
  84.                     unset($$key);
  85.                 }    
  86.             }
  87.         }
  88.  
  89.         // Is $_GET data allowed?
  90.         if ($this->allow_get_array == FALSE)
  91.         {
  92.             $_GET array();
  93.         }
  94.         
  95.         // Clean $_POST Data
  96.         if (is_array($_POSTAND count($_POST0)
  97.         {
  98.             foreach($_POST as $key => $val)
  99.             {                
  100.                 if (is_array($val))
  101.                 {     
  102.                     foreach($val as $k => $v)
  103.                     {                    
  104.                         $_POST[$this->_clean_input_keys($key)][$this->_clean_input_keys($k)$this->_clean_input_data($v);
  105.                     }
  106.                 }
  107.                 else
  108.                 {
  109.                     $_POST[$this->_clean_input_keys($key)$this->_clean_input_data($val);
  110.                 }
  111.             }            
  112.         }
  113.     
  114.         // Clean $_COOKIE Data
  115.         if (is_array($_COOKIEAND count($_COOKIE0)
  116.         {
  117.             foreach($_COOKIE as $key => $val)
  118.             {              
  119.                 $_COOKIE[$this->_clean_input_keys($key)$this->_clean_input_data($val);
  120.             }    
  121.         }
  122.         
  123.         log_message('debug'"Global POST and COOKIE data sanitized");
  124.     }    
  125.     // END _sanitize_globals()
  126.     
  127.     // --------------------------------------------------------------------
  128.     
  129.         
  130.     /**
  131.      * Clean Intput Data
  132.      *
  133.      * This is a helper function. It escapes data and
  134.      * standardizes newline characters to \n
  135.      *
  136.      * @access    private
  137.      * @param    string 
  138.      * @return    string 
  139.      */    
  140.     function _clean_input_data($str)
  141.     {
  142.         if (is_array($str))
  143.         {
  144.             $new_array array();
  145.             foreach ($str as $key => $val)
  146.             {
  147.                 $new_array[$key$this->_clean_input_data($val);
  148.             }
  149.             return $new_array;
  150.         }
  151.         
  152.         if ($this->use_xss_clean === TRUE)
  153.         {
  154.             $str $this->xss_clean($str);
  155.         }
  156.         
  157.         return preg_replace("/\015\012|\015|\012/""\n"$str);
  158.     }
  159.     // END _clean_input_data()
  160.     
  161.     // --------------------------------------------------------------------
  162.     
  163.         
  164.     /**
  165.      * Clean Keys
  166.      *
  167.      * This is a helper function. To prevent malicious users
  168.      * from trying to exploit keys we make sure that keys are
  169.      * only named with alpha-numeric text and a few other items.
  170.      *
  171.      * @access    private
  172.      * @param    string 
  173.      * @return    string 
  174.      */
  175.     function _clean_input_keys($str)
  176.     {    
  177.          if preg_match("/^[a-z0-9:_\/-]+$/i"$str))
  178.          
  179.             exit('Disallowed Key Characters: '.$str);
  180.          }
  181.     
  182.         if get_magic_quotes_gpc())
  183.         {
  184.            return addslashes($str);
  185.         }
  186.         
  187.         return $str;
  188.     }
  189.     // END _clean_input_keys()
  190.     
  191.     // --------------------------------------------------------------------
  192.     
  193.         
  194.     /**
  195.      * Fetch an item from the POST array
  196.      *
  197.      * @access    public
  198.      * @param    string 
  199.      * @return    string 
  200.      */
  201.     function post($index ''$xss_clean FALSE)
  202.     {        
  203.         if isset($_POST[$index]))
  204.         {
  205.             return FALSE;
  206.         }
  207.         else
  208.         {
  209.             if ($xss_clean === TRUE)
  210.             {
  211.                 return $this->xss_clean($_POST[$index]);
  212.             }
  213.             else
  214.             {
  215.                 return $_POST[$index];
  216.             }
  217.         }
  218.     }
  219.     // END post()
  220.     
  221.     // --------------------------------------------------------------------
  222.     
  223.         
  224.     /**
  225.      * Fetch an item from the COOKIE array
  226.      *
  227.      * @access    public
  228.      * @param    string 
  229.      * @return    string 
  230.      */
  231.     function cookie($index ''$xss_clean FALSE)
  232.     {
  233.         if isset($_COOKIE[$index]))
  234.         {
  235.             return FALSE;
  236.         }
  237.         else
  238.         {
  239.             if ($xss_clean === TRUE)
  240.             {
  241.                 return $this->xss_clean($_COOKIE[$index]);
  242.             }
  243.             else
  244.             {
  245.                 return $_COOKIE[$index];
  246.             }
  247.         }
  248.     }
  249.     // END cookie()
  250.     
  251.     // --------------------------------------------------------------------
  252.     
  253.         
  254.     /**
  255.      * Fetch the IP Address
  256.      *
  257.      * @access    public
  258.      * @return    string 
  259.      */
  260.     function ip_address()
  261.     {
  262.         if ($this->ip_address !== FALSE)
  263.         {
  264.             return $this->ip_address;
  265.         }
  266.     
  267.         $cip (isset($_SERVER['HTTP_CLIENT_IP']AND $_SERVER['HTTP_CLIENT_IP'!= ""$_SERVER['HTTP_CLIENT_IP'FALSE;
  268.         $rip (isset($_SERVER['REMOTE_ADDR']AND $_SERVER['REMOTE_ADDR'!= ""$_SERVER['REMOTE_ADDR'FALSE;
  269.         $fip (isset($_SERVER['HTTP_X_FORWARDED_FOR']AND $_SERVER['HTTP_X_FORWARDED_FOR'!= ""$_SERVER['HTTP_X_FORWARDED_FOR'FALSE;
  270.                     
  271.         if ($cip && $rip)    $this->ip_address = $cip;    
  272.         elseif ($rip)        $this->ip_address = $rip;
  273.         elseif ($cip)        $this->ip_address = $cip;
  274.         elseif ($fip)        $this->ip_address = $fip;
  275.         
  276.         if (strstr($this->ip_address','))
  277.         {
  278.             $x explode(','$this->ip_address);
  279.             $this->ip_address = end($x);
  280.         }
  281.         
  282.         if $this->valid_ip($this->ip_address))
  283.         {
  284.             $this->ip_address = '0.0.0.0';
  285.         }
  286.         
  287.         unset($cip);
  288.         unset($rip);
  289.         unset($fip);
  290.         
  291.         return $this->ip_address;
  292.     }
  293.     // END ip_address()
  294.     
  295.     // --------------------------------------------------------------------
  296.     
  297.         
  298.     /**
  299.      * Validate IP Address
  300.      *
  301.      * @access    public
  302.      * @param    string 
  303.      * @return    string 
  304.      */
  305.     function valid_ip($ip)
  306.     {
  307.         return preg_match"/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/"$ip)) FALSE TRUE;
  308.     }
  309.     // END valid_ip()
  310.     
  311.     // --------------------------------------------------------------------
  312.     
  313.         
  314.     /**
  315.      * User Agent
  316.      *
  317.      * @access    public
  318.      * @return    string 
  319.      */
  320.     function user_agent()
  321.     {
  322.         if ($this->user_agent !== FALSE)
  323.         {
  324.             return $this->user_agent;
  325.         }
  326.     
  327.         $this->user_agent = isset($_SERVER['HTTP_USER_AGENT'])) FALSE $_SERVER['HTTP_USER_AGENT'];
  328.         
  329.         return $this->user_agent;
  330.     }
  331.     // END user_agent()
  332.     
  333.     // --------------------------------------------------------------------
  334.     
  335.         
  336.     /**
  337.      * XSS Clean
  338.      *
  339.      * Sanitizes data so that Cross Site Scripting Hacks can be
  340.      * prevented.Ê This function does a fair amount of work but
  341.      * it is extremely thorough, designed to prevent even the
  342.      * most obscure XSS attempts.Ê Nothing is ever 100% foolproof,
  343.      * of course, but I haven't been able to get anything passed
  344.      * the filter.
  345.      *
  346.      * Note: This function should only be used to deal with data
  347.      * upon submission.Ê It's not something that should
  348.      * be used for general runtime processing.
  349.      *
  350.      * This function was based in part on some code and ideas I
  351.      * got from Bitflux: http://blog.bitflux.ch/wiki/XSS_Prevention
  352.      *
  353.      * To help develop this script I used this great list of
  354.      * vulnerabilities along with a few other hacks I've
  355.      * harvested from examining vulnerabilities in other programs:
  356.      * http://ha.ckers.org/xss.html
  357.      *
  358.      * @access    public
  359.      * @param    string 
  360.      * @return    string 
  361.      */
  362.     function xss_clean($str$charset 'ISO-8859-1')
  363.     {    
  364.         /*
  365.          * Remove Null Characters
  366.          *
  367.          * This prevents sandwiching null characters
  368.          * between ascii characters, like Java\0script.
  369.          *
  370.          */
  371.         $str preg_replace('/\0+/'''$str);
  372.         $str preg_replace('/(\\\\0)+/'''$str);
  373.  
  374.         /*
  375.          * Validate standard character entites
  376.          *
  377.          * Add a semicolon if missing.  We do this to enable
  378.          * the conversion of entities to ASCII later.
  379.          *
  380.          */
  381.         $str preg_replace('#(&\#*\w+)[\x00-\x20]+;#u',"\\1;",$str);
  382.         
  383.         /*
  384.          * Validate UTF16 two byte encodeing (x00) 
  385.          *
  386.          * Just as above, adds a semicolon if missing.
  387.          *
  388.          */
  389.         $str preg_replace('#(&\#x*)([0-9A-F]+);*#iu',"\\1\\2;",$str);
  390.  
  391.         /*
  392.          * URL Decode
  393.          *
  394.          * Just in case stuff like this is submitted:
  395.          *
  396.          * <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
  397.          *
  398.          * Note: Normally urldecode() would be easier but it removes plus signs
  399.          *
  400.          */    
  401.         $str preg_replace("/%u0([a-z0-9]{3})/i""&#x\\1;"$str);
  402.         $str preg_replace("/%([a-z0-9]{2})/i""&#x\\1;"$str);        
  403.                 
  404.         /*
  405.          * Convert character entities to ASCII 
  406.          *
  407.          * This permits our tests below to work reliably.
  408.          * We only convert entities that are within tags since
  409.          * these are the ones that will pose security problems.
  410.          *
  411.          */
  412.          
  413.         if (preg_match_all("/<(.+?)>/si"$str$matches))
  414.         {        
  415.             for ($i 0$i count($matches['0'])$i++)
  416.             {
  417.                 $str str_replace($matches['1'][$i]
  418.                                     $this->_html_entity_decode($matches['1'][$i]$charset)
  419.                                     $str);
  420.             }
  421.         }
  422.     
  423.         /*
  424.          * Convert all tabs to spaces
  425.          *
  426.          * This prevents strings like this: ja    vascript
  427.          * Note: we deal with spaces between characters later.
  428.          *
  429.          */        
  430.         $str preg_replace("#\t+#"" "$str);
  431.     
  432.         /*
  433.          * Makes PHP tags safe
  434.          *
  435.          *  Note: XML tags are inadvertently replaced too:
  436.          *
  437.          *    <?xml
  438.          *
  439.          * But it doesn't seem to pose a problem.
  440.          *
  441.          */        
  442.         $str str_replace(array('<?php''<?PHP''<?''?>'),  array('&lt;?php''&lt;?PHP''&lt;?''?&gt;')$str);
  443.     
  444.         /*
  445.          * Compact any exploded words
  446.          *
  447.          * This corrects words like:  j a v a s c r i p t
  448.          * These words are compacted back to their correct state.
  449.          *
  450.          */        
  451.         $words array('javascript''vbscript''script''applet''alert''document''write''cookie''window');
  452.         foreach ($words as $word)
  453.         {
  454.             $temp '';
  455.             for ($i 0$i strlen($word)$i++)
  456.             {
  457.                 $temp .= substr($word$i1)."\s*";
  458.             }
  459.             
  460.             $temp substr($temp0-3);
  461.             $str preg_replace('#'.$temp.'#s'$word$str);
  462.             $str preg_replace('#'.ucfirst($temp).'#s'ucfirst($word)$str);
  463.         }
  464.     
  465.         /*
  466.          * Remove disallowed Javascript in links or img tags
  467.          */        
  468.          $str preg_replace("#<a.+?href=.*?(alert\(|alert&\#40;|javascript\:|window\.|document\.|\.cookie|<script|<xss).*?\>.*?</a>#si"""$str);
  469.          $str preg_replace("#<img.+?src=.*?(alert\(|alert&\#40;|javascript\:|window\.|document\.|\.cookie|<script|<xss).*?\>#si"""$str);
  470.          $str preg_replace("#<(script|xss).*?\>#si"""$str);
  471.  
  472.         /*
  473.          * Remove JavaScript Event Handlers
  474.          *
  475.          * Note: This code is a little blunt.  It removes
  476.          * the event handler and anything up to the closing >, 
  477.          * but it's unlkely to be a problem.
  478.          *
  479.          */        
  480.          $str preg_replace('#(<[^>]+.*?)(onblur|onchange|onclick|onfocus|onload|onmouseover|onmouseup|onmousedown|onselect|onsubmit|onunload|onkeypress|onkeydown|onkeyup|onresize)[^>]*>#iU',"\\1>",$str);
  481.     
  482.         /*
  483.          * Sanitize naughty HTML elements
  484.          *
  485.          * If a tag containing any of the words in the list 
  486.          * below is found, the tag gets converted to entities.
  487.          *
  488.          * So this: <blink>
  489.          * Becomes: &lt;blink&gt;
  490.          *
  491.          */        
  492.         $str preg_replace('#<(/*\s*)(alert|applet|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|layer|link|meta|object|plaintext|style|script|textarea|title|xml|xss)([^>]*)>#is'"&lt;\\1\\2\\3&gt;"$str);
  493.         
  494.         /*
  495.          * Sanitize naughty scripting elements
  496.          *
  497.          * Similar to above, only instead of looking for
  498.          * tags it looks for PHP and JavaScript commands
  499.          * that are disallowed.  Rather than removing the
  500.          * code, it simply converts the parenthesis to entities
  501.          * rendering the code unexecutable.
  502.          *
  503.          * For example:    eval('some code')
  504.          * Becomes:        eval&#40;'some code'&#41;
  505.          *
  506.          */
  507.         $str preg_replace('#(alert|cmd|passthru|eval|exec|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si'"\\1\\2&#40;\\3&#41;"$str);
  508.                         
  509.         /*
  510.          * Final clean up
  511.          *
  512.          * This adds a bit of extra precaution in case
  513.          * something got through the above filters
  514.          *
  515.          */    
  516.         $bad array(
  517.                         'document.cookie'    => '',
  518.                         'document.write'    => '',
  519.                         'window.location'    => '',
  520.                         "javascript\s*:"    => '',
  521.                         "Redirect\s+302"    => '',
  522.                         '<!--'                => '&lt;!--',
  523.                         '-->'                => '--&gt;'
  524.                     );
  525.     
  526.         foreach ($bad as $key => $val)
  527.         {
  528.             $str preg_replace("#".$key."#i"$val$str);   
  529.         }
  530.         
  531.                         
  532.         log_message('debug'"XSS Filtering completed");
  533.         return $str;
  534.     }
  535.     // END xss_clean()
  536.  
  537.  
  538.     
  539.     /**
  540.      * HTML Entities Decode
  541.      *
  542.      * This function is a replacement for html_entity_decode()
  543.      *
  544.      * In some versions of PHP the native function does not work
  545.      * when UTF-8 is the specified character set, so this gives us
  546.      * a work-around.  More info here:
  547.      * http://bugs.php.net/bug.php?id=25670
  548.      *
  549.      * @access    private
  550.      * @param    string 
  551.      * @param    string 
  552.      * @return    string 
  553.      */
  554.     /* -------------------------------------------------
  555.     /*  Replacement for html_entity_decode()
  556.     /* -------------------------------------------------*/
  557.     
  558.     /*
  559.     NOTE: html_entity_decode() has a bug in some PHP versions when UTF-8 is the 
  560.     character set, and the PHP developers said they were not back porting the
  561.     fix to versions other than PHP 5.x.
  562.     */
  563.     function _html_entity_decode($str$charset='ISO-8859-1'
  564.     {
  565.         if (stristr($str'&'=== FALSEreturn $str;
  566.     
  567.         // The reason we are not using html_entity_decode() by itself is because
  568.         // while it is not technically correct to leave out the semicolon
  569.         // at the end of an entity most browsers will still interpret the entity
  570.         // correctly.  html_entity_decode() does not convert entities without
  571.         // semicolons, so we are left with our own little solution here. Bummer.
  572.     
  573.         if (function_exists('html_entity_decode'&& (strtolower($charset!= 'utf-8' OR version_compare(phpversion()'5.0.0''>=')))
  574.         {
  575.             $str html_entity_decode($str