输入类有两个用途:一:为了安全性,对输入数据进行预处理;二:提供了一些辅助方法来获取输入数据并处理。并且该类由系统自动加载,你无需手工加载。
① 对输入进行过滤
安全性过滤:当访问控制器时,安全过滤方法会自动被调用,它做了以下几件事情:首先,如果 $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 配置文件中设置如下参数:
注:参数 'global_xss_filtering' 已经废弃,保留它只是为了实现向前兼容。 XSS 过滤应该在*输出*的时候进行,而不是*输入*的时候!$config['global_xss_filtering'] = TRUE;
② 访问表单数据
使用 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() 的检查:
注:你可以使用 method() 方法来获取你读取的是什么数据,PUT、DELETE 还是 PATCH 。$this->input->input_stream('key', TRUE); // XSS Clean $this->input->input_stream('key', FALSE); // No XSS filter
就说这么多吧,我翻看了一下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;
}
}
}