web-dev-qa-db-ja.com

外部Webルートからファイルをダウンロードする

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
}

このフォームはdownload.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/' );
}

これは完璧に機能します。しかし、私は仕方がないのですが、違うことをするべきかどうか疑問に思います。私は、この実装が実動対応かどうか、またはこれが私がこのようなことを試みるのは初めてなので、私が何か重要なものを見逃していないかどうかについて意見を求めたいと思いました。

ありがとうございました。

1
Rob

あなたが持っているものは生産準備ができています。しかし、いくつかの小さな改良の余地があるので、私はあなたのためにそれらを指摘するつもりです。 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圧縮を確実に無効にします。これはあなたの現在のウェブホストでは違いを生じないかもしれませんが、サイトをどこか他の場所に動かして、これらの行がなければあなたはスクリプトがひどく壊れるのを見ることができます。

@ini_set('zlib.output_compression', 0);
if(function_exists('Apache_setenv')) @Apache_setenv('no-gzip', '1');
header('Content-Encoding: none');

注意:このPHP駆動のファイルダウンロード技術をより大きなファイル(例えば、20MBを超えるサイズ)に使用する場合は注意が必要です。どうして? 2つの理由

  • PHPには内部メモリ制限があります。ファイルをメモリに読み込んでビジターに提供するときにreadfile()がその制限を超えると、スクリプトは失敗します。

  • さらに、PHPスクリプトにも制限時間があります。非常に遅い接続でビジターがより大きなファイルをダウンロードするのに長い時間がかかる場合、スクリプトはタイムアウトになり、ユーザは失敗したダウンロード試行を経験するか、または部分的な/破損したファイルを受け取るでしょう。


注意:readfile()テクニックを使ったPHPによるファイルのダウンロードは再開可能なバイト範囲をサポートしていないことにも注意してください。そのため、ダウンロードを一時停止したり、ダウンロードが何らかの方法で中断されたりしても、ユーザーは再開することができません。彼らはもう一度ダウンロードを開始する必要があります。 PHPでRangeリクエスト(resume)をサポートすることは可能ですが、それは 面倒です


長期的には、Apacheで X-Sendfile と呼ばれる、保護されたファイルを提供するためのはるかに効果的な方法を検討し始めることをお勧めします。 Nginxでは、 X-Accel-Redirect となります。

X-SendfileとX-Accel-Redirectはどちらも同じ基本概念で機能します。 PHPのようなスクリプト言語でファイルをメモリに取り込むように要求するのではなく、単に内部リダイレクトを行い、それ以外の方法で保護されているファイルの内容を提供するようにWebサーバーに指示します。要するに、あなたは上記の多くを廃止して、解決策をただheader('X-Accel-Redirect: ...')に減らすことができます。

2
jaswrks