2011-06-21 66 views
3

我已经写了一个PHP页面用于从服务器下载文件。该文件的名称为URL中的GET变量传递,然后将下面的代码提供了下载的文件:使用PHP下载2GB文件

$filepath = "/path/to/files"; 
$filename = $_GET['id']; 

if(! file_exists($filepath . "/" . $filename)) 
{ 
    header("HTTP/1.1 404 Not Found"); 
    @session_destroy(); 
    exit(0); 
} 

$cmd = '/usr/bin/stat -c "%s" ' . $filepath . "/" . $filename; 
$out = array(); 
$ret = 0; 

exec($cmd, $out, $ret); 

header('Content-type: application/octet-stream'); 
header('Content-Length: ' . $out[0]); 
header('Content-Disposition: attachment; filename="' . $filename . '"'); 

readfile($filepath . "/" . $filename); 

注意:我使用exec()调用,因为大多数文件都大(> 2GB),较大的文件导致filesize()和stat()函数失败。

无论如何,这段代码几乎适用于所有的文件。但是,如果文件大小为,大小为,大小为2 GB(2147483648字节),则不会发送头文件,浏览器会尝试下载PHP页面,导致保存一个空文件,称为download.php。

这里的时候我测试了,卷曲发生了什么事:

测试#1:获得1 GB的文件名为bigfile1:

$ curl -v http://<SERVER>/download.php?id=bigfile1 
* About to connect() to <SERVER> port 80 (#0) 
* Trying <IP_ADDRESS>... connected 
* Connected to <SERVER> (<IP_ADDRESS>) port 80 (#0) 
> GET /download.php?id=bigfile1 HTTP/1.1 
> User-Agent: curl/7.18.2 (i486-pc-linux-gnu) libcurl/7.18.2 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.8 libssh2/0.18 
> Host: <SERVER> 
> Accept: */* 
> 
< HTTP/1.1 200 OK 
< Date: Tue, 21 Jun 2011 19:10:06 GMT 
< Server: Apache/2.2.4 (Unix) mod_ssl/2.2.4 OpenSSL/0.9.8c PHP/5.3.0 
< X-Powered-By: PHP/5.3.0 
< Set-Cookie: PHPSESSID=382769731f5e3782e3c1e3e14fc8ae71; path=/ 
< Expires: Thu, 19 Nov 1981 08:52:00 GMT 
< Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 
< Pragma: no-cache 
< Content-Length: 1073741824 
< Content-Disposition: attachment; filename="bigfile1" 
< Content-Type: application/octet-stream 
< 
* Connection #0 to host <SERVER> left intact 
* Closing connection #0 

测试#2:获得2 GB的文件名为bigfile2

$ curl -v http://<SERVER>/download.php?id=bigfile2 
* About to connect() to <SERVER> port 80 (#0) 
* Trying <IP_ADDRESS>... connected 
* Connected to <SERVER> (<IP_ADDRESS>) port 80 (#0) 
> GET /download.php?id=bigfile2 HTTP/1.1 
> User-Agent: curl/7.18.2 (i486-pc-linux-gnu) libcurl/7.18.2 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.8 libssh2/0.18 
> Host: <SERVER> 
> Accept: */* 
> 
* Empty reply from server 
* Connection #0 to host <SERVER> left intact 
curl: (52) Empty reply from server 
* Closing connection #0 

我已经创建了大小为1 GB,2 GB,3 GB,4 GB和5 GB的测试文件。 2 GB文件是唯一导致此行为的文件,但它始终如一地发生,并且无论客户端浏览器或操作系统如何,它都会发生。该服务器运行Debian GNU/Linux 4.0,Apache 2.2.4和PHP 5.3.0。

任何想法将不胜感激。谢谢!

+1

它是导致问题的'readfile'或'exec'吗?逐个评论它们以检查。 – Matthew

+0

什么是您的'PHP_INT_MAX'输出?和/或,这是一个64位或32位主机(操作系统类型是不相关的,我的意思是这里的实际硬件类型)? –

+0

每隔一行调用一次'error_log()'来找出2G文件的代码路径。难道只是该文件不存在并且你的脚本在'session_destroy()'之后退出? – Robin

回答

3

在64位安装中,我使用PHP 5.3.6提到的文件大小没有问题。 32位安装递给我:

failed to open stream: Value too large for defined data type 

话虽如此,我甚至不会用readfile。相反:

header("X-Sendfile: $filepath/$filename"); 

它需要安装和配置mod_xsendfile。

+0

我看到了另一篇文章中提到的mod_xsendfile。也许我会试试看。谢谢。 – Dave

+0

X-Sendfile解决了这个问题(并且mod_xsendfile非常易于安装和配置)。感谢您的信息! – Dave

1

我把它放在评论中,但格式不起作用,所以我会把它作为一个答案发布,尽管它没有回答你的问题。如果攻击者访问此URL会发生什么情况?

http://yourserver.com/download.php?id=../../../../etc/passwd

当心的目录遍历在id GET变量。

$realfile = realpath($filepath .'/'. $filename); 
if (substr($realfile, strlen($filepath)) == $filepath && file_exists($realfile)) 
{ 
    do_the_download(); 
} else { 
    die('file not found (or you are a bad person)'); 
} 
+0

谢谢。实际上,我在这种情况下做的是获取目录中的文件列表,然后检查列表中的$ _GET ['id']'的值。如果它包含列表中除文件名以外的任何内容,则该脚本将引发错误。 – Dave

+0

那么,这取决于该目录中的文件数量,可能是性能上的一击,但是,至少你是受保护的:) –