PHP上传问题总结(文件大小检测,大文件上传)

      由于涉及到本地和服务器两方面的安全问题,所以基于input type="file"形式的页面文件上传一直处于一个很尴尬的位置。一方面,用户不希望隐私泄露,所以浏览器无法对用户在上传时选择的文件做有效的判 断。另一方面,为了服务器端的安全,减轻传输负担,系统又希望能在用户开始上传之前就将非法的文件拒之门外。

       一来一去,基于原始input方式的上传,成为网络存储网站避之唯恐不及的遗留性问题,也造就了现在千奇百怪的插件、上传客户端。

       input方式的上传就如此之差么?当然不是。上传文件不大的时候,它还是非常简单可靠的,在PHP中,我们只需要一个复合型表单


[xml]  
1. <form enctype="multipart/form-data" action="__URL__" method="POST">

      一个输入框


[xml]
1. <input name="userfile" type="file" />

       和服务器端的一行代码


[php]  
1. move_uploaded_file($_FILES['userfile']['tmp_name'], '/var/www/uploads/'. basename($_FILES['userfile']['name']));

       就可以实现整个上传过程。

       但随文件增大,表单上传的不足就会暴露出来。尤其是我们想取得最基本的文件大小来阻止过大文件上传这一简单的想法,也变得如此困难。以下一一道来:

通过MAX_FILE_SIZE

        我们经常会在手册里读到:

MAX_FILE_SIZE 隐藏字段(单位为字节)必须放在文件输入字段之前,其值为接收文件的最大尺寸。这是对浏览器的一个建议,PHP 也会检查此项。在浏览器端可以简单绕过此设置,因此不要指望用此特性来阻挡大文件。实际上,PHP 设置中的上传文件最大值是不会失效的。但是最好还是在表单中加上此项目,因为它可以避免用户在花时间等待上传大文件之后才发现文件过大上传失败的麻烦。

       显然PHP的开发者们也考虑到了大文件上传的问题,但就像手册所说,MAX_FILE_SIZE只是对浏览器的一个建议,事实上目前为止所有主流的浏览器并没有采纳这个建议,所以采用MAX_FILE_SIZE约束文件大小形同摆设,不可行。

通过服务器端

       MAX_FILE_SIZE既然无效,那么用户可以将文件上传到服务器,服务器端通过$_FILES['userfile']['size']判断用户上 传的文件大小,然后决定是否接受上传并返回信息。暂且排除服务器的负荷以及可能存在的恶意破坏行为,这种解决方案听起来无非是浪费一部分带宽,也能对用户 上传文件作出约束。

       但这也是不可行的,PHP的文件上传受到php.ini以下这些设置的影响:

  • post_max_size
  • upload_max_filesize
  • max_execution_time
  • memory_limit

       虽然设置方法在手册 中都有比较详细的说明,之所以仍然说此方法不可行,是因为php执行脚本在超过memory_limit时,该次的POST数据会全部丢失并且不会报错!

       试想用户填写了一个超长的表单,并伴随一个超过memory_limit的文件一起上传,经过了漫长的等待时间之后发现等来的又是一张干干净净的空白表 单,那是何等印象深刻的用户体验啊。更何况数十M的服务器流量仅仅用来检测文件大小,是现在的网络环境不允许的。

通过Javascript

        Javascript是基于浏览器的,虽然JS能完成很多看似不可能的任务,但浏览器做不到的事情JS同样无法做到。先天不足注定了这项工作仅仅靠Javascript是无法胜任的。不过一些IE Only的方法 也还是存在的,仅作参考 。

通过Flash

         Flash的FileReference类提供了一套比较全面的文件处理方法,现在大多数大文件上传也都采用了基于Flash的方案。如果利用Flash与Js交互,能否实现客户端对文件大小的检测呢?答案是可行的。

       首先在flash文件中实例化FileReference类。


[jscript]  
1. var fr = new FileReference();


       基于这个类就可以用Flash提供的file browse和SelectFile事件替代浏览器的事件。我们需要:

       1.绑定SelectFile


[jscript]  
1. fr.addEventListener(Event.SELECT, onSelectFile);


       2.创建一个供Js访问的对象,用来放置flash得到的文件信息


[jscript]  
1. var s = {<br>  
2.    size:0,<br>  
3. '',<br>  
4. ''<br>  
5.     }

       3.创建file browse方法


[jscript]  
1. function browseFile():void {<br>  
2.  fr.browse();<br>  
3.     }


       4.当SelectFile事件触发的时候,传递文件信息


[jscript]  
1. function onSelectFile(e:Event):void {<br>  
2.   s.size = fr.size;<br>  
3.   s.name = fr.name;<br>  
4.   s.type = fr.type;<br>  
5.     }

       5.将browseFile方法公开可供Js调用


[jscript]  
1. ExternalInterface.addCallback("browseFile", browseFile);


       6.将得到的文件信息传递给Js


[jscript] 
1. ExternalInterface.call("onSelectFile",s);

       现在我们已经可以通过Js获得由flash传递来的文件大小信息了,具体的实现可以参看Demo 。

结论

       问题至此似乎已经得到解决了,我们已经成功的校验了文件大小不是么。但本文的最终结论是,基于Flash的文件大小校验,仍然不可行。

       文件大小校验的唯一目的,是为了上传。在上面的Demo中可以看到校验成功的文件名会显示在一个输入框里。熟悉上传的同学不觉得少了什么吗?没错,通过 flash只能得到文件名,而无法得到文件的完整路径,而文件路径却是input方式上传的必要条件。所以虽然可以成功的通过Flash与Js交互校验文 件大小,但我们能做到的也仅仅只是校验而已,之后想要上传,唯有继续通过flash方式进行。

       Flash开发出于安全考虑屏蔽了文件的完整路径这无可厚非,不过文件上传,尤其是PHP环境下的文件校验上传方案仍然没有得到最好的解决。

       当然弥补的方法有很多:

      但终究我希望有一天能看到仅基于HTML就能实现的严整健壮的上传方案,但愿这一天不会太远。

      最后是本次的代码下载 。

 

 

php文件上传大小设置详解

用php上传文件,问题最多的就是上传大体积文件时出现错误。 
这就涉及到php的配置文件:php.ini 

在此配置文件中,有这么几个值是跟文件上传有密切关系的: 
file_uploads = on //是否允许系统支持文件上传 
;upload_tmp_dir //临时文件的存储路径,linux下为系统默认路径,win32下需要指定 
upload_max_filesize = 2m //允许文件上传最大体积 

post_max_size = 2m //通过post方法给php时,php所能接受的最大数据容量 

如果你上传的文件体积在8m一下(通常情况),那修改以上设置就可以满足你的要求了。。 

但要>8m,那除了上面几个值,还要特别关注另外两个值了: 
max_execution_time = 30 //每个script所执行的最大时间(php上传就时,体积大了,就是个时间问题) 
memory_limit = 8m //每个script所能消耗的最大memory 

试着把这两个值改大些。一般就可以解决大多数问题了。 

就此推断,上传文件的体积是可以无穷大的。但还要考虑你的网络情况,等等。 

在php.net上,有人说按照这个方法改了后,大于100m的文件还是会出错。 
不知道是不是php本身的问题了:

 

 

php实现文件上传的一些经验

• //图片上传处理
 $upload_file=$_FILES['upload_file']['tmp_name'];
 echo $upload_file;
 $upload_file_name=$_FILES['upload_file']['name'];
 $type=strstr($upload_file_name, '.');
 if($upload_file=="")
 {
 $newname="0.jpg"; 
 }
 else
 {
 $newname=$newid.$type;
 }
 //$newname=$newid.$type;
$intonew2="update mms_news_info set col_image='$newname' where col_id='$newid'";
 mysql_query($intonew2,$db);
 
if($upload_file){
 $file_size_max = 1000*1000;// 1M限制文件上传最大容量(bytes)
 $store_dir = "neirongimg/";// 上传文件的储存位置
 $accept_overwrite = 1;//是否允许覆盖相同文件
 // 检查文件大小
 if ($upload_file_size > $file_size_max) {
 echo "对不起,你的文件容量大于规定";
 exit;
 }
// 检查读写文件
 if (file_exists($store_dir . $upload_file_name) && $accept_overwrite) {
 Echo   "存在相同文件名的文件";
 exit;
 }
//复制文件到指定目录
 if (!move_uploaded_file($upload_file,$store_dir.$newname)) {
 echo "复制文件失败";
 exit;
 }
}
Echo   "<p>你上传了文件:";
 echo  $_FILES['upload_file']['name'];
 echo "<br>";
 //客户端机器文件的原名称。
Echo   "文件的 MIME 类型为:";
 echo $_FILES['upload_file']['type'];
 //文件的 MIME 类型,需要浏览器提供该信息的支持,例如“image/gif”。 
 echo "<br>";
Echo   "上传文件大小:";
 echo $_FILES['upload_file']['size'];
 //已上传文件的大小,单位为字节。 
 echo "<br>";
Echo   "文件上传后被临时储存为:";
 echo $_FILES['upload_file']['tmp_name'];
 //文件被上传后在服务端储存的临时文件名。 
 echo "<br>";

 $Erroe=$_FILES['upload_file']['error'];
 switch($Erroe){
 case 0:
 Echo   "上传成功"; break;
 case 1:
 Echo   "上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值."; break;
 case 2:
 Echo   "上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。";    break;
 case 3:
 Echo   "文件只有部分被上传";break;
 case 4:
 Echo   "没有文件被上传";break;
 }
  //图片上传处理
?>
  • +++++++++++++++++++++++++++++++++ 关于错误信息的解释 从 PHP 4.2.0 开始,PHP 将随文件信息数组一起返回一个对应的错误代码。该代码可以在文件上传时生成的文件数组中的 ['error'] 字段中被找到,也就是 $_FILES['userfile']['error']。 UPLOAD_ERR_OK 值:0; 没有错误发生,文件上传成功。UPLOAD_ERR_INI_SIZE 值:1; 上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值。UPLOAD_ERR_FORM_SIZE 值:2; 上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。UPLOAD_ERR_PARTIAL 值:3; 文件只有部分被上传。UPLOAD_ERR_NO_FILE 值:4; 没有文件被上传。 注: 这些在 PHP 4.3.0 之后变成了 PHP 常量。 PHP 能够接受任何来自符合 RFC-1867 标准的浏览器(包括 Netscape Navigator 3 及更高版本,Microsoft Internet Explorer 3 加微软补丁,或者更高版本)上传的文件。PHP 的这种特性使得我们既可以上传文本文件,也可以上传二进制文件。利用 PHP 的认证和文件操作函数,您就可以控制谁有上传的权限,以及在文件上传后进行哪些处理。相关的设置: 请参阅 php.ini 的 file_uploads、 upload_max_filesize、upload_tmp_dir 以及 post_max_size 设置选项。请注意 PHP 也支持 PUT 方法的文件上传,Netscape Composer 和 W3C 的 Amaya 客户端使用这种方法。请参阅 PUT 方法支持以获取更多信息。您可以如下建立一个特殊的表单来支持文件上传:例子 18-1. 文件上传表单
<form enctype="multipart/form-data" action="_URL_" method="POST">
 <input type="hidden" name="MAX_FILE_SIZE" value="30000">
 Send this file: <input name="userfile" type="file">
 <input type="submit" value="Send File">
 </form>
  • 以上范例中的“_URL_”应该替换成指向一个 PHP 文件的真实 URL。MAX_FILE_SIZE 隐藏域(单位为字节)必须先于文件输入域,其值为接收文件的最大尺寸。同时,要保证您的文件上传表单中要有 enctype="multipart/form-data",否则文件上传将不能工作。 警告  MAX_FILE_SIZE 的值只是对浏览器的一个建议,实际上它可以被简单的绕过。因此不要把对浏览器的限制寄希望于该值。实际上,PHP 设置中的上传文件最大值,是不会失效的。但是最好还是在表单中加上 MAX_FILE_SIZE,因为它可以避免用户在花时间等待上传大文件之后才发现该文件太大了的麻烦。 为上传文件定义的变量会根据 PHP 的版本及设置的不同而不同。自动全局变量 $_FILES 从 PHP 4.1.0 版本开始被支持。在这之前,从 4.0.0 版本开始,PHP 支持 $HTTP_POST_FILES 数组。这些数组将包含所有关于您上传的文件的信息,其中,我们推荐您使用 $_FILES。如果 PHP 的设置选项 register_globals 为 on,则相关的变量名将也会存在。从 PHP 4.2.0 版本开始,register_globals 的默认值被设为 off。以上范例中 $_FILES 数组的内容如下所示。我们假设文件上传字段的名称如上例所示,为 userfile。名称可随意命名。 $_FILES['userfile']['name'] 客户端机器文件的原名称。$_FILES['userfile']['type'] 文件的 MIME 类型,需要浏览器提供该信息的支持,例如“image/gif”。$_FILES['userfile']['size'] 已上传文件的大小,单位为字节。$_FILES['userfile']['tmp_name'] 文件被上传后在服务端储存的临时文件名。$_FILES['userfile']['error'] 和该文件上传相关的错误代码。['error'] 是在 PHP 4.2.0 版本中增加的。 注: 在 PHP 4.1.0 版本以前该数组的名称为 $HTTP_POST_FILES,它并不像 $_FILES 一样是自动全局变量。PHP 3 不支持 $HTTP_POST_FILES 数组。当 php.ini 中的 register_globals 被设置为 on 时,您可以使用更多的变量。例如,$userfile_name 等价于 $_FILES['userfile']['name'],$userfile_type 等价于 $_FILES['userfile']['type'] 等。请记住从 PHP 4.2.0 开始,register_globals 的默认值为 off,因此我们建议您不要依赖于改设置项而使用刚刚提到的那些附加变量。文件被上传后,默认地会被储存到服务端的默认临时目 录中,除非您将 php.ini 中的 upload_tmp_dir 设置为了其它的路径。服务端的默认临时目录可以通过更改 PHP 运行环境的环境变量 TMPDIR 来重新设置,但是在 PHP 脚本内部通过运行 putenv() 函数来设置是不起作用的。该环境变量也可以用来确认其它的操作也是在上传的文件上进行的。 例子 18-2. 使文件上传生效请查阅函数 is_uploaded_file() 和 move_uploaded_file() 以获取进一步的信息。以下范例处理由表单提供的文件上传。
<?php
 // 在 4.1.0 以前的 PHP 中,需要用 $HTTP_POST_FILES 代替 $_FILES。
 // 在 4.0.3 以前的 PHP 中,需要用 copy() 和 is_uploaded_file() 来代替 move_uploaded_file()。
$uploaddir = '/var/www/uploads/';
 $uploadfile = $uploaddir. $_FILES['userfile']['name'];
 print "<pre>";
 if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploaddir . $_FILES['userfile']['name'])) {
 print "File is valid, and was successfully uploaded.  Here's some more debugging info:/n";
 print_r($_FILES);
 } else {
 print "Possible file upload attack!  Here's some debugging info:/n";
 print_r($_FILES);
 }
 print "</pre>";
 ?>



  • 接受上传文件的 PHP 脚本必须在文件上传后进行判断,来决定接下来要对该文件进行那些操作。