[PHP] 06 - Security: Error, Exception and Filter
前言
Ref: PHP 发送电子邮件
Ref: PHP Secure E-mails
PHP发邮件部分在此系列中略。
这里展开”安全“相关的部分。
有啥区别?
Ref: PHP异常与错误处理机制
PHP错误:是属于php程序自身的问题,一般是由非法的语法,环境问题导致的,使得编译器无法通过检查,甚至无法运行的情况。平时遇到的warming、notice都是错误,只是级别不同而已。
PHP异常:一般是业务逻辑上出现的不合预期、与正常流程不同的状况,不是语法错误。
PHP异常处理机制借鉴了java c++等,但是PHP的异常处理机制是不健全的。异常处理机制目的是将 程序正常执行的代码 与 出现异常如何处理的代码分离。
PHP是无法自动捕获异常的(绝大多数),只有主动抛出异常并捕捉。也就是说,对于异常,是可预见的。
错误处理
Ref: http://www.runoob.com/php/php-error.html
一、die() 函数
<?php if(!file_exists("welcome.txt")) { die("文件不存在"); // 返回可控的错误信息 } else { $file=fopen("welcome.txt","r"); } ?>
二、专用错误处理函数
创建了一个专用函数,可以在 PHP 中发生错误时调用该函数。
可以接受最多五个参数(可选的:file, line-number 和 error context):
参数 | 描述 |
---|---|
error_level | 必需。为用户定义的错误规定错误报告级别。必须是一个数字。参见下面的表格:错误报告级别。 |
error_message | 必需。为用户定义的错误规定错误消息。 |
error_file | 可选。规定错误发生的文件名。 |
error_line | 可选。规定错误发生的行号。 |
error_context | 可选。规定一个数组,包含了当错误发生时在用的每个变量以及它们的值。 |
- 综合例子:通过 E-Mail 发送错误消息
在下面的例子中,如果特定的错误发生,我们将发送带有错误消息的电子邮件,并结束脚本:
<?php // 错误处理函数 function customError($errno, $errstr) // (3) this is the error process. { echo "<b>Error:</b> [$errno] $errstr<br>"; echo "已通知网站管理员"; error_log("Error: [$errno] $errstr",1, // (4) 可以通过使用 error_log() 函数,您可以向指定的文件或远程目的地发送错误记录 "someone@example.com","From: webmaster@example.com"); } // 设置错误处理函数 set_error_handler("customError",E_USER_WARNING); // (2) this is the type, thus customError will be run to handle the error process. // 触发错误 $test=2; if ($test>1) // Jeff: 其实算异常,因为是逻辑错误,非系统错误 { trigger_error("变量值必须小于等于 1",E_USER_WARNING); // (1) trigger this error and it is a E_USER_WARNING type. } ?>
异常处理
Ref: http://www.runoob.com/php/php-exception.html
一、抛出异常
- 生成一个异常
不符合逻辑就抛出异常,走特殊通道。
<?php // 创建一个有异常处理的函数 function checkNum($number) { if($number>1) { throw new Exception("Value must be 1 or below"); } return true; } // 触发异常 checkNum(2); ?>
异常信息显示:
Fatal error: Uncaught exception 'Exception' with message 'Value must be 1 or below' in /www/runoob/test/test.php:7 Stack trace: #0 /www/runoob/test/test.php(13): checkNum(2) #1 {main} thrown in /www/runoob/test/test.php on line 7
- Try、throw 和 catch
<?php // 创建一个有异常处理的函数 function checkNum($number) { if($number>1) { throw new Exception("变量值必须小于等于 1"); } return true; } // 在 try 块 触发异常 try { checkNum(2); // 如果抛出异常,以下文本不会输出 echo '如果输出该内容,说明 $number 变量'; }
catch(Exception $e) { echo 'Message: ' .$e->getMessage(); } ?>
二、自定义的 Exception 类
- 创建 customException 类
<?php class customException extends Exception { public function errorMessage() { // 错误信息 $errorMsg = '错误行号 '.$this->getLine().' in '.$this->getFile() .': <b>'.$this->getMessage().'</b> 不是一个合法的 E-Mail 地址'; return $errorMsg; } }
-----------------------------------------------------------------------
$email = "someone@example...com"; try { // 检测邮箱 if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE) { // 如果是个不合法的邮箱地址,抛出异常 throw new customException($email); } } catch (customException $e) { // display custom message echo $e->errorMessage(); } ?>
- 多个异常
可以为一段脚本使用多个异常,来检测多种情况。
<?php class customException extends Exception { public function errorMessage() { // 错误信息 $errorMsg = '错误行号 '.$this->getLine().' in '.$this->getFile() .': <b>'.$this->getMessage().'</b> 不是一个合法的 E-Mail 地址'; return $errorMsg; } } $email = "someone@example.com"; try { // 检测邮箱 if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE) { // 如果是个不合法的邮箱地址,抛出异常 throw new customException($email); } // 检测 "example" 是否在邮箱地址中 if(strpos($email, "example") !== FALSE) { throw new Exception("$email 是 example 邮箱"); } } catch (customException $e) { echo $e->errorMessage(); } catch(Exception $e) { echo $e->getMessage(); } ?>
- 重新抛出异常
在一个 "catch" 代码块中再次抛出异常。
<?php class customException extends Exception { public function errorMessage() { // 错误信息 $errorMsg = $this->getMessage().' 不是一个合法的 E-Mail 地址。'; return $errorMsg; } }
------------------------------------------------------------------------------- $email = "someone@example.com"; try { try { // 检测 "example" 是否在邮箱地址中 if(strpos($email, "example") !== FALSE) { // 如果是个不合法的邮箱地址,抛出异常 throw new Exception($email); } } catch(Exception $e) { // 重新抛出异常 throw new customException($email); // 普通异常的处理其实跳转到了自定义异常 } } catch (customException $e) { // 显示自定义信息 echo $e->errorMessage(); } ?>
三、顶层异常处理器
set_exception_handler() 函数可设置处理所有未捕获异常的用户定义函数。
也就是”忘了写try...catch“,哈哈。
<?php function myException($exception) { echo "<b>Exception:</b> " , $exception->getMessage(); } set_exception_handler('myException'); throw new Exception('Uncaught Exception occurred'); ?>
PHP 过滤器
Ref: http://www.runoob.com/php/php-filter.html
Ref: http://www.runoob.com/php/php-filter-advanced.html
类似于node里的assert:NodeJS 断言的使用
应该始终对外部数据进行过滤!
输入过滤是最重要的应用程序安全课题之一。
什么是外部数据?
- 来自表单的输入数据
- Cookies
- Web services data
- 服务器变量
- 数据库查询结果
一、Filter 函数
filter_has_var — 检测接收指定类型的变量是否存在,例如通过post传递的username变量是否存在
filter_id — 返回与某个特定名称的过滤器相关联的id
filter_input_array — 获取一系列外部变量,并且可以通过过滤器处理它们
filter_input — 通过名称获取特定的外部变量,并且可以通过过滤器处理它
filter_list — 返回所支持的过滤器列表
filter_var_array — 获取多个变量并且过滤它们
filter_var — 使用特定的过滤器过滤一个变量
二、常见验证示范
Ref: 使用 PHP 过滤器(Filter)进行严格表单验证
- [1] 类型 $type
filter_input(int $type, string $variable_name [, int $filter = FILTER_DEFAULT] [, mixed $options])
参数 $type 可以是 INPUT_GET,INPUT_POST,INPUT_COOKIE,INPUT_SERVER 或 INPUT_ENV
参数 $filter 的类型可以参见 php 手册:http://php.net/manual/zh/filter.filters.php
- [2] 何时用
使用 Filter 可以对表单必填域验证、数字验证、email 验证、下拉菜单验证、单选按钮验证、复选框验证等。
使用 Filter 可以节省很多正则,例如验证 email、INT、bool ,并且 filter_has_var 函数比 isset 函数要更快。
(1) 验证必填域
//检查$_POST['username']长度之前首先确保它存在
if(! (filter_has_var(INPUT_POST, 'username') && (strlen(filter_input(INPUT_POST, 'username')) > 0) )) {
echo '请输入用户名'; exit; }
说明:filter_has_var 函数在接收到了变量时对变量的值进行验证,在该例中,
-- 如果接受到了 $_POST['username'] ,即对 $_POST['username'] 的值进行验证,
-- 如果没有接收到变量 $_POST['username'],例如该字段在表单中是单个的复选框,不勾选的话,处理的页面是接收不到该字段的信息的。
(2) 验证长度
//FILTER_SANITIZE_STRING 过滤器 会去除HTML标记、删除二进制非ASCII字符、并对与字符编码(&)
if(filter_has_var(INPUT_POST, 'country') && strlen(filter_input(INPUT_POST, 'country', FILTER_SANITIZE_STRING)) <=2) {
echo 'country长度不小于2个字符'; exit; }
说明:参数 FILTER_SANITIZE_STRING 用于去除 HTML 标记、删除二进制非 ASCII 字符、并且对与字符编码(&)
(3) 验证邮箱
//验证邮箱
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if($email === false) { echo '请输入正确的邮箱'; exit; }
说明:使用参数 FILTER_VALIDATE_EMAIL 验证 email
(4) 验证整数
//验证整数,如果填写了年龄则进行验证
if(strlen(filter_input(INPUT_POST, 'age')) > 0) { $age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT);
if($age === false) { echo '请输入正确的年龄'; exit; } }
说明:使用参数FILTER_VALIDATE_INT 验证整数
(5) 验证小数
//验证小数,如果填写了salary则进行验证
if(strlen(filter_input(INPUT_POST, 'salary')) > 0) { $salary = filter_input(INPUT_POST, 'salary', FILTER_VALIDATE_FLOAT);
if($salary === false) { echo '请输入正确的薪资'; exit; } }
说明:使用参数 FILTER_VALIDATE_FLOAT 验证浮点数
(6) 验证数组,复选框验证组
//确保$_POST['sports']存在且是一个数组
if(! (filter_has_var(INPUT_POST, 'sports') && filter_input(INPUT_POST, 'sports', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY))) {
echo '请选择一项运动'; exit; } //验证复选框组
//array_intersect 计算数组的交集 if(array_intersect($_POST['sports'], array_values($sports)) != $_POST['sports']) {
echo '请选择正确的运动'; exit; }
说明:使用参数 FILTER_DEFAULT 进行占位,使用参数 FILTER_REQUIRE_ARRAY 验证是否是数组
使用 array_intersect 函数计算数组的交集
(7) 验证单个复选框
//验证单个复选框
if(filter_has_var(INPUT_POST, 'single')) {
if($_POST['single'] == $value) { $single = true; } else { $single = false; echo '错误的提交'; exit; } }
(8) 验证下拉菜单
//验证下拉菜单
if(! (filter_has_var(INPUT_POST, 'food') && array_key_exists($_POST['food'], $choices))) {
echo '请选择喜欢的食物'; exit; }
(9) 验证单选按钮
//验证性别
if(! (filter_has_var(INPUT_POST, 'sex') && in_array($_POST['sex'], $sex))) {
echo '请选择性别'; exit; }
(10) 验证时间
//验证时间
if(filter_has_var(INPUT_POST, 'time')) { foreach($_POST['time'] as $time) {
@list($year, $month, $day) = explode('-', $time); if(! @checkdate($month, $day, $year)) { echo '时间错误'; exit; } //时间段验证(略) } }
说明:使用 checkdate 函数验证是否是正确的时间
三、Validating 和 Sanitizing
Validating 过滤器
Sanitizing 过滤器
- 选项 和 标志
选项和标志用于向指定的过滤器添加额外的过滤选项。
不同的过滤器有不同的选项和标志。
在下面的实例中,我们用 filter_var() 和 "min_range" 以及 "max_range" 选项验证了一个整数:
<?php $var=300; $int_options = array( "options"=>array # 选项必须放入一个名为 "options" 的相关数组中 ( "min_range"=>0, "max_range"=>256 ) );
----------------------------------------------------------------------------- if(!filter_var($var, FILTER_VALIDATE_INT, $int_options)) { echo("不是一个合法的整数"); } else { echo("是个合法的整数"); } ?>
- 验证输入
<?php if(!filter_has_var(INPUT_GET, "email")) # 检测是否存在 "GET" 类型的 "email" 输入变量 { echo("没有 email 参数"); } else {
# 如果存在输入变量,检测它是否是有效的 e-mail 地址 if (!filter_input(INPUT_GET, "email", FILTER_VALIDATE_EMAIL)) { echo "不是一个合法的 E-Mail"; } else { echo "是一个合法的 E-Mail"; } } ?>
- 净化输入
从表单传来的 URL,处理一下。
<?php if(!filter_has_var(INPUT_GET, "url")) { echo("没有 url 参数"); } else { $url = filter_input(INPUT_GET, "url", FILTER_SANITIZE_URL); echo $url; } ?>
效果:http://www.ruåånoøøob.com/ ----> [净化后] ----> http://www.runoob.com/
- 过滤多个输入
毕竟,表单通常由多个输入字段组成。
为了避免对 filter_var 或 filter_input 函数重复调用,我们可以使用 filter_var_array 或 the filter_input_array 函数。
使用 filter_input_array() 函数来过滤三个 GET 变量。接收到的 GET 变量是一个名字、一个年龄以及一个 e-mail 地址:
<?php $filters = array ( "name" => array ( "filter"=>FILTER_SANITIZE_STRING ), "age" => array ( "filter"=>FILTER_VALIDATE_INT, "options"=>array ( "min_range"=>1, "max_range"=>120 ) ), "email"=> FILTER_VALIDATE_EMAIL ); $result = filter_input_array(INPUT_GET, $filters); if (!$result["age"]) { echo("年龄必须在 1 到 120 之间。<br>"); } elseif(!$result["email"]) { echo("E-Mail 不合法<br>"); } else { echo("输入正确"); } ?>
- 使用 Filter Callback - 自定义过滤器
这样,我们就拥有了数据过滤的完全控制权。
您可以创建自己的自定义函数,也可以使用已存在的 PHP 函数。
将您准备用到的过滤器的函数,按指定选项的规定方法进行规定。在关联数组中,带有名称 "options"。
在下面的实例中,我们使用了一个自定义的函数把所有 "_" 转换为 ".":
<?php function convertSpace($string) { return str_replace("_", ".", $string); } $string = "www_runoob_com!"; echo filter_var($string, FILTER_CALLBACK, array("options"=>"convertSpace")); ?>
Jeff: 这里关注下option的用法。
四、PHP 高级过滤器
- 检测一个数字是否在一个范围内
<?php $int = 122; $min = 1; $max = 200; if (filter_var($int, FILTER_VALIDATE_INT, array("options" => array("min_range"=>$min, "max_range"=>$max))) === false) { echo("变量值不在合法范围内"); } else { echo("变量值在合法范围内"); } ?>
- 检测 IPv6 地址
<?php $ip = "2001:0db8:85a3:08d3:1319:8a2e:0370:7334"; if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
echo("$ip 是一个 IPv6 地址"); } else { echo("$ip 不是一个 IPv6 地址"); } ?>
- 检测 URL - 必须包含 QUERY_STRING(查询字符串)
<?php $url = "http://www.runoob.com"; if (!filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED) === false) {
echo("$url 是一个合法的 URL"); } else { echo("$url 不是一个合法的 URL"); } ?>
- 移除 ASCII 值大于 127 的字符
<?php $str = "<h1>Hello WorldÆØÅ!</h1>"; $newstr = filter_var($str, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH);
echo $newstr; ?>
- PHP 过滤器参考手册
你也可以通过访问本站的 PHP 过滤器参考手册 来查看过滤器的具体应用。