PHPでアップロードするファイルの拡張子をチェックするサンプル
と来ましたので、今度はPHPでファイルの拡張子をチェックする例です。
前回、前々回としつこいくらい書きましたが、ブラウザ側(JavaScript)でファイルの拡張子をチェックしたからといって、PHPでのチェックを怠ってはなりません。むしろ、どちらか一方しかしたくない!というならPHP側だけ実装するべきです。
やろうと思えばブラウザを経由しないPOST操作なんて簡単に出来ちゃいますからね。
PHPでファイル名の拡張子を取得する例
■ファイル名を渡したら拡張子を返す関数
//ファイル名から拡張子を取得する関数
function getExt($filename)
{
return pathinfo($filename, PATHINFO_EXTENSION);
}
■解説
JavaScriptのときもわずか3行で済んだ処理ですが、PHPでは輪をかけてシンプルで、1行で済んじゃいます。
標準関数であるpathinfo関数のおかげですね。このpathinfoにPATHINFO_EXTENSIONを渡すだけで良いのです。
じゃあ、わざわざgetExt関数とか作る必要ないじゃないか、と言われればそのとおり。
JavaScriptと同じ関数名にすることでやっていることが一目でわかるようにしているだけです。
自分ひとりで開発しているときは可読性なんてどうでも良いような気がしてくるかもですが、1週間も経ったら何でこんな書き方したのかと考え始めるし、1ヶ月も経ったら自分が書いた記憶すらなくなっているのが普通です。いやホントホント。
なので、多少冗長になったとしても他人が読んでもわかりやすい書き方を心がけるのは大事かなって思います。
予め指定した拡張子のみアップロードを許可する例
//アップロードを許可する拡張子
$cfg['ALLOW_EXTS'] = array('jpg', 'jpeg', 'png');
//アップロードされたファイル名の拡張子が許可されているか確認する関数
function checkExt($filename)
{
global $cfg;
$ext = strtolower(getExt($filename));
return in_array($ext, $cfg['ALLOW_EXTS']);
}
■解説
こちらもJavaScriptと同様の書き方ですね。指定されたファイル名の拡張子を取得し、予め設定しておいた配列の中にあるかどうか確認して、trueかfalseを返すようにしています。
PHPの場合、関数外のグローバル変数を参照するためには global ほにゃらら; と関数内で宣言する必要があるため、$cfgみたいな連想配列を使うことが多いです。global $cfg;の一行で済みますし、あれやこれやと個別にグローバル変数を用意するよりも $cfg['ほにゃらら'] だったら、あぁこれはグローバルだな、と一目でわかるためです。
この$cfgはデータベースから取得して設定するようにしても良いですし、config.php みたいな$cfgに設定だけするPHPファイルを用意してインクルードして使うのが一般的でしょうか。
PHPとJavaScriptを使ったアップロード処理の全体像
と解説してきて、今回PHPでの拡張子チェックも解説しましたので、これを全部組み合わせた全体像を書き出しておきます。
■PHPでの複数ファイルアップロードと拡張子チェックの例
<?php
//アップロードを許可する拡張子
$cfg['ALLOW_EXTS'] = array('jpg', 'jpeg', 'png');
?>
<html lang="ja">
<head><meta charset="utf-8"></head>
<body>
<h1>アップロード処理のサンプル</h1>
<form enctype="multipart/form-data" method="post" onsubmit="return checkForm();">
<input type="hidden" name="MAX_FILE_SIZE" value="1000000">
<input name="file[]" type="file" multiple="multiple" accept="image/*">
<input type="submit" name="_upload" value="アップロード">
</form>
<script>
//PHPで設定した拡張子をJavaScriptでも使う
var allow_exts = new Array(<?php echo "'".implode("','", $cfg['ALLOW_EXTS'])."'"; ?>);
</script>
<?php
//[アップロード]ボタンの押下確認
if (isset($_POST['_upload'])) {
foreach ($_FILES['file']['tmp_name'] as $no => $tmp_name) {
$filename = $_FILES['file']['name'][$no];
//ファイルの拡張子をチェック
if (checkExt($filename)) {
//ファイルをテンポラリから保存場所へ移動
if (move_uploaded_file ($tmp_name, './'.$filename)) {
echo $filename.'をアップロードしました<br>';
} else {
//エラー処理
}
} else {
echo $filename.'はアップロードできません<br>';
}
}
}
//ファイル名から拡張子を取得する関数
function getExt($filename)
{
return pathinfo($filename, PATHINFO_EXTENSION);
}
//アップロードされたファイル名の拡張子が許可されているか確認する関数
function checkExt($filename)
{
global $cfg;
$ext = strtolower(getExt($filename));
return in_array($ext, $cfg['ALLOW_EXTS']);
}
?>
<script>
//アップロードを許可する拡張子
var allow_exts = new Array('jpg', 'jpeg', 'png');
//フォーム内容の確認をする関数
function checkForm()
{
var files = document.getElementsByName('file[]')[0].files;
//ファイルが選択されているか確認
if (files.length == 0) {
alert('ファイルを選択してください');
return false;
}
return true;
//指定されたファイルの数だけ拡張子をチェックする
for (var i = 0; i < files.length; i++) {
if (!checkExt(files[i].name)) {
alert(files[i].name + 'はアップロードできません');
return false;
}
}
//チェックを通ったらtrueを返す
return true;
}
//アップロード予定のファイル名の拡張子が許可されているか確認する関数
function checkExt(filename)
{
//比較のため小文字にする
var ext = getExt(filename).toLowerCase();
//許可する拡張子の一覧(allow_exts)から対象の拡張子があるか確認する
if (allow_exts.indexOf(ext) === -1) return false;
return true;
}
//ファイル名から拡張子を取得する関数
function getExt(filename)
{
var pos = filename.lastIndexOf('.');
if (pos === -1) return '';
return filename.slice(pos + 1);
}
</script>
</body>
</html>
■解説
便宜上、スクリプトの最上部で $cfg['ALLOW_EXTS'] の設定をしていますが、先述したとおり、本来はデータベースから読み込んだり、外部ファイルからインクルードしたりするほうが良いでしょう。
そして、前回の記事で書いた
例えば、アップロードを許可する拡張子の種類が変わった場合、JavaScriptとPHPの両方を修正しなければならないというメンテナンス上の問題もあります。
という部分への対応として、
<script>
//PHPで設定した拡張子をJavaScriptでも使う
var allow_exts = new Array(<?php echo "'".implode("','", $cfg['ALLOW_EXTS'])."'"; ?>);
</script>
という処理で、PHPで設定した配列をJavaScriptでも設定するようにしました。一応これでPHP側の $cfg['ALLOW_EXTS'] だけ修正すればJavaScript側も自動的に対応できる、という寸法です。
あとは前回までに説明したところに加えて、checkExt関数で拡張子をチェックし、許可されていない拡張子だったらアップロードせずに「○○はアップロードできません」というメッセージを出すようにしただけです。
まとめ
今回は全体像を載せたため、ちょっと長いサンプルになりましたが、結局のところ、前回と変わらず、
- ファイル名から拡張子を取得する関数 (getExt)
- ファイル名の拡張子が許可されているか確認する関数 (checkExt)
をJavaScriptと同様のやり方でPHPでも実装したに過ぎません。
あとはオマケですが、メンテナンスを楽にするためにPHP側で「許可する拡張子一覧($cfg['ALLOW_EXTS'])」を用意し、それをJavaScriptでも参照できるようにした、というくらいです。
実際に運用しているWebサイトのコードと比べるとこれでもほんの一部なのですが、ブログで解説するには少々長くなってきましたね…。
しかし、ここまで書いておいてアレなのですが、実際問題として、拡張子をチェックしただけでは、「gifのふりしたJavaScript」とかをアップロードされる危険性があるため、本来はファイルの中身自体をチェックしなければいけません。幸いなことにPHPにはMIMEタイプをチェックする関数があるため、次回はその例を紹介しようと思います。
2020/05/13追記
pathinfoを使ってファイル名から拡張子を取得する例はわかりやすくて好きなのですが、軽く検証したところstrrposと比べて抽出速度が3.5倍くらい遅かったので一応strrposを使った例も追記しておきます。
//ファイル名から拡張子を取得する関数
function getExt($filename)
{
if (($pos = strrpos($filename, '.')) !== false) {
return substr($filename, $pos + 1);
} else {
return '';
}
}
更に詳しいことは「PHPで拡張子等の末尾文字列を比較するのに最も速い方法を検証してみた」を参照してください。