PHP表单数据验证

背景

  在上次项目的时候,一直不明白为什么要对数据验证,我能保证我每次请求的数据都是合法的,但是在后面的时候,原来“用户”并不是那样听话,他总是要给我们找麻烦,然后可能让我们的服务器崩掉。但是只对单个请求做数据验证,不能很好的统一管理,所有就想到了将验证信息提出来。以后每次直接调用就好。

验证类 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过滤; 不支持数组字段过滤。