source: sourcecode/system/core/Input.php @ 1

Last change on this file since 1 was 1, checked in by dungnv, 11 years ago
File size: 17.9 KB
Line 
1<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP 5.1.6 or newer
6 *
7 * @package             CodeIgniter
8 * @author              ExpressionEngine Dev Team
9 * @copyright   Copyright (c) 2008 - 2011, EllisLab, Inc.
10 * @license             http://codeigniter.com/user_guide/license.html
11 * @link                http://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              ExpressionEngine Dev Team
27 * @link                http://codeigniter.com/user_guide/libraries/input.html
28 */
29class CI_Input {
30
31        /**
32         * IP address of the current user
33         *
34         * @var string
35         */
36        var $ip_address                         = FALSE;
37        /**
38         * user agent (web browser) being used by the current user
39         *
40         * @var string
41         */
42        var $user_agent                         = FALSE;
43        /**
44         * If FALSE, then $_GET will be set to an empty array
45         *
46         * @var bool
47         */
48        var $_allow_get_array           = TRUE;
49        /**
50         * If TRUE, then newlines are standardized
51         *
52         * @var bool
53         */
54        var $_standardize_newlines      = TRUE;
55        /**
56         * Determines whether the XSS filter is always active when GET, POST or COOKIE data is encountered
57         * Set automatically based on config setting
58         *
59         * @var bool
60         */
61        var $_enable_xss                        = FALSE;
62        /**
63         * Enables a CSRF cookie token to be set.
64         * Set automatically based on config setting
65         *
66         * @var bool
67         */
68        var $_enable_csrf                       = FALSE;
69        /**
70         * List of all HTTP request headers
71         *
72         * @var array
73         */
74        protected $headers                      = array();
75
76        /**
77         * Constructor
78         *
79         * Sets whether to globally enable the XSS processing
80         * and whether to allow the $_GET array
81         *
82         * @return      void
83         */
84        public function __construct()
85        {
86                log_message('debug', "Input Class Initialized");
87
88                $this->_allow_get_array = (config_item('allow_get_array') === TRUE);
89                $this->_enable_xss              = (config_item('global_xss_filtering') === TRUE);
90                $this->_enable_csrf             = (config_item('csrf_protection') === TRUE);
91
92                global $SEC;
93                $this->security =& $SEC;
94
95                // Do we need the UTF-8 class?
96                if (UTF8_ENABLED === TRUE)
97                {
98                        global $UNI;
99                        $this->uni =& $UNI;
100                }
101
102                // Sanitize global arrays
103                $this->_sanitize_globals();
104        }
105
106        // --------------------------------------------------------------------
107
108        /**
109         * Fetch from array
110         *
111         * This is a helper function to retrieve values from global arrays
112         *
113         * @access      private
114         * @param       array
115         * @param       string
116         * @param       bool
117         * @return      string
118         */
119        function _fetch_from_array(&$array, $index = '', $xss_clean = FALSE)
120        {
121                if ( ! isset($array[$index]))
122                {
123                        return FALSE;
124                }
125
126                if ($xss_clean === TRUE)
127                {
128                        return $this->security->xss_clean($array[$index]);
129                }
130
131                return $array[$index];
132        }
133
134        // --------------------------------------------------------------------
135
136        /**
137        * Fetch an item from the GET array
138        *
139        * @access       public
140        * @param        string
141        * @param        bool
142        * @return       string
143        */
144        function get($index = NULL, $xss_clean = FALSE)
145        {
146                // Check if a field has been provided
147                if ($index === NULL AND ! empty($_GET))
148                {
149                        $get = array();
150
151                        // loop through the full _GET array
152                        foreach (array_keys($_GET) as $key)
153                        {
154                                $get[$key] = $this->_fetch_from_array($_GET, $key, $xss_clean);
155                        }
156                        return $get;
157                }
158
159                return $this->_fetch_from_array($_GET, $index, $xss_clean);
160        }
161
162        // --------------------------------------------------------------------
163
164        /**
165        * Fetch an item from the POST array
166        *
167        * @access       public
168        * @param        string
169        * @param        bool
170        * @return       string
171        */
172        function post($index = NULL, $xss_clean = FALSE)
173        {
174                // Check if a field has been provided
175                if ($index === NULL AND ! empty($_POST))
176                {
177                        $post = array();
178
179                        // Loop through the full _POST array and return it
180                        foreach (array_keys($_POST) as $key)
181                        {
182                                $post[$key] = $this->_fetch_from_array($_POST, $key, $xss_clean);
183                        }
184                        return $post;
185                }
186
187                return $this->_fetch_from_array($_POST, $index, $xss_clean);
188        }
189
190
191        // --------------------------------------------------------------------
192
193        /**
194        * Fetch an item from either the GET array or the POST
195        *
196        * @access       public
197        * @param        string  The index key
198        * @param        bool    XSS cleaning
199        * @return       string
200        */
201        function get_post($index = '', $xss_clean = FALSE)
202        {
203                if ( ! isset($_POST[$index]) )
204                {
205                        return $this->get($index, $xss_clean);
206                }
207                else
208                {
209                        return $this->post($index, $xss_clean);
210                }
211        }
212
213        // --------------------------------------------------------------------
214
215        /**
216        * Fetch an item from the COOKIE array
217        *
218        * @access       public
219        * @param        string
220        * @param        bool
221        * @return       string
222        */
223        function cookie($index = '', $xss_clean = FALSE)
224        {
225                return $this->_fetch_from_array($_COOKIE, $index, $xss_clean);
226        }
227
228        // ------------------------------------------------------------------------
229
230        /**
231        * Set cookie
232        *
233        * Accepts six parameter, or you can submit an associative
234        * array in the first parameter containing all the values.
235        *
236        * @access       public
237        * @param        mixed
238        * @param        string  the value of the cookie
239        * @param        string  the number of seconds until expiration
240        * @param        string  the cookie domain.  Usually:  .yourdomain.com
241        * @param        string  the cookie path
242        * @param        string  the cookie prefix
243        * @param        bool    true makes the cookie secure
244        * @return       void
245        */
246        function set_cookie($name = '', $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = FALSE)
247        {
248                if (is_array($name))
249                {
250                        // always leave 'name' in last place, as the loop will break otherwise, due to $$item
251                        foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'name') as $item)
252                        {
253                                if (isset($name[$item]))
254                                {
255                                        $$item = $name[$item];
256                                }
257                        }
258                }
259
260                if ($prefix == '' AND config_item('cookie_prefix') != '')
261                {
262                        $prefix = config_item('cookie_prefix');
263                }
264                if ($domain == '' AND config_item('cookie_domain') != '')
265                {
266                        $domain = config_item('cookie_domain');
267                }
268                if ($path == '/' AND config_item('cookie_path') != '/')
269                {
270                        $path = config_item('cookie_path');
271                }
272                if ($secure == FALSE AND config_item('cookie_secure') != FALSE)
273                {
274                        $secure = config_item('cookie_secure');
275                }
276
277                if ( ! is_numeric($expire))
278                {
279                        $expire = time() - 86500;
280                }
281                else
282                {
283                        $expire = ($expire > 0) ? time() + $expire : 0;
284                }
285
286                setcookie($prefix.$name, $value, $expire, $path, $domain, $secure);
287        }
288
289        // --------------------------------------------------------------------
290
291        /**
292        * Fetch an item from the SERVER array
293        *
294        * @access       public
295        * @param        string
296        * @param        bool
297        * @return       string
298        */
299        function server($index = '', $xss_clean = FALSE)
300        {
301                return $this->_fetch_from_array($_SERVER, $index, $xss_clean);
302        }
303
304        // --------------------------------------------------------------------
305
306        /**
307        * Fetch the IP Address
308        *
309        * @return       string
310        */
311        public function ip_address()
312        {
313                if ($this->ip_address !== FALSE)
314                {
315                        return $this->ip_address;
316                }
317
318                $proxy_ips = config_item('proxy_ips');
319                if ( ! empty($proxy_ips))
320                {
321                        $proxy_ips = explode(',', str_replace(' ', '', $proxy_ips));
322                        foreach (array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP') as $header)
323                        {
324                                if (($spoof = $this->server($header)) !== FALSE)
325                                {
326                                        // Some proxies typically list the whole chain of IP
327                                        // addresses through which the client has reached us.
328                                        // e.g. client_ip, proxy_ip1, proxy_ip2, etc.
329                                        if (strpos($spoof, ',') !== FALSE)
330                                        {
331                                                $spoof = explode(',', $spoof, 2);
332                                                $spoof = $spoof[0];
333                                        }
334
335                                        if ( ! $this->valid_ip($spoof))
336                                        {
337                                                $spoof = FALSE;
338                                        }
339                                        else
340                                        {
341                                                break;
342                                        }
343                                }
344                        }
345
346                        $this->ip_address = ($spoof !== FALSE && in_array($_SERVER['REMOTE_ADDR'], $proxy_ips, TRUE))
347                                ? $spoof : $_SERVER['REMOTE_ADDR'];
348                }
349                else
350                {
351                        $this->ip_address = $_SERVER['REMOTE_ADDR'];
352                }
353
354                if ( ! $this->valid_ip($this->ip_address))
355                {
356                        $this->ip_address = '0.0.0.0';
357                }
358
359                return $this->ip_address;
360        }
361
362        // --------------------------------------------------------------------
363
364        /**
365        * Validate IP Address
366        *
367        * @access       public
368        * @param        string
369        * @param        string  ipv4 or ipv6
370        * @return       bool
371        */
372        public function valid_ip($ip, $which = '')
373        {
374                $which = strtolower($which);
375
376                // First check if filter_var is available
377                if (is_callable('filter_var'))
378                {
379                        switch ($which) {
380                                case 'ipv4':
381                                        $flag = FILTER_FLAG_IPV4;
382                                        break;
383                                case 'ipv6':
384                                        $flag = FILTER_FLAG_IPV6;
385                                        break;
386                                default:
387                                        $flag = '';
388                                        break;
389                        }
390
391                        return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flag);
392                }
393
394                if ($which !== 'ipv6' && $which !== 'ipv4')
395                {
396                        if (strpos($ip, ':') !== FALSE)
397                        {
398                                $which = 'ipv6';
399                        }
400                        elseif (strpos($ip, '.') !== FALSE)
401                        {
402                                $which = 'ipv4';
403                        }
404                        else
405                        {
406                                return FALSE;
407                        }
408                }
409
410                $func = '_valid_'.$which;
411                return $this->$func($ip);
412        }
413
414        // --------------------------------------------------------------------
415
416        /**
417        * Validate IPv4 Address
418        *
419        * Updated version suggested by Geert De Deckere
420        *
421        * @access       protected
422        * @param        string
423        * @return       bool
424        */
425        protected function _valid_ipv4($ip)
426        {
427                $ip_segments = explode('.', $ip);
428
429                // Always 4 segments needed
430                if (count($ip_segments) !== 4)
431                {
432                        return FALSE;
433                }
434                // IP can not start with 0
435                if ($ip_segments[0][0] == '0')
436                {
437                        return FALSE;
438                }
439
440                // Check each segment
441                foreach ($ip_segments as $segment)
442                {
443                        // IP segments must be digits and can not be
444                        // longer than 3 digits or greater then 255
445                        if ($segment == '' OR preg_match("/[^0-9]/", $segment) OR $segment > 255 OR strlen($segment) > 3)
446                        {
447                                return FALSE;
448                        }
449                }
450
451                return TRUE;
452        }
453
454        // --------------------------------------------------------------------
455
456        /**
457        * Validate IPv6 Address
458        *
459        * @access       protected
460        * @param        string
461        * @return       bool
462        */
463        protected function _valid_ipv6($str)
464        {
465                // 8 groups, separated by :
466                // 0-ffff per group
467                // one set of consecutive 0 groups can be collapsed to ::
468
469                $groups = 8;
470                $collapsed = FALSE;
471
472                $chunks = array_filter(
473                        preg_split('/(:{1,2})/', $str, NULL, PREG_SPLIT_DELIM_CAPTURE)
474                );
475
476                // Rule out easy nonsense
477                if (current($chunks) == ':' OR end($chunks) == ':')
478                {
479                        return FALSE;
480                }
481
482                // PHP supports IPv4-mapped IPv6 addresses, so we'll expect those as well
483                if (strpos(end($chunks), '.') !== FALSE)
484                {
485                        $ipv4 = array_pop($chunks);
486
487                        if ( ! $this->_valid_ipv4($ipv4))
488                        {
489                                return FALSE;
490                        }
491
492                        $groups--;
493                }
494
495                while ($seg = array_pop($chunks))
496                {
497                        if ($seg[0] == ':')
498                        {
499                                if (--$groups == 0)
500                                {
501                                        return FALSE;   // too many groups
502                                }
503
504                                if (strlen($seg) > 2)
505                                {
506                                        return FALSE;   // long separator
507                                }
508
509                                if ($seg == '::')
510                                {
511                                        if ($collapsed)
512                                        {
513                                                return FALSE;   // multiple collapsed
514                                        }
515
516                                        $collapsed = TRUE;
517                                }
518                        }
519                        elseif (preg_match("/[^0-9a-f]/i", $seg) OR strlen($seg) > 4)
520                        {
521                                return FALSE; // invalid segment
522                        }
523                }
524
525                return $collapsed OR $groups == 1;
526        }
527
528        // --------------------------------------------------------------------
529
530        /**
531        * User Agent
532        *
533        * @access       public
534        * @return       string
535        */
536        function user_agent()
537        {
538                if ($this->user_agent !== FALSE)
539                {
540                        return $this->user_agent;
541                }
542
543                $this->user_agent = ( ! isset($_SERVER['HTTP_USER_AGENT'])) ? FALSE : $_SERVER['HTTP_USER_AGENT'];
544
545                return $this->user_agent;
546        }
547
548        // --------------------------------------------------------------------
549
550        /**
551        * Sanitize Globals
552        *
553        * This function does the following:
554        *
555        * Unsets $_GET data (if query strings are not enabled)
556        *
557        * Unsets all globals if register_globals is enabled
558        *
559        * Standardizes newline characters to \n
560        *
561        * @access       private
562        * @return       void
563        */
564        function _sanitize_globals()
565        {
566                // It would be "wrong" to unset any of these GLOBALS.
567                $protected = array('_SERVER', '_GET', '_POST', '_FILES', '_REQUEST',
568                                                        '_SESSION', '_ENV', 'GLOBALS', 'HTTP_RAW_POST_DATA',
569                                                        'system_folder', 'application_folder', 'BM', 'EXT',
570                                                        'CFG', 'URI', 'RTR', 'OUT', 'IN');
571
572                // Unset globals for securiy.
573                // This is effectively the same as register_globals = off
574                foreach (array($_GET, $_POST, $_COOKIE) as $global)
575                {
576                        if ( ! is_array($global))
577                        {
578                                if ( ! in_array($global, $protected))
579                                {
580                                        global $$global;
581                                        $$global = NULL;
582                                }
583                        }
584                        else
585                        {
586                                foreach ($global as $key => $val)
587                                {
588                                        if ( ! in_array($key, $protected))
589                                        {
590                                                global $$key;
591                                                $$key = NULL;
592                                        }
593                                }
594                        }
595                }
596
597                // Is $_GET data allowed? If not we'll set the $_GET to an empty array
598                if ($this->_allow_get_array == FALSE)
599                {
600                        $_GET = array();
601                }
602                else
603                {
604                        if (is_array($_GET) AND count($_GET) > 0)
605                        {
606                                foreach ($_GET as $key => $val)
607                                {
608                                        $_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
609                                }
610                        }
611                }
612
613                // Clean $_POST Data
614                if (is_array($_POST) AND count($_POST) > 0)
615                {
616                        foreach ($_POST as $key => $val)
617                        {
618                                $_POST[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
619                        }
620                }
621
622                // Clean $_COOKIE Data
623                if (is_array($_COOKIE) AND count($_COOKIE) > 0)
624                {
625                        // Also get rid of specially treated cookies that might be set by a server
626                        // or silly application, that are of no use to a CI application anyway
627                        // but that when present will trip our 'Disallowed Key Characters' alarm
628                        // http://www.ietf.org/rfc/rfc2109.txt
629                        // note that the key names below are single quoted strings, and are not PHP variables
630                        unset($_COOKIE['$Version']);
631                        unset($_COOKIE['$Path']);
632                        unset($_COOKIE['$Domain']);
633
634                        foreach ($_COOKIE as $key => $val)
635                        {
636                                $_COOKIE[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
637                        }
638                }
639
640                // Sanitize PHP_SELF
641                $_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']);
642
643
644                // CSRF Protection check on HTTP requests
645                if ($this->_enable_csrf == TRUE && ! $this->is_cli_request())
646                {
647                        $this->security->csrf_verify();
648                }
649
650                log_message('debug', "Global POST and COOKIE data sanitized");
651        }
652
653        // --------------------------------------------------------------------
654
655        /**
656        * Clean Input Data
657        *
658        * This is a helper function. It escapes data and
659        * standardizes newline characters to \n
660        *
661        * @access       private
662        * @param        string
663        * @return       string
664        */
665        function _clean_input_data($str)
666        {
667                if (is_array($str))
668                {
669                        $new_array = array();
670                        foreach ($str as $key => $val)
671                        {
672                                $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
673                        }
674                        return $new_array;
675                }
676
677                /* We strip slashes if magic quotes is on to keep things consistent
678
679                   NOTE: In PHP 5.4 get_magic_quotes_gpc() will always return 0 and
680                         it will probably not exist in future versions at all.
681                */
682                if ( ! is_php('5.4') && get_magic_quotes_gpc())
683                {
684                        $str = stripslashes($str);
685                }
686
687                // Clean UTF-8 if supported
688                if (UTF8_ENABLED === TRUE)
689                {
690                        $str = $this->uni->clean_string($str);
691                }
692
693                // Remove control characters
694                $str = remove_invisible_characters($str);
695
696                // Should we filter the input data?
697                if ($this->_enable_xss === TRUE)
698                {
699                        $str = $this->security->xss_clean($str);
700                }
701
702                // Standardize newlines if needed
703                if ($this->_standardize_newlines == TRUE)
704                {
705                        if (strpos($str, "\r") !== FALSE)
706                        {
707                                $str = str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $str);
708                        }
709                }
710
711                return $str;
712        }
713
714        // --------------------------------------------------------------------
715
716        /**
717        * Clean Keys
718        *
719        * This is a helper function. To prevent malicious users
720        * from trying to exploit keys we make sure that keys are
721        * only named with alpha-numeric text and a few other items.
722        *
723        * @access       private
724        * @param        string
725        * @return       string
726        */
727        function _clean_input_keys($str)
728        {
729                if ( ! preg_match("/^[a-z0-9:_\/-]+$/i", $str))
730                {
731                        exit('Disallowed Key Characters.');
732                }
733
734                // Clean UTF-8 if supported
735                if (UTF8_ENABLED === TRUE)
736                {
737                        $str = $this->uni->clean_string($str);
738                }
739
740                return $str;
741        }
742
743        // --------------------------------------------------------------------
744
745        /**
746         * Request Headers
747         *
748         * In Apache, you can simply call apache_request_headers(), however for
749         * people running other webservers the function is undefined.
750         *
751         * @param       bool XSS cleaning
752         *
753         * @return array
754         */
755        public function request_headers($xss_clean = FALSE)
756        {
757                // Look at Apache go!
758                if (function_exists('apache_request_headers'))
759                {
760                        $headers = apache_request_headers();
761                }
762                else
763                {
764                        $headers['Content-Type'] = (isset($_SERVER['CONTENT_TYPE'])) ? $_SERVER['CONTENT_TYPE'] : @getenv('CONTENT_TYPE');
765
766                        foreach ($_SERVER as $key => $val)
767                        {
768                                if (strncmp($key, 'HTTP_', 5) === 0)
769                                {
770                                        $headers[substr($key, 5)] = $this->_fetch_from_array($_SERVER, $key, $xss_clean);
771                                }
772                        }
773                }
774
775                // take SOME_HEADER and turn it into Some-Header
776                foreach ($headers as $key => $val)
777                {
778                        $key = str_replace('_', ' ', strtolower($key));
779                        $key = str_replace(' ', '-', ucwords($key));
780
781                        $this->headers[$key] = $val;
782                }
783
784                return $this->headers;
785        }
786
787        // --------------------------------------------------------------------
788
789        /**
790         * Get Request Header
791         *
792         * Returns the value of a single member of the headers class member
793         *
794         * @param       string          array key for $this->headers
795         * @param       boolean         XSS Clean or not
796         * @return      mixed           FALSE on failure, string on success
797         */
798        public function get_request_header($index, $xss_clean = FALSE)
799        {
800                if (empty($this->headers))
801                {
802                        $this->request_headers();
803                }
804
805                if ( ! isset($this->headers[$index]))
806                {
807                        return FALSE;
808                }
809
810                if ($xss_clean === TRUE)
811                {
812                        return $this->security->xss_clean($this->headers[$index]);
813                }
814
815                return $this->headers[$index];
816        }
817
818        // --------------------------------------------------------------------
819
820        /**
821         * Is ajax Request?
822         *
823         * Test to see if a request contains the HTTP_X_REQUESTED_WITH header
824         *
825         * @return      boolean
826         */
827        public function is_ajax_request()
828        {
829                return ($this->server('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest');
830        }
831
832        // --------------------------------------------------------------------
833
834        /**
835         * Is cli Request?
836         *
837         * Test to see if a request was made from the command line
838         *
839         * @return      bool
840         */
841        public function is_cli_request()
842        {
843                return (php_sapi_name() === 'cli' OR defined('STDIN'));
844        }
845
846}
847
848/* End of file Input.php */
849/* Location: ./system/core/Input.php */
Note: See TracBrowser for help on using the repository browser.