PHPでファイルをダウンロードさせる方法
サイトからファイルをダウンロードさせるには、「<a href="ダウンロードさせたいファイル名"・・・」のようにファイルに直接リンクさせることでも可能ですが、以下の2点が気になります。
- ダウンロードさせるファイルは公開ディレクトリ(htdocsとかhtml以下)におく必要がある。
- ブラウザによっては「右クリック→名前をつけて保存」など操作がちょっと面倒。
これを解消するために、PHPを通してファイルをダウンロードさせる方法をやってみました。
PHPのちょっとしたTIPSさんのページの様に「try/catch」を使わなくても実装出来るのですが、今回はあえて「try/catch」を使用した実装を試してみました。
エラーハンドラの設定
まず、ダウンロードをさせるにあたってファイル操作を行うので、ファイルオープン等の例外をキャッチするためにエラーハンドラを設定します。
ユーザー定義のエラーハンドラとして、例外をスローさせる関数を作成します。(throw new Exception)
あとは作成した関数を「set_error_handler」でエラーハンドラとして設定します。
<?php /* 無名関数を使用する場合(PHP5.3以降のみ可) */ $errHandler = function($errno, $errstr, $errfile, $errline){ throw new Exception($errstr, $errno); }; set_error_handler($errHandler); /* 通常の関数を使用する場合 */ function errHandler($errno, $errstr, $errfile, $errline){ throw new Exception($errstr, $errno); } set_error_handler('errHandler'); ?>
ダウンロード可能なディレクトリとファイル拡張子のチェック
PHPからのダウンロードの場合、公開ディレクトリ(htdocs,html)以外のディレクトリからもファイルをダウンロードすることが出来てしまいます。
そこで、特定のディレクトリにあるファイル以外はダウンロードさせないように、ダウンロードが可能なディレクトリと、ダウンロードが可能なファイル拡張子のチェックを行ないます。
正規表現を使用してチェックを行ないますが、ディレクトリパスの判定として「/(スラッシュ)」を使用するため、正規表現のデリミタを「#」にしています。
特定のディレクトリ以外のファイルをダウンロードしようとした場合(チェックでエラーとなった場合)は、「trigger_error」を使用してエラーメッセージ('Error: File is not permitted.')と共に例外をスローします。
<?php /* Download Directory */ define("DOWNLOAD_DIR", "/var/download"); /* Directory and Extension Check */ if ( !preg_match('#^'.DOWNLOAD_DIR.'.*\.(tsv|csv|txt)$#', $file) ) { trigger_error('Error: File is not permitted.'); } ?>
ダウンロードファイルの読み込み&出力
「file_get_contents」を使用してファイルの中身(データ)を一括で読み込みます。ここでファイルがオープンできない場合は自動的に例外がスローされます。
「file_get_contents」でのファイルデータの読み込みに成功したら、ダウンロード用のHTMLヘッダーを出力します。
出力するHTMLヘッダーは以下の通りになります。
HTMLヘッダー名 | 設定値 |
---|---|
Content-Disposition | inline; filename="ダウンロードするファイル名(パス無し)" |
Content-Length | ダウンロードさせるファイルのバイト数 |
Content-Type | application/octet-stream(固定) |
HTMLヘッダー出力後は、「file_get_contents」を使用して読み込んだファイルデータを「echo」で出力します。
<?php /* File Read */ $read_data = file_get_contents($file); /* Output HTTP Header */ header('Content-Disposition: inline; filename="'.basename($file).'"'); header('Content-Length: '.$content_length); header('Content-Type: application/octet-stream'); /* Output File Data */ echo $read_data; ?>
catch部分
try/catchのcatchの部分です。
例外となった場合、ここにExceptionクラスのインスタンスが渡されてきます。Exceptionクラスの「getMessage()」を使用してエラーメッセージを取得します。
エラーメッセージについては、HTMLとして出力されることを考慮してを「htmlentities」でHTMLエスケープし、「die」に渡します。
ちなみに「die」は「exit」のエイリアスとなっています。
<?php catch ( Exception $e ) { /* Process end. HTML Escapes in the Error message. */ die( htmlentities($e->getMessage()) ); } ?>
サンプルコード(全体)
上記の内容をすべて踏まえたサンプルコードが以下になります。
<?php /* Download Directory */ define("DOWNLOAD_DIR", "/var/download"); /* * File Download Function * @param string $file (AbsolutePath + FileName) * @return none */ function download($file) { $errHandler = function($errno, $errstr, $errfile, $errline){ throw new Exception($errstr, $errno); }; set_error_handler($errHandler); try { /* Directory and Extension Check */ if ( !preg_match('#^'.DOWNLOAD_DIR.'.*\.(tsv|csv|txt)$#', $file) ) { trigger_error('Error: File is not permitted.'); } /* File Existence Check */ else if ( !file_exists($file) ) { trigger_error('Error: File('.$file.') does not exist'); } /* File Size 0(Zero) Check */ else if ( ($content_length = filesize($file)) == 0 ) { trigger_error('Error: File size is 0.('.$file.')'); } /* File Read */ $read_data = file_get_contents($file); /* Output HTTP Header */ header('Content-Disposition: inline; filename="'.basename($file).'"'); header('Content-Length: '.$content_length); header('Content-Type: application/octet-stream'); /* Output File Data */ echo $read_data; #unlink($file); } catch ( Exception $e ) { /* Process end. HTML Escapes in the Error message. */ die( htmlentities($e->getMessage()) ); } } ?>
今回参考にしたページ
任意のファイルをダウンロードさせる - PHPのちょっとしたTIPS
http://www.spencernetwork.org/memo/tips-5.php
PHPでクリックした時にファイルをダウンロードさせる設定 - #OPQR.jp
http://opqr.jp/2007/09/php.html
/(スラッシュ)を含む正規表現を見やすくする方法。- kimihiko Tech.htm
http://tech.kimihiko.jp/article/13153562.html
PHPの例外ってどれぐらい使われているのでしょうか - 一人WEBサービス屋メモ
http://d.hatena.ne.jp/uratch/20100303/1267587165