CI框架源码解析十二之输入类文件Input.php

        输入类有两个用途:一:为了安全性,对输入数据进行预处理;二:提供了一些辅助方法来获取输入数据并处理。并且该类由系统自动加载,你无需手工加载。

① 对输入进行过滤

        安全性过滤:当访问控制器时,安全过滤方法会自动被调用,它做了以下几件事情:首先,如果 $config['allow_get_array'] 设置为 FALSE (默认是 TRUE),销毁全局的 GET 数组。其次,当开启 register_globals 时,销毁所有的全局变量。 然后,过滤 GET/POST/COOKIE 数据的键值,只允许出现字母和数字(和其他一些)字符。再然后,提供了 XSS (跨站脚本攻击)过滤,可全局启用,或按需启用。 最后,将换行符统一为 PHP_EOL (基于 UNIX 的系统下为 \n,Windows 系统下为 \r\n),这个是可配置的。

        XSS 过滤:输入类可以自动的对输入数据进行过滤,来阻止跨站脚本攻击。如果你希望在每次遇到 POST 或 COOKIE 数据时自动运行过滤,你可以在 application/config/config.php 配置文件中设置如下参数:

    $config['global_xss_filtering'] = TRUE;
注:参数 'global_xss_filtering' 已经废弃,保留它只是为了实现向前兼容。 XSS 过滤应该在*输出*的时候进行,而不是*输入*的时候!

② 访问表单数据

        使用 POST、GET、COOKIE 和 SERVER 数据:CodeIgniter 提供了几个辅助方法来从 POST、GET、COOKIE 和 SERVER 数组中获取数据。使用这些方法来获取数据而不是直接访问数组($_POST['something'])的最大的好处是,这些方法会检查获取的数据是否存在,如果不存在则返回 NULL 。这使用起来将很方便,你不再需要去检查数据是否存在。换句话说,通常你需要像下面这样做:$something = isset($_POST['something']) ? $_POST['something'] : NULL;,使用 CodeIgniter 的方法,你可以简单的写成:$something = $this->input->post('something');。

        使用 php://input 流:如果你需要使用 PUT、DELETE、PATCH 或其他的请求方法,你只能通过一个特殊的输入流来访问,这个流只能被读一次,这和从诸如 $_POST 数组中读取数据相比起来要复杂一点,因为 POST 数组可以被访问多次来获取多个变量,而不用担心它会消失。CodeIgniter 为你解决了这个问题,你只需要使用下面的 $raw_input_stream 属性即可,就可以在任何时候读取 php://input 流中的数据:$this->input->raw_input_stream;,另外,如果输入流的格式和 $_POST 数组一样,你也可以通过 input_stream() 方法来访问它的值:$this->input->input_stream('key');,和其他的 get() 和 post() 方法类似,如果请求的数据不存在,则返回 NULL 。你也可以将第二个参数设置为 TRUE ,来让数据经过 xss_clean() 的检查:

    $this->input->input_stream('key', TRUE); // XSS Clean
    $this->input->input_stream('key', FALSE); // No XSS filter
注:你可以使用 method() 方法来获取你读取的是什么数据,PUT、DELETE 还是 PATCH 。

        就说这么多吧,我翻看了一下CI框架手册,上面关于这个类讲的还是比较详细的,大家可以参考查看。最后按照惯例贴一下整个输入类Input.php文件的源码(注释版):

    _allow_get_array = (config_item('allow_get_array') === TRUE);
            $this->_enable_xss = (config_item('global_xss_filtering') === TRUE);
            $this->_enable_csrf = (config_item('csrf_protection') === TRUE);
            $this->_standardize_newlines = (bool)config_item('standardize_newlines');
            $this->security =& load_class('Security', 'core');
            //判断是否加载UTF8编码类
            if (UTF8_ENABLED === TRUE) {
                $this->uni =& load_class('Utf8', 'core');
            }
            //处理全局变量数组
            $this->_sanitize_globals();
            //CSRF保护检查
            if ($this->_enable_csrf === TRUE && !is_cli()) {
                $this->security->csrf_verify();
            }
            log_message('info', 'Input Class Initialized');
        }
    
        /**
         * 从$array数组中取得某个key的值,并可设置是否进行xss过滤
         */
        protected function _fetch_from_array(&$array, $index = NULL, $xss_clean = NULL)
        {
            is_bool($xss_clean) OR $xss_clean = $this->_enable_xss;
    
            //如果$index为空,则表示整个数组已被请求
            isset($index) OR $index = array_keys($array);
            //允许立即取出多个键
            if (is_array($index)) {
                $output = array();
                foreach ($index as $key) {
                    $output[$key] = $this->_fetch_from_array($array, $key, $xss_clean);
                }
                return $output;
            }
            //对索引进行判断
            if (isset($array[$index])) {
                $value = $array[$index];
            } elseif (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 1) {
                //索引是否包含数组符号
                $value = $array;
                for ($i = 0; $i < $count; $i++) {
                    $key = trim($matches[0][$i], '[]');
                    //空符号将返回的值为阵列
                    if ($key === '') {
                        break;
                    }
                    if (isset($value[$key])) {
                        $value = $value[$key];
                    } else {
                        return NULL;
                    }
                }
            } else {
                return NULL;
            }
            return ($xss_clean === TRUE) ? $this->security->xss_clean($value) : $value;
        }
    
        /**
         * 获取$_GET值,并可进行xss过滤
         * 可以看出获取不指定key也可以进行过滤:$this->input->get(NULL, TRUE);
         */
        public function get($index = NULL, $xss_clean = NULL)
        {
            return $this->_fetch_from_array($_GET, $index, $xss_clean);
        }
    
        /**
         * 获取$_POST中指定item,和get()原理一样
         * 第一个参数为你想要获取的 POST 数据名
         * 第二个参数可选,用于决定是否使用 XSS 过滤器对数据进行过滤。
         */
        public function post($index = NULL, $xss_clean = NULL)
        {
            return $this->_fetch_from_array($_POST, $index, $xss_clean);
        }
    
        /**
         * 可同时获取$_GET值或$_POST值
         * 该方法和 post() 和 get() 方法类似,
         * 它会同时查找 POST 和 GET 两个数组来获取数据,先查找 POST ,再查找 GET
         */
        public function post_get($index, $xss_clean = NULL)
        {
            return isset($_POST[$index]) ? $this->post($index, $xss_clean) : $this->get($index, $xss_clean);
        }
    
        /**
         * 可同时获取$_GET值或$_POST值
         * 该方法和 post_get() 方法一样,只是它先查找 GET 数据
         */
        public function get_post($index, $xss_clean = NULL)
        {
            return isset($_GET[$index]) ? $this->get($index, $xss_clean) : $this->post($index, $xss_clean);
        }
    
        /**
         * 获取HTTP Cookies超级全局变量$_COOKIE的值
         * 该方法和 post() 和 get() 方法一样,只是它用于获取 COOKIE 数据
         */
        public function cookie($index = NULL, $xss_clean = NULL)
        {
            return $this->_fetch_from_array($_COOKIE, $index, $xss_clean);
        }
    
        /**
         * 获取超级全局变量$_SERVER的值
         * 该方法和 post() 、 get() 和 cookie() 方法一样,只是它用于获取 SERVER 数据
         */
        public function server($index, $xss_clean = NULL)
        {
            return $this->_fetch_from_array($_SERVER, $index, $xss_clean);
        }
    
        /**
         * 输入流数据
         * 该方法和 get() 、 post() 和 cookie() 方法一样,只是它用于获取 php://input 流数据
         */
        public function input_stream($index = NULL, $xss_clean = NULL)
        {
            if (!is_array($this->_input_stream)) {
                parse_str($this->raw_input_stream, $this->_input_stream);
                is_array($this->_input_stream) OR $this->_input_stream = array();
            }
            return $this->_fetch_from_array($this->_input_stream, $index, $xss_clean);
        }
    
        /**
         * 设置cookie
         * $name  可作为数组形式传入所有参数
         * $value  设置cookie的值
         * $expire 设置cookie的有效时间
         * $domain 设置cookie的有效域名
         * $path 设置cookie的有效路径
         * $prefix 设置cookie前缀
         * $secure 设置是否在安全的HTTPS传输cookie有效
         */
        public function set_cookie($name, $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = FALSE, $httponly = FALSE)
        {
            if (is_array($name)) {
                foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'httponly', 'name') as $item) {
                    if (isset($name[$item])) {
                        $$item = $name[$item];
                    }
                }
            }
            //是否配置cookie前缀
            if ($prefix === '' && config_item('cookie_prefix') !== '') {
                $prefix = config_item('cookie_prefix');
            }
            //是否配置cookie有效域名
            if ($domain == '' && config_item('cookie_domain') != '') {
                $domain = config_item('cookie_domain');
            }
            //是否配置cookie的有效路径,默认是当前目录
            if ($path === '/' && config_item('cookie_path') !== '/') {
                $path = config_item('cookie_path');
            }
            //规定是否通过安全的 HTTPS 连接来传输 cookie。
            if ($secure === FALSE && config_item('cookie_secure') === TRUE) {
                $secure = config_item('cookie_secure');
            }
            if ($httponly === FALSE && config_item('cookie_httponly') !== FALSE) {
                $httponly = config_item('cookie_httponly');
            }
            //设置cookie的过期时间,默认:默认在会话结束【浏览器关闭】失效
            if (!is_numeric($expire)) {
                $expire = time() - 86500;
            } else {
                $expire = ($expire > 0) ? time() + $expire : 0;
            }
            setcookie($prefix . $name, $value, $expire, $path, $domain, $secure, $httponly);
        }
    
        /**
         * 获取IP地址
         * 返回当前用户的 IP 地址,如果 IP 地址无效,则返回 '0.0.0.0'
         */
        public function ip_address()
        {
            if ($this->ip_address !== FALSE) {
                return $this->ip_address;
            }
            $proxy_ips = config_item('proxy_ips');
            if (!empty($proxy_ips) && !is_array($proxy_ips)) {
                $proxy_ips = explode(',', str_replace(' ', '', $proxy_ips));
            }
            $this->ip_address = $this->server('REMOTE_ADDR');
            if ($proxy_ips) {
                foreach (array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP') as $header) {
                    if (($spoof = $this->server($header)) !== NULL) {
                        //一些代理通常列出了整个链的IP地址,通过它的客户端已经找到我们。
                        //例如client_ip,proxy_ip1,proxy_ip2,等。.
                        sscanf($spoof, '%[^,]', $spoof);
                        if (!$this->valid_ip($spoof)) {
                            $spoof = NULL;
                        } else {
                            break;
                        }
                    }
                }
                if ($spoof) {
                    for ($i = 0, $c = count($proxy_ips); $i < $c; $i++) {
                        //检查我们是否有一个IP地址或子网
                        if (strpos($proxy_ips[$i], '/') === FALSE) {
                            if ($proxy_ips[$i] === $this->ip_address) {
                                $this->ip_address = $spoof;
                                break;
                            }
                            continue;
                        }
                        isset($separator) OR $separator = $this->valid_ip($this->ip_address, 'ipv6') ? ':' : '.';
                        if (strpos($proxy_ips[$i], $separator) === FALSE) {
                            continue;
                        }
                        //如果需要,将REMOTE_ADDR IP转化为二进制
                        if (!isset($ip, $sprintf)) {
                            if ($separator === ':') {
                                $ip = explode(':',
                                    str_replace('::',
                                        str_repeat(':', 9 - substr_count($this->ip_address, ':')),
                                        $this->ip_address
                                    )
                                );
                                for ($j = 0; $j < 8; $j++) {
                                    $ip[$j] = intval($ip[$j], 16);
                                }
                                $sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b';
                            } else {
                                $ip = explode('.', $this->ip_address);
                                $sprintf = '%08b%08b%08b%08b';
                            }
                            $ip = vsprintf($sprintf, $ip);
                        }
                        //分裂的子网掩码长度下的网络地址
                        sscanf($proxy_ips[$i], '%[^/]/%d', $netaddr, $masklen);
                        //再次,IPv6地址以压缩的形式最有可能是
                        if ($separator === ':') {
                            $netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr));
                            for ($j = 0; $j < 8; $j++) {
                                $netaddr[$i] = intval($netaddr[$j], 16);
                            }
                        } else {
                            $netaddr = explode('.', $netaddr);
                        }
                        //转换为二进制,最后比较
                        if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0) {
                            $this->ip_address = $spoof;
                            break;
                        }
                    }
                }
            }
            if (!$this->valid_ip($this->ip_address)) {
                return $this->ip_address = '0.0.0.0';
            }
            return $this->ip_address;
        }
    
        /**
         * 验证IP地址
         * 判断一个 IP 地址是否有效,返回 TRUE/FALSE 。
         */
        public function valid_ip($ip, $which = '')
        {
            switch (strtolower($which)) {
                //验证IPv4地址
                case 'ipv4':
                    $which = FILTER_FLAG_IPV4;
                    break;
                //验证IPv6地址
                case 'ipv6':
                    $which = FILTER_FLAG_IPV6;
                    break;
                default:
                    $which = NULL;
                    break;
            }
            return (bool)filter_var($ip, FILTER_VALIDATE_IP, $which);
        }
    
        /**
         * 返回当前用户正在使用的浏览器的user agent信息
         */
        public function user_agent($xss_clean = NULL)
        {
            return $this->_fetch_from_array($_SERVER, 'HTTP_USER_AGENT', $xss_clean);
        }
    
        /**
         * 过滤全局变量,保护功能如下:
         * 当配置allow_get_array为FALSE,则Unsets $_GET
         * 当开启register_globals,Unsets all globals,以防安全。
         */
        protected function _sanitize_globals()
        {
            if ($this->_allow_get_array === FALSE) {
                $_GET = array();
            } elseif (is_array($_GET)) {
                foreach ($_GET as $key => $val) {
                    $_GET[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
                }
            }
            //过滤$_POST值
            if (is_array($_POST)) {
                foreach ($_POST as $key => $val) {
                    $_POST[$this->_clean_input_keys($key)] = $this->_clean_input_data($val);
                }
            }
            // 清除Cookie数据信息
            if (is_array($_COOKIE)) {
                unset(
                    $_COOKIE['$Version'],
                    $_COOKIE['$Path'],
                    $_COOKIE['$Domain']
                );
                foreach ($_COOKIE as $key => $val) {
                    if (($cookie_key = $this->_clean_input_keys($key)) !== FALSE) {
                        $_COOKIE[$cookie_key] = $this->_clean_input_data($val);
                    } else {
                        unset($_COOKIE[$key]);
                    }
                }
            }
            $_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']);
            log_message('debug', 'Global POST, GET and COOKIE data sanitized');
        }
    
        /**
         * 过滤输入的值
         */
        protected function _clean_input_data($str)
        {
            if (is_array($str)) {
                $new_array = array();
                foreach (array_keys($str) as $key) {
                    $new_array[$this->_clean_input_keys($key)] = $this->_clean_input_data($str[$key]);
                }
                return $new_array;
            }
            if (!is_php('5.4') && get_magic_quotes_gpc()) {
                $str = stripslashes($str);
            }
            if (UTF8_ENABLED === TRUE) {
                $str = $this->uni->clean_string($str);
            }
            $str = remove_invisible_characters($str, FALSE);
            //彼岸准换行,不同的操作系统换行不同,
            //unix系列用 /n,windows系列用 /r/n,mac用 /r,
            //PHP_EOL是一个些已经定义好的变量,代表php的换行符,这个变量会根据平台而变
            if ($this->_standardize_newlines === TRUE) {
                return preg_replace('/(?:\r\n|[\r\n])/', PHP_EOL, $str);
            }
            return $str;
        }
    
        /**
         * 过滤键值
         */
        protected function _clean_input_keys($str, $fatal = TRUE)
        {
            if (!preg_match('/^[a-z0-9:_\/|-]+$/i', $str)) {
                if ($fatal === TRUE) {
                    return FALSE;
                } else {
                    set_status_header(503);
                    echo 'Disallowed Key Characters.';
                    exit(7); // EXIT_USER_INPUT
                }
            }
            if (UTF8_ENABLED === TRUE) {
                return $this->uni->clean_string($str);
            }
            return $str;
        }
    
        /**
         * 获取http 请求头信息
         */
        public function request_headers($xss_clean = FALSE)
        {
            if (!empty($this->headers)) {
                return $this->_fetch_from_array($this->headers, NULL, $xss_clean);
            }
            if (function_exists('apache_request_headers')) {
                $this->headers = apache_request_headers();
            } else {
                isset($_SERVER['CONTENT_TYPE']) && $this->headers['Content-Type'] = $_SERVER['CONTENT_TYPE'];
    
                foreach ($_SERVER as $key => $val) {
                    if (sscanf($key, 'HTTP_%s', $header) === 1) {
                        $header = str_replace('_', ' ', strtolower($header));
                        $header = str_replace(' ', '-', ucwords($header));
                        $this->headers[$header] = $_SERVER[$key];
                    }
                }
            }
            return $this->_fetch_from_array($this->headers, NULL, $xss_clean);
        }
    
        /**
         * 获取http 头信息,如果设置xxs_clean了则过滤
         */
        public function get_request_header($index, $xss_clean = FALSE)
        {
            static $headers;
            if (!isset($headers)) {
                empty($this->headers) && $this->request_headers();
                foreach ($this->headers as $key => $value) {
                    $headers[strtolower($key)] = $value;
                }
            }
            $index = strtolower($index);
            if (!isset($headers[$index])) {
                return NULL;
            }
            return ($xss_clean === TRUE) ? $this->security->xss_clean($headers[$index]) : $headers[$index];
        }
    
        /**
         * 判断是否为ajax请求
         */
        public function is_ajax_request()
        {
            return (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
        }
    
        /**
         * 判断是否为CLI【命令行执行方式】请求
         */
        public function is_cli_request()
        {
            return is_cli();
        }
    
        /**
         * 方法
         */
        public function method($upper = FALSE)
        {
            return ($upper) ? strtoupper($this->server('REQUEST_METHOD')) : strtolower($this->server('REQUEST_METHOD'));
        }
    
        /**
         * 魔术方法__get()
         */
        public function __get($name)
        {
            if ($name === 'raw_input_stream') {
                isset($this->_raw_input_stream) OR $this->_raw_input_stream = file_get_contents('php://input');
                return $this->_raw_input_stream;
            } elseif ($name === 'ip_address') {
                return $this->ip_address;
            }
        }
    
    }

你可能感兴趣的:(CodeIgniter,CodeIgniter源码解析)