業務システム開発・保守の実務メモを発信中

Laravelのファイルアップロードで元ファイル名をそのまま保存してはいけない理由

Laravelでアップロードされたファイルの元ファイル名を残したいとき、getClientOriginalName() の値をそのまま storeAs() に渡したくなることがあります。

しかし、元ファイル名はクライアント由来の値なので、ストレージ上の保存ファイル名として信用するべきではありません。保存時はLaravel側で名前を生成し、元ファイル名が必要なら保存名とは別の情報として扱うのが基本です

本記事ではLaravel 13.xを前提に、storeAs() で元ファイル名を指定した場合と、store() に保存名を生成させた場合の違いを、実際のコードと結果を交えて説明します。

目次

getClientOriginalName()を保存名として信用しない

結論からいうと、次のように getClientOriginalName() の戻り値をそのまま保存名へ使うのは避けたほうがよいです。

PHP
$file = $request->file('file');

$path = $file->storeAs(
    'uploads',
    $file->getClientOriginalName()
);

公式ドキュメントでは、getClientOriginalName()getClientOriginalExtension() は、悪意のあるユーザーによってファイル名や拡張子を変更される可能性があるため unsafe とされています。

つまり、getClientOriginalName() で取得できるのは「利用者側から送られてきた元ファイル名」であり、アプリケーションが信頼して決めた保存名ではありません。

元ファイル名を画面表示などの用途で保持すること自体と、その値をストレージ上の物理ファイル名として使うことは分けて考える必要があります。

storeAs()に元ファイル名を渡すと、その名前で保存される

storeAs() は、保存先だけでなく保存ファイル名も明示的に指定したいときに使えます。

たとえば、元ファイル名が monthly-report.txt のファイルに対して、次のコードを実行します。

PHP
$file = $request->file('file');
$originalName = $file->getClientOriginalName();

$path = $file->storeAs(
    'article-original-name/store-as',
    $originalName,
    'local'
);

このとき、getClientOriginalName() と保存パスは次のようになりました。

JSON
{
  "original_name": "monthly-report.txt",
  "path": "article-original-name/store-as/monthly-report.txt"
}

monthly-report.txt というクライアント由来の名前が、そのまま保存ファイル名に使われています。

storeAs() 自体が問題なのではありません。問題は、保存名として渡す値に getClientOriginalName() をそのまま使い、クライアント由来の値を保存名として信用してしまうことです。

保存名はstore()でLaravelに生成させる

保存ファイル名を自分で指定する必要がなければ、store() を使うとLaravel側で名前を生成できます。

PHP
$file = $request->file('file');

$path = $file->store(
    'article-original-name/store',
    'local'
);

Laravelの公式ドキュメントでは、store() に保存先ディレクトリだけを指定すると、Laravelが保存ファイル名を自動生成します。拡張子はファイルのMIMEタイプをもとに決定され、戻り値には保存されたパスが返ります。

実際に monthly-report.txt を保存し、getClientOriginalName()hashName()extension()store() の戻り値を確認すると、次の結果になりました。

JSON
{
  "original_name": "monthly-report.txt",
  "hash_name": "i6MAw7IqfAMjFrCu592QYnK6qcAkiev7QlAsf9tp.txt",
  "extension": "txt",
  "path": "article-original-name/store/i6MAw7IqfAMjFrCu592QYnK6qcAkiev7QlAsf9tp.txt"
}

元ファイル名の monthly-report.txt ではなく、Laravelが生成した40文字の名前に .txt が付いたファイル名で保存されています。

この例では、hashName() の戻り値と store() が返したパスのファイル名も一致しています。

Laravel 13.xの hashName() はランダムな40文字の名前を生成し、guessExtension() で求めた拡張子を付ける実装です。Laravel公式でも、クライアント由来の元ファイル名や元拡張子ではなく、hashName()extension() の利用が推奨されています。

保存名をLaravelに任せられるケースでは、まず store() を使うのが分かりやすい選択です。

元ファイル名が必要でも、保存名とは分けて扱う

業務上、利用者がアップロードした元ファイル名を残したいケースはあります。その場合も、元ファイル名をストレージ上の保存名として使う必要はありません。

考え方としては、次の2つを別の情報として扱います。

  • ストレージ上の保存名:Laravel側で生成した名前
  • ユーザー向けの元ファイル名:getClientOriginalName() で取得した名前

たとえば、保存処理では store() を使い、必要に応じて元ファイル名も別途取得します。

PHP
$file = $request->file('file');

$originalName = $file->getClientOriginalName();
$path = $file->store('article-original-name/store', 'local');

$path は実際に保存されたファイルを識別するために使い、$originalName は表示など元ファイル名が必要な用途の情報として扱います。

このように分離しておけば、「利用者に見せたい名前」と「ストレージ上で安全に管理したい名前」を同じものとして扱わずに済みます。

まとめ

getClientOriginalName() はアップロード元のファイル名を取得するためのメソッドですが、その値はクライアント由来なので、ストレージ上の保存名としてそのまま信用するべきではありません。

保存ファイル名を自分で決める必要がなければ、store() を使ってLaravel側に生成させる方法が基本です。元ファイル名を業務上残したい場合も、Laravelが生成した保存名とは別の情報として扱うようにしましょう。

よかったらシェアしてね!
  • URLをコピーしました!
目次