Laravel で作ったシステムに Excel をアップロードしたら拡張子が zip になった
環境
環境は ローカルのHomestead
と 本番のVPS
の2つで、どちらでも発生。
- Laravel Framework 5.8.24
- PHP 7.3.4 (PHP 7.2.16)
- Ubuntu 18.04.2 LTS (CentOS Linux release 7.6.1810)
- Homestead (ConoHa VPS Laravelイメージ)
何がどうなった?
エクセルをアップロードし、 $file->guessExtension()
で拡張子を取得したところ zip
となってしまった。
さらに $file->getMimeType()
をみたところ application/zip
となっていた。
クライアントの情報を取得する関数を使うと、それぞれ $file->guessClientExtension()
が
xlsx
となり、 $file->getClientMimeType()
は application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
となった。
表にまとめると以下。
extension | mime_type | |
---|---|---|
larval | zip | application/zip |
client | xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
エクセルの mime_type なっが。
どうして?
finfo
がうまく判定できないらしい。
ライブラリをガンガン追って行ったところ、Laravel の問題というよりは、内部で使用している Symfony のクラスの内部で使用している finfo
に行きついた。
実際に finfo
で mime_type を判定させてみると、 application/zip
となってしまった。
application/zip
から拡張子を推測すると zip
になるという流れである。
[StackOverflow の関連記事] (https://stackoverflow.com/questions/6595183/docx-file-type-in-php-finfo-file-is-application-zip)
上記記事を読むと、ほかの Microsoft の Office 系ファイルもダメらしい。
どうする?
解決策はいくつかあると思う。
- Symfony の該当クラスは複数の guesser を扱えるので、Laravel で頑張って拡張する
- finfo を修正する
- 素直にクライアントの拡張子を利用する
などなど。
自分は 上記のどれも採用せず、拡張子を推測するための関数をつくった(理由は後述)。
作ったと言っても、Laravel/Symfonyが用意したものと、先人の知恵を組み合わせただけだが。
基本方針
- 拡張子に関してはクライアント(ファイルアップロード者)を信じるという選択は取りたくない
- Laravel を拡張するのはしんどそう
- finfo を修正するのは移植性がない
- 不具合報告があったのはとりあえず excel だけ
参考ソース
public function guessExtension(UploadedFile $file): string
{
$extension = $file->guessExtension();
if ($extension !== 'zip') {
return $extension;
}
$guessedMimeType = $file->getMimeType();
$clientMimeType = $file->getClientMimeType();
if ($guessedMimeType === $clientMimeType) {
return $extension;
}
$map = [
'application/vnd.ms-word.document.macroEnabled.12' => 'docm',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
'application/vnd.ms-powerpoint.template.macroEnabled.12' => 'potm',
'application/vnd.ms-powerpoint.addin.macroEnabled.12' => 'ppam',
'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => 'ppsm',
'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => 'pptm',
'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx',
'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx',
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
'application/vnd.ms-excel.template.macroEnabled.12' => 'xltm',
'application/vnd.ms-excel.addin.macroEnabled.12' => 'xlam',
'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => 'xlsb',
'application/vnd.ms-excel.sheet.macroEnabled.12' => 'xlsm',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
];
return $map[$clientMimeType] ?? $extension;
}
作った後に「 Symfony の同様の力技を行使する guesser を使う手もあったな」などと思ったけど、いろんな mime_type を検討するのはしんどいので、とりあえずこれで。
Discussion