PHP での愚か者防止のクロスブラウザ強制ダウンロード
-
19-09-2019 - |
質問
私は強制ダウンロードを使用して、サイトで主にzipとmp3をダウンロードしています(http://pr1pad.kissyour.net) - Google Analytics やデータベースでダウンロードを追跡し、実際のダウンロード パスを非表示にします。
それは次のとおりです。
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 Open Modal Open/Save/Cancel)
- ファイルのサイズと推定ダウンロード時間を表示します (それはブラウザ次第です、私は知っていますが、最初にブラウザはファイルサイズを知る必要があります)
- PC + Mac (Linux...) 上の IE6、7、8、FF3、Opera、Chrome、および safari で動作 (テストおよび確認済み!)あまり気にしない) - これはヘッダー部分です
- サーバーには56MBのメモリ制限もありますが、これを追加することはできないので、それも重要です
よろしくお願いします。
編集:.htaccess で強制的にダウンロードしようとしたので、これまで以上にひどいことになったと感じています。うまくいきましたが、マイナー/メジャー (好みで選んでください) 問題はほとんどありませんでした。
- フルパスが表示されました(私にとってはマイナーです)
- ダウンロード全体が完了するまで待機し(「接続中」と表示されます)、ダウンロード中であることを表示するだけで、1秒でダウンロードされます(私にとっては重要です)
さて、.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のすべてのmentiondブラウザで期待どおりに動作します - 。これまでのところ、それで何の問題
他のヒント
さて、これは古い問題であり、アダムはすでに彼自身の答えを受け入れたので、おそらく彼は彼自身のため、この作業を得たが、それは働いた理由を彼は説明しませんでした。
:私が気づいたことの一つは、彼は、ヘッダーを使用し、質問にありました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ので設定されたコンテンツヘッダを含むと想定)最初のクライアントにすべてを出力します。 ob_end_clean()
の呼び出しを変更し、それがバッファをクリアし、それを破棄します。これはあなた自身のヘッダを生成するためのクリーンなスタートを与えるだろう。
もう一つのヒント:
の代わりにストリームとしてファイルを読み取り、ブロック単位でそれを渡すのは、この機能を試してみることができます:
// ...
if (file_exists("dir-with-files/$filename")) {
readfile($file);
}
これは、ほぼすべての面倒を見る。
print($this->_fullread($stream,1024*16));
私は_fullreadがクラス内にあると仮定しますか?上記のコードは、のように見える場合、$this->
は動作しません。
あなたは、ヘッダーのもののすべてをコメントアウト場合は、画面に出力ファイルの内容をしていますか?
暗闇の中でだけでショットを...私は(だけでなく、あなたのようにテストされていません)私の力のダウンロード」コードで送信するすべてのヘッダは、あなたと同じです。 ヘッダ( "のCache-Control:プライベート"、偽);
の代わりに: ヘッダ( "のCache-Control:パブリック"、FALSE);
それが役立つかない場合、私は知りません。
、あなたはあなたのユーザーに残り時間、または予想されたサイズを表示することができません。どうして?ブラウザが途中でダウンロードを再開しようとした場合ので、あなたは、PHPでそのケースを処理する方法がありません。
あなたは、通常のファイルのダウンロードをお持ちの場合は、、ApacheはHTTP経由でのダウンロードを再開し支持することができるが、ダウンロードが一時停止された場合には、Apacheはクライアントが要求すると、あなたのスクリプトの中で物事が実行された場所を考え出すの方法はありません次のチャンクます。
ブラウザがダウンロードを一時停止したときに基本的に、それは完全にウェブサーバへの接続を終了します。あなたがダウンロードを再開すると、接続が再び開かれ、そしてリクエストは「バイト数Xから起動します」と言ってフラグが含まれています。しかし、上記のあなたのPHPを見てWebサーバに、どこからバイトX来るのでしょうか?
サーバーが中断、ダウンロードの際にスクリプトを再開する場所を特定するためにのために理論的には可能かもしれませんが、Apacheが再開する場所を把握しようとしません。その結果、ブラウザに送信されたヘッダは、サーバが、ほとんどの主要なブラウザでの予想ファイルサイズとタイムリミット項目をオフに履歴書を、サポートしていないと述べています。
編集:あなたがこのケースを処理することができるかもしれないようだが、それはあなたの部分に多くのコードを取ることになるだろう。 http://www.php.net/manual/en/functionを参照してください。 #84115 の.fread.phpます。
の代わりに外部からそれにアクセスできないよう世界からあなたのdownloadpathを隠すだけ上記のスクリプトを使用してファイルにアクセスしようとします。 そうするために、あなたはhtaccessのファイルディレクトリに(先頭のドットを忘れないでください」の.htaccess'という名前のテキストファイル)を置きます。 htaccessファイルの内容はつぎのようになります:
order deny,allow
deny from all
allow from localhost
今*世界はウェブサーバが作成したようになりますから、パスにアクセスしようとしている401禁じられています。
あいまいさによるセキュリティは、あなたが望むものではありません。