Office2007ファイル(docx,xlsx,pptx)のMIME-Typeを正しく判定する方法

アップローダーなどを設置してOffice2007系のWord(.docx) やExcel(xlsx), PowerPoint(pptx)かどうかを判断するのは以外と大変です。

理由:全て「zipファイル」と判断されるから

Office2007以降からファイルのフォーマットがOffice Open XML(OOXML) に変更されたのですが、これは実際はZIPファイルになっていて、PHPのFileInfoモジュールmime_content_type() 関数(公式には非推奨)などを利用しても、「application/zip」や「application/x-zip」と判断されてしまいます。

正しく判定するには、ZIP展開して[Content_Types].xml を見る

正しく判定するにはどうするかというと「File Signatures」にはこう書いてあります。

~Office2003以前まではバイト列を調べる判定方法がありましたが、それが使えないことを言及した上で~

DOC, PPT, XLS にあったようなサブヘッダー(バイト列)はありません。
これらDOCX, PPTX, XLSX を判定するためには、これらOOXMLファイルの拡張子を
「.zip」に変更して解凍し、[Content_Types].xml というファイルの中身を調べます。
その中で「<Override PartName=/(tag)/」と書いてある箇所を探しましょう。
この(tag)の部分に「word」「ppt」「xl」といった文字列があるので、それで判定します。

つまり、こういうことですね。

見つかった文字列 ファイル形式
<Override PartName=/word/ Wordファイル(docx)
<Override PartName=/xl/ Excelファイル(xlsx)
<Override PartName=/ppt/ PowerPointファイル(pptx)

ということで、ちょっと面倒ではありますが、アップロードされたファイルをZIP解凍して調べることで判定できることが分かりました。

UNZIPコマンドで[Content_Types].xml を調べてみよう

ここからは主にPHPで記述していきますが、PerlやJavaでも基本的には同じでしょう。

解凍方法としてUNIX系OSでは大抵実装されているUNZIPコマンドを利用します。

PHP5.2以降ではZipArchiveモジュールが利用できます。 (PHP5.2以前のバージョンでも手動でインストールすることは可能です) ZipArchiveモジュールが利用できる環境にある場合はぜひ利用しましょう。

OOXMLかどうかを判断する

まずは、アップロードされたファイルがOOXMLかどうかを判定しないといけないので、通常の方法でMIME-Typeを調べます。 この方法について詳しくは「PHPでMIME-Typeを判定する方法」を参照ください。

$path = $_FILES['myfile']['tmp_name'];
$mime = shell_exec('file -bi '.escapeshellcmd($path));
$mime = trim($mime);
$mime = preg_replace("/ [^ ]*/", "", $mime);

アップロードされたファイルがOOXMLであれば、この$mime変数に「application/zip」あるいは「application/x-zip」が設定されますので、if文で分岐させておくと良いでしょう。

// 正規表現で判定
if (preg_match("/^application-(?:x-|)zip$/", $mime) {
    // (OOXML用の処理)
}

アップロードファイルを解凍して[Content_Types].xml を見る方法

さて、ここからが本番です。 上記のようにOOXMLであることまで分かったら次の順序で[Content_Types].xml 内を調べましょう。

  1. アップロードファイルを一時フォルダにZIPファイルとしてコピー
  2. ZIPファイルの中から[Content_Types].xml を取り出し
  3. [Content_Types].xml内の文字列を調べてMIME-Typeを判定
  4. [Content_Types].xml を削除
  5. コピーしたZIPファイルを削除

これをPHPで書くと以下のようになります。

//ooxmlの判定文字列と拡張子の対応データ
$ooxmltag2ext = array("word"=>"docx","xl"=>"xlsx","ppt"=>"pptx");
$path = $_FILES['myfile']['tmp_name'];  //アップロードファイルの保存パス
$tmpDir  = '/tmp/';  //一時フォルダ
$tmpZip	 = 'ooxml_'.date("YmdHis").'.zip';  //ZIPファイル
$tmpPath = "$tmpDir$tmpZip";  //ZIPファイルの保存パス
$tmpXML  = "$tmpDir/[Content_Types].xml";  //[Content_Type].xmlファイルの保存パス
//アップロードファイルをコピーし[Content_Types].xmlを抽出後、中身を判定
copy($path, $tmpPath);
shell_exec("unzip $tmpPath [Content_Types].xml -d $tmpDir");
if (preg_match("/.*?<Override PartName="/([^/]+)/.*/m", file_get_contents($tmpXML), $match)) {
    $t = $match[1];
    if (array_key_exists($t, $ooxmltag2ext)) $extension = $ooxmltag2ext[$t];
}
unlink($tmpXML);
unlink($tmpPath);

これで$extension変数に「docx」「xlsx」「pptx」のいずれかが設定されるので、正しいMIME-Typeが設定された配列からMIME-Typeを設定しましょう。

$ext2mimes = array(
    "docx"=>"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    "xlsx"=>"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    "pptx"=>"application/vnd.openxmlformats-officedocument.presentationml.presentation",
);
$mime = $ext2mimes[$extension];

おわりに

いかがでしたか? 面倒くさい場合は拡張子とZIPかどうかだけでも大丈夫な気もしますが、ZIPファイルは偽装ファイルとしても良く利用される形式なので正しく判定することを心がけてくださいね。

このページをシェアする

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

2011-09-02