やろうとしたこと
やろうとしたことは超シンプルです。- HTTPのmultipart/form-dataで送られてきたファイルをリネームして保存する
- ファイルが無ければエラーにする
サンプルコード(修正前)
PHP+Symfonyのコードです。例外処理などはごっそり省略。/**
* ファイル保存
*/
public function uploadFile(Request $request)
{
// ①リクエストからファイルを取り出す
$file = $request->files->get('file');
// ②ファイルが一時的に保存されているパスを取得
$tmpFilePath = $file->getRealPath();
// ③ファイル存在チェック
if (!$tmpFilePath) {
return 'ファイルが見つかりません';
}
// ④ファイル名をリネーム
$newFilePath = $this->makeNewPath($tmpFilePath);
rename($tmpFilePath, $newFilePath);
// ⑤ファイル保存
$this->saveFile($tmpFilePath);
return '成功しました';
}
起きたこと
②でファイルパスを取得したとき、通常なら、 // $tmpFilePath : ‘/tmp/phpucYAwE’ といった一時ファイルのパスが取得できるのですが、PHPのアップロードサイズ上限値(php.iniで設定する値)を超えたファイルを送ってみると、// $tmpFilePath : '/opt/myWebApp/public'といったように、なぜかWebアプリのルートディレクトリパスが取得されてしまいました。そして、③のファイル存在チェックでもエラー判定できず、そのまま④でアプリのルートディレクトリがリネームされてアプリが動かなくなる事態に…
対処
②でのファイルパス取得にて「getRealPath」を「getPathname」に変えたところ、アップロードサイズ上限を超えたファイルの場合に、期待通りに空文字が取得できるようになりました。ファイルサイズが大きくない場合は、「getRealPath」と「getPathname」は同じパスを返してくれたので、問題なさそうです。
// $tmpFilePath : '' ←ファイルサイズ上限オーバーのときは空文字が返ってくる(期待通り)
サンプルコード(修正後)
ディレクトリパスが取得されてしまうと悲惨なので、②のパス取得部分だけでなく、安全のため③のファイル存在チェックの条件も増やしています。/**
* ファイル保存
*/
public function uploadFile(Request $request)
{
// ①リクエストからファイルを取り出す
$file = $request->files->get('file');
// ②ファイルが一時的に保存されているパスを取得
$tmpFilePath = $file->getPathname(); // getRealPath ではなく getPathname を使用
// ③ファイル存在チェック
if (!$tmpFilePath || is_dir($tmpFilePath)) { // パスがディレクトリだった場合もエラーにしておく
return 'ファイルが見つかりません';
}
// ④ファイル名をリネーム
$newFilePath = $this->makeNewPath($tmpFilePath);
rename($tmpFilePath, $newFilePath);
// ⑤ファイル保存
$this->saveFile($tmpFilePath);
return '成功しました';
}
原因推測
「getPathname」と「getRealPath」はどちらも似た説明になっていますが、「getRealPath」は絶対パスを取得するようです。- SplFileInfo::getPathname
- ファイルへのパスを取得する
- SplFileInfo::getRealPath
- ファイルへの絶対パスを取得する
$file = $request->files->get('file'); のところでnullが取得されて特に被害は発生しないのですが、サイズ上限オーバーの場合は中途半端にファイルが存在する感じになっていそうです。 今回は事例紹介まで。