2011-06-21 98 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

那麼,這取決於該目錄中的文件數量,可能是性能上的一擊,但是,至少你是受保護的:) –