从外部Web根目录下载文件

时间:2015-10-06 作者:Rob

我已经设置了一些代码来下载存在于web根目录上方文件夹中的zip文件。下载将从WordPress内的用户帐户页面触发。它们不需要是银行级安全的,但我希望阻止从站点外部直接访问文件,并使其仅可供具有正确权限级别的用户访问,并且只能从相应的用户帐户页面访问。该站点完全是https。

zip文件所在的文件夹通过htaccess进行保护。

分配给特定用户角色的每个用户将在其“帐户”页面上看到一个下载链接:

if(current_user_can(\'download_these_files\')){
    $SESSION[\'check\'] = \'HVUKfb0IG1HIzHxJj5fZ\';
    ?>
        <form class="user-file-download-form" action="/download.php" method="POST">
            <input type="submit" name="submit" value="Download File">
        </form>
    <?php
}
此表单提交下载。php,它位于web根目录中,包含一些我在Google帮助下拼凑起来的代码。

session_start();
if( isset( $_POST[\'submit\'] ) ){
    $check = $_SESSION[\'check\'];
    if( $check === \'HVUKfb0IG1HIzHxJj5fZ\' ){
        $file = /path/to/file/above/root.zip;
        header(\'Content-Description: File Transfer\');
        header(\'Content-Type: application/zip\');
        header(\'Content-Disposition: attachment; filename=\' . basename($file));
        header(\'Content-Transfer-Encoding: binary\');
        header(\'Expires: 0\');
        header(\'Cache-Control: must-revalidate, post-check=0, pre-check=0\');
        header(\'Pragma: public\');
        header(\'Content-Length: \' . filesize($file));
        ob_clean();
        flush();
        readfile( $file );
        exit;
    }else{
        header( \'Location: https://example.com/404page/\' );
}else{
    header( \'Location: https://example.com/404page/\' );
}
这非常有效。但我忍不住想知道我是否应该做些不同的事情。我希望了解这个实现是否已准备好生产,或者是否遗漏了一些重要内容,因为这是我第一次尝试这样的东西。

非常感谢。

2 个回复
SO网友:jaswrks

你们的产品已经准备好了。然而,还有一些小的改进空间,所以我将为您指出这些改进。另请参阅下面我关于X-Sendfile和X-Accel-Redirect的注释。

更换这些线路:

ob_clean();
flush();
具有以下功能:

while (@ob_end_clean());
关键是,如果输出缓冲区中已经有东西,您不想将其清除,只想将其清除。如果刷新它,将在可下载文件内容的前面加上输出缓冲区内容,这只会损坏可下载文件。请参见:http://php.net/manual/en/function.ob-end-clean.php

这一行之后:

$file = /path/to/file/above/root.zip;
添加以下内容以确保服务器级别的GZIP压缩已关闭。这可能不会对您当前的web主机造成影响,但将站点移到其他位置,如果没有这些行,您可能会看到脚本严重中断。

@ini_set(\'zlib.output_compression\', 0);
if(function_exists(\'apache_setenv\')) @apache_setenv(\'no-gzip\', \'1\');
header(\'Content-Encoding: none\');
<小时>Caution: 在较大的文件(例如超过20MB的文件)上使用这种PHP驱动的文件下载技术时要小心。为什么?两个原因:

PHP有内部内存限制。如果readfile() 如果将文件读入内存并提供给访问者,则脚本将失败。

此外,PHP脚本也有时间限制。如果访问速度非常慢的访问者需要很长时间才能下载较大的文件,则脚本将超时,用户将体验到下载尝试失败,或收到部分/损坏的文件。


Caution: 还要注意,PHP驱动的文件下载使用readfile() 该技术不支持可恢复的字节范围。因此,暂停下载或下载被以某种方式中断,不会让用户有继续的选项。他们需要重新开始下载。可以在PHP中支持范围请求(resume),但这是tedious.

从长远来看,我的建议是,您应该开始寻找一种更有效的服务受保护文件的方法,即X-Sendfile 在Apache中,以及X-Accel-Redirect 在Nginx中。

X-Sendfile和X-Accel-Redirect都使用相同的基本概念。与其要求PHP之类的脚本语言将文件拉入内存,不如简单地告诉web服务器进行内部重定向,并提供其他受保护文件的内容。简而言之,您可以去掉上面的大部分内容,并将解决方案简化为header(\'X-Accel-Redirect: ...\').

SO网友:Mark Kaplun

不要使用表单,只需使用如下链接/下载php?文件=abc。拉链在下载时检查用户凭据。php和您节省了会话和表单处理的需要。

相关推荐

Cannot modify headers

我非常讨厌这个错误。根本不懂。这是我的代码,您要关注的是foreach循环:public function reset_theme_options(){ if($this->is_theme_options_array()){ foreach($this->_theme_option[\'admin_options\'] as $option_name=>$value){ if($value != false){&#