背景:
在上次项目的时候,一直不明白为什么要对数据验证,我能保证我每次请求的数据都是合法的,但是在后面的时候,原来“用户”并不是那样听话,他总是要给我们找麻烦,然后可能让我们的服务器崩掉。但是只对单个请求做数据验证,不能很好的统一管理,所有就想到了将验证信息提出来。以后每次直接调用就好。
验证类 Validator.class.php
该验证类中加入了XSS过滤,可以对参数进行过滤后验证,实现数据合理化 也可以调用setXss(false)关闭XSS过滤
1 <?php 2 3 /************************************************* 4 Validator for PHP 服务器端 【验证数据】 5 code by HoldKing 6 date:2016-06-01 7 *************************************************/ 8 9 class Validator{ 10 11 private static $_instance; //保存类实例的静态成员变量 12 private $error_msg = []; // 错误提示信息 13 private $xss = true; // 是否进行XSS的过滤 14 15 //private标记的构造方法 16 private function __construct(){ 17 // echo 'This is a Constructed method. '; 18 } 19 20 //创建__clone方法防止对象被复制克隆 21 public function __clone(){ 22 die('Validator Clone is not allow!'); 23 } 24 25 //单例方法,用于访问实例的公共的静态方法 26 public static function getInstance(){ 27 if(!(self::$_instance instanceof self)){ 28 self::$_instance = new self; 29 } 30 return self::$_instance; 31 } 32 33 //是否进行XSS的过滤 34 public function setXss($xss){ 35 $this->xss = $xss; 36 } 37 38 private function remove_xss($string) { 39 if( empty($string) ) return $string; 40 if( !$this->valiUrl($string) ) return $string; 41 42 if( strstr($string, '%25') ){ $string = urldecode($string); } 43 $string = urldecode($string); 44 45 $string = preg_replace('/<script[\w\W]+?<\/script>/si', '', $string); 46 $string = preg_replace("/'.*?'/si", '', preg_replace('/".*?"/si', '', $string) ); 47 48 $string = strip_tags ( trim($string) ); 49 $string = str_replace ( array ('"', "'", "\\", "..", "../", "./", "//", '/', '>'), '', $string ); 50 51 $string = preg_replace ( '/%0[0-8bcef]/', '', $string ); 52 $string = preg_replace ( '/%1[0-9a-f]/' , '', $string ); 53 $string = preg_replace ( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $string ); 54 55 return $string; 56 } 57 58 /* 59 * 添加数据与验证规则 60 * @param array $data 验证数据源 [&引用传递] 61 * @param array $gule 验证数据规则 62 */ 63 public function make(array &$data, array $gules){ 64 65 if( !is_array($data) ){ 66 $this->error_msg['datas'] = 'The datas is not array.'; 67 return false; 68 } 69 70 if( !is_array($gules) ){ 71 $this->error_msg['gules'] = 'The gules is not array.'; 72 return false; 73 } 74 75 foreach($gules as $name => $gule){ 76 if( !isset($data[$name]) ){ 77 $this->error_msg[$name] = "We need to $name field in the datas"; 78 continue ; 79 } 80 81 if($this->xss){ // xss 过滤 82 $data[$name] = $this->remove_xss($data[$name]); 83 } 84 85 $this->valiRun($gule, $data[$name], $name); 86 } 87 88 } 89 90 /* 91 * 查看是否有验证错误 92 * return bool 93 */ 94 public function fails(){ 95 return count($this->error_msg); 96 } 97 98 /* 99 * 查看是否有验证错误 100 * return array 101 */ 102 public function error(){ 103 return $this->error_msg; 104 } 105 106 107 /* 108 * 对数据进行验证规则验证 109 * @param int|string|... $data 验证数据 110 * @param string $gule 验证规则字符串 111 * @param string $name 验证数据键名 112 * return bool 113 */ 114 private function valiRun($gule, $data, $name){ 115 // 拆分多个验证规则 116 $gule = explode('|', $gule); 117 foreach($gule as $method){ 118 119 @list($method, $param) = explode(':', $method); 120 $valiMethod = 'vali' . ucwords($method) ; 121 if( method_exists($this, $valiMethod) ){ 122 if( $this->$valiMethod($data, $param) ){ 123 $this->doMessage($name, $method, $param); 124 break ; 125 } 126 continue ; 127 } 128 129 $this->error_msg[$name] = "Method [$method] does not exist."; 130 } 131 } 132 133 private function doMessage($attribute, $method, $param = ''){ 134 if( $param == '' ){ 135 $this->error_msg[$attribute] = "The $attribute field must be $method."; 136 }else{ 137 $param = str_replace(':attribute', $attribute, $param); 138 $this->error_msg[$attribute] = $param; 139 } 140 } 141 142 143 /********** 规则验证 直接验证 *************/ 144 private function valiRequired($str, $param=null){ 145 return empty($str); 146 } 147 private function valiString($str, $param=null){ 148 return !is_string($str); 149 } 150 private function valiInt($str, $param=null){ 151 return $str != (int)$str; 152 } 153 private function valiNumeric($str, $param=null){ 154 return !is_numeric($str); 155 } 156 private function valiArray($str, $param=null){ 157 return !is_array($str); 158 } 159 private function valiEmail($str, $param=null){ 160 return !preg_match("/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/", $str); 161 } 162 private function valiUrl($str, $param=null){ 163 return !preg_match("/^https?:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"])*$/", $str); 164 } 165 private function valiQq($str, $param=null){ 166 return !preg_match("/^[1-9]\d{4,8}$/", $str); 167 } 168 private function valiZip($str, $param=null){ 169 return !preg_match("/^[1-9]\d{5}$/", $str); 170 } 171 private function valiIdcard($str, $param=null){ 172 return !preg_match("/^\d{17}[X0-9]$/", $str); 173 } 174 private function valiChinese($str, $param=null){ 175 return !ereg("^[".chr(0xa1)."-".chr(0xff)."]+$", $str); 176 } 177 private function valiMobile($str, $param=null){ 178 return !preg_match("/^((\(\d{3}\))|(\d{3}\-))?13\d{9}$/", $str); 179 } 180 private function valiPhone($str, $param=null){ 181 return !preg_match("/^((\(\d{3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}$/", $str); 182 } 183 private function valiDate($str, $param=null){ 184 return !preg_match("/^\d{2}(\d{2})?-\d{1,2}-\d{1,2}(\s\d{1,2}:\d{1,2}(:\d{1,2})?)?$/", $str); 185 } 186 187 /********** 规则验证 组合验证 *************/ 188 private function valiMin($value, &$param ){ 189 if( !is_numeric($param) ){ 190 $param = "The :attribute need to number"; return true; 191 } 192 if($value < $param){ 193 $param = "The :attribute[$value] must be more than $param"; return true; 194 } 195 } 196 private function valiMax($value, &$param){ 197 if( !is_numeric($param) ){ 198 $param = "The :attribute need to number"; return true; 199 } 200 if($value > $param){ 201 $param = "The :attribute[$value] must be less than $param"; return true; 202 } 203 } 204 private function valiSize($value, &$param){ 205 if( !is_numeric($param) ){ 206 $param = "The :attribute need to number"; return true; 207 } 208 if( strlen($value) != $param ){ 209 $param = "The :attribute[$value] length must be $param"; return true; 210 } 211 } 212 private function valiIn($value, &$param){ 213 $params = explode(',', $param); 214 if( !is_array($params) || empty($param) || count($params) < 1 ){ 215 $param = "Validation rule in requires at least 1 parameters."; return true; 216 } 217 if( !in_array($value, $params) ){ 218 $param = "The :attribute[$value] must be one of the following: $param"; return true; 219 } 220 } 221 222 private function valiBetween($value, &$param){ 223 @list($min, $max) = explode(',', $param); 224 if( empty($min) || empty($max) ){ 225 $param = "Validation rule between requires at least 2 parameters."; return true; 226 } 227 if( $value < $min || $value > $max ){ 228 $param = "The :attribute[$value] must be between $min - $max."; return true; 229 } 230 } 231 private function valiAfter($value, &$param){ 232 if( !$param || !preg_match("/^[\d-]{10}(\s[\d:]{8})?$/", $param) ){ 233 $param = "Validation rule after requires date string."; return true; 234 } 235 if( strtotime($value) <= strtotime($param) ){ 236 $param = "The :attribute[$value] must be after $param"; return true; 237 } 238 } 239 private function valiBefore($value, &$param){ 240 if( !$param || !preg_match("/^[\d-]{10}(\s[\d:]{8})?$/", $param) ){ 241 $param = "Validation rule after requires date string."; return true; 242 } 243 if( strtotime($value) >= strtotime($param) ){ 244 $param = "The :attribute[$value] must be before $param"; return true; 245 } 246 } 247 248 /******************* 规则验证 end ******************************************/ 249 250 } 251 252 ?>
使用示例
// 单例模式 获得单例实例 $Validator = Validator::getInstance(); // 验证数据源 $input['name'] = 'kingsoft<meta http-equiv="refresh" content="5;">'; $input['age'] = 10; $input['type'] = "1'%22+onmouseover=alert()+d='%22"; $input['date'] = '2016-05-31 12:26:12"><script>alert(document.cookie)</script><!-'; $input['email'] = 'pengjinping@163.com'; print_r($input); // 开始验证 $Validator->make($input, array( 'name' => 'required|string|size:4', 'age' => 'min:5|max:99', 'email' => 'email', 'type' => 'in:0,1', 'date' => 'date' )); // 判断是否验证通过 if( $Validator->fails() ){ print_r( $Validator->error() ); } // XSS标记被过滤后数据 print_r($input);
运行结果
1 Array 2 ( 3 [name] => kingsoft<meta http-equiv="refresh" content="5;"> 4 [age] => 10 5 [type] => 1'%22+onmouseover=alert()+d='%22 6 [date] => 2016-05-31 12:26:12"><script>alert(document.cookie)</script><!- 7 [email] => pengjinping@163.com 8 ) 9 10 Array 11 ( 12 [name] => The name[kingsoft] length must be 4 13 ) 14 15 Array 16 ( 17 [name] => kingsoft 18 [age] => 10 19 [type] => 1 20 [date] => 2016-05-31 12:26:12 21 [email] => pengjinping@163.com 22 )
我们可以看出:
1. name字段没有验证通过,是因为他的长度为8并不是4, 而且支持多种规则验证【组合验证】,这样我们就不用针对不同的情况使用if...else 来判断验证了。
2. 验证结束后的数据与源数据存在差异,我们分析出,这些差异在表单中是不能出现的非法【Xss】攻击数据,所以进行XSS攻击过滤。如果非要使用这些数据内容验证,可以关闭xss过滤,使用原样数据验证。
3. 不支持单个字段关闭xss过滤; 不支持数组字段过滤。