PHP 中的防白痴跨浏览器强制下载
-
19-09-2019 - |
题
我正在使用强制下载在网站上下载大部分 zip 和 mp3(http://pr1pad.kissyour.net) - 跟踪谷歌分析、数据库中的下载并隐藏真实的下载路径:
是这样的:
extending CI model
... - bunch of code
function _fullread ($sd, $len) {
$ret = '';
$read = 0;
while ($read < $len && ($buf = fread($sd, $len - $read))) {
$read += strlen($buf);
$ret .= $buf;
}
return $ret;
}
function download(){
/* DOWNLOAD ITSELF */
ini_set('memory_limit', '160M');
apache_setenv('no-gzip', '1');
ob_end_flush();
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public",FALSE);
header("Content-Description: File Transfer");
header("Content-type: application/octet-stream");
if (isset($_SERVER['HTTP_USER_AGENT']) &&
(strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false))
header('Content-Type: application/force-download'); //IE HEADER
header("Accept-Ranges: bytes");
header("Content-Disposition: attachment; filename=\"" . basename("dir-with- files/".$filename) . "\";");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize("dir-with-files/".$filename));
// Send file for download
if ($stream = fopen("dir-with-files/$filename", 'rb')){
while(!feof($stream) && connection_status() == 0){
//reset time limit for big files
set_time_limit(0);
print($this->_fullread($stream,1024*16));
flush();
}
fclose($stream);
}
}
它在带有 CI 1.7.2 的 LAMP 上 - 这是我自己的方法,根据互联网上的各种操作方法整理而成,因为在开发过程中出现了以下问题:- 服务器限制. ini_set
没有帮助,所以我使用了缓冲 _fullread
相反正常 fread
, ,它被用来代替 @readonly
- ob_end_flush(),因为站点是在 CI1.7.2 中完成的,我需要清理缓冲区
现在...这不起作用。确实如此,然后它停止显示预期的大小/下载时间 - 我试图清理它,当我清理代码时,发生了一些事情,我不知道发生了什么 任何 以前的版本 - 它没有工作(设置没有任何变化) - 编辑:不起作用=将所有内容输出到浏览器窗口中。
所以我说,管它呢,我看看这里。
因此,我基本上寻找脚本或函数,我可以将其放入我的输出模型中并执行以下操作:
- 呼叫力量下载(在Chrome中开始下载,在IE,FF,Safari打开模式打开/保存/CANCAL)
- 显示文件大小和估计的 dl 时间(这取决于浏览器,我知道,但首先,浏览器必须知道文件大小
- 可在 PC + Mac(Linux...我真的不在乎) - 这是标题部分
- 在服务器上,我还有 56MB 内存限制,我无法添加它,所以这也很重要
先感谢您。
编辑:现在我感觉比以前更糟糕了,因为我尝试使用 .htaccess 强制下载 - 虽然它有效,但它有一些小/主要(选择你的)问题
- 它显示了完整路径(对我来说次要)
- 它会等到整个下载完成(显示为“连接”),然后只显示正在下载 - 一秒钟内下载(对我来说主要)
现在,虽然我删除了 .htaccess,但它仍然等待下载完成(就像它首先下载到缓存一样)并且它只是得到 connected
并显示打开/保存对话框。
解决方案
所以,我使用了这段代码(它是在互联网上找到的可恢复http下载的修改版本)
function _output_file($file, $path)
{
$size = filesize($path.$file);
@ob_end_clean(); //turn off output buffering to decrease cpu usage
// required for IE, otherwise Content-Disposition may be ignored
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');
header('Content-Type: application/force-download');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header("Content-Transfer-Encoding: binary");
header('Accept-Ranges: bytes');
/* The three lines below basically make the
download non-cacheable */
header("Cache-control: no-cache, pre-check=0, post-check=0");
header("Cache-control: private");
header('Pragma: private');
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
// multipart-download and download resuming support
if(isset($_SERVER['HTTP_RANGE']))
{
list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2);
list($range) = explode(",",$range,2);
list($range, $range_end) = explode("-", $range);
$range=intval($range);
if(!$range_end) {
$range_end=$size-1;
} else {
$range_end=intval($range_end);
}
$new_length = $range_end-$range+1;
header("HTTP/1.1 206 Partial Content");
header("Content-Length: $new_length");
header("Content-Range: bytes $range-$range_end/$size");
} else {
$new_length=$size;
header("Content-Length: ".$size);
}
/* output the file itself */
$chunksize = 1*(1024*1024); //you may want to change this
$bytes_send = 0;
if ($file = fopen($path.$file, 'rb'))
{
if(isset($_SERVER['HTTP_RANGE']))
fseek($file, $range);
while
(!feof($file) &&
(!connection_aborted()) &&
($bytes_send<$new_length) )
{
$buffer = fread($file, $chunksize);
print($buffer); //echo($buffer); // is also possible
flush();
$bytes_send += strlen($buffer);
}
fclose($file);
} else die('Error - can not open file.');
die();
}
然后在模型中:
function download_file($filename){
/*
DOWNLOAD
*/
$path = "datadirwithmyfiles/"; //directory
//track analytics
include('includes/Galvanize.php'); //great plugin
$GA = new Galvanize('UA-XXXXXXX-7');
$GA->trackPageView();
$this->_output_file($filename, $path);
}
它在 Win / MAC 上的所有提到的浏览器中按预期工作 - 到目前为止,没有任何问题。
其他提示
好吧,这是一个老问题,亚当已经接受了自己的答案,所以想必他自己得到了这个答案,但他没有解释为什么它有效。我注意到的一件事是在他使用标题的问题中:
header("Pragma: public");
header("Cache-Control: public",FALSE);
而在他使用的解决方案中:
header("Cache-control: private");
header('Pragma: private');
他没有解释为什么要更改这些内容,但我怀疑这与 SSL 的使用有关。我最近解决了软件中的类似问题,需要通过 HTTP 和 HTTPS 启用下载,使用以下命令添加正确的标头:
if(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) {
header("Cache-control: private");
header('Pragma: private');
} else {
header('Pragma: public');
}
希望有人会发现这个答案中的信息是对上述内容的有用补充。
有一点我觉得很奇怪:你正在呼唤 ob_end_flush()
在函数的开始处。这实际上会清理输出缓冲区,但它也会首先将所有内容输出到客户端(我假设包括 CodeIgniter 设置的 Content-Headers)。将呼叫更改为 ob_end_clean()
, ,它清除缓冲区并丢弃它。这将为您生成自己的标头提供一个干净的开始。
另一个提示:
您可以尝试一下这个函数,而不是作为流读取文件并按块传递它:
// ...
if (file_exists("dir-with-files/$filename")) {
readfile($file);
}
这几乎可以解决所有问题。
print($this->_fullread($stream,1024*16));
我假设 _fullread 在一个类中?如果代码如下所示 $this->
行不通的。
如果您注释掉所有标题内容,它是否会将文件内容输出到屏幕?
只是在黑暗中拍摄...我在“强制下载”代码中发送的每个标头(没有像您的那样经过充分测试)与您的相同,除了我调用:标题(“缓存控制:私人”,假);
代替:标题(“缓存控制:公共”,假);
我不知道这是否有帮助。
如果您打算执行这种“用 php 回显”方法,那么您将无法向用户显示剩余时间或预期大小。为什么?因为如果浏览器尝试中途恢复下载,您将无法在 PHP 中处理这种情况。
如果您进行正常的文件下载,Apache 能够支持通过 HTTP 恢复下载,但在下载暂停的情况下,Apache 无法确定当客户端请求下一个块时脚本中的执行位置。
本质上,当浏览器暂停下载时,它将完全终止与网络服务器的连接。当您恢复下载时,连接将重新打开,并且请求包含一个标志“从字节号 X 开始”。但是对于上面查看 PHP 的网络服务器来说,字节 X 来自哪里?
虽然从理论上讲,如果下载中断,服务器可能会识别从哪里恢复脚本,但 Apache 不会尝试找出从哪里恢复。因此,发送到浏览器的标头表明服务器不支持恢复,这会关闭大多数主要浏览器中的预期文件大小和时间限制项目。
编辑:看起来您也许能够处理这种情况,但是您需要编写大量代码。看 http://www.php.net/manual/en/function.fread.php#84115 .
不要试图向世界隐藏您的下载路径,而是使其无法从外部访问,并且只能使用上述脚本访问文件。为此,您需要在目录中放置一个 htaccess 文件(名为“.htaccess”的文本文件,不要忘记前导点)。htaccess 的内容如下:
order deny,allow
deny from all
allow from localhost
现在尝试从 *world 访问路径将使网络服务器创建 401 禁止。
通过默默无闻实现安全并不是您想要的。