WMV/WMAのMIMEタイプ誤検出をUnicode文字列対応で修正
WMVおよびWMAファイルのマジックバイト検出ロジックをUTF-16エンコーディングに対応させ、PDFやMP3がWMV/WMAとして誤判定されるバグを修正しました。
背景
WMVおよびWMAのマジックバイト検出がASCII文字列パターンを使用していたため、PDFファイルやMP3ファイルが誤ってvideo/x-ms-wmvやaudio/x-ms-wmaと判定されるケースが存在していました。
具体的には、#77で報告されたPDFファイル(内部にASCIIとしてwmv2の文字列を含む)がvideo/x-ms-wmvと誤識別される問題と、#125で報告されたLAMEやffmpegでトランスコードされたMP3ファイル(audio2.mp3)がvideo/x-ms-wmvとして誤識別される問題が存在していました。WMVやWMAのメタデータ文字列(Windows Media Video、wmv2、Windows Media Audioなど)は、実際のASF/WMV/WMAコンテナフォーマットではUTF-16LE(Little Endian)エンコーディングで格納されています。ASCII文字列として検索していた旧来のパターンは、このエンコーディングを考慮していませんでした。
その結果、WMV/WMAとは無関係なファイルがたまたまASCII範囲にこれらの文字列を含む場合に誤検出が発生し、反対に正規のWMV/WMAファイルがUTF-16LEで格納された文字列を検出できないケースも生じていました。
技術的な変更
lib/marcel/tables.rbのマジックバイト定義と、テーブル生成スクリプトscript/generate_tables.rbの2箇所が変更されました。
script/generate_tables.rbでは、unicodeLEおよびunicodeBE型への対応が追加されました。これはshared-mime-infoのマジックデータベースが定義するUnicode文字列型で、従来のstring型と同様の前処理(16進数エスケープの展開)を行った後、Ruby標準のencodeメソッドでUTF-16LE/BEにエンコードし、さらにforce_encoding(Encoding::BINARY)でバイナリ列として扱います。
when 'unicodeLE', 'unicodeBE' # Unicode string types (UTF-16 Little/Big Endian)
value.gsub!(/\A0x([0-9a-f]+)\z/i) { [$1].pack('H*') }
encoding = type == 'unicodeLE' ? Encoding::UTF_16LE : Encoding::UTF_16BE
value = value.encode(encoding).force_encoding(Encoding::BINARY)
lib/marcel/tables.rbでは、video/x-ms-wmvとaudio/x-ms-wmaのマッチパターンが更新されています。変更前はASCII文字列としてそのまま記述されていましたが、変更後はUTF-16LEのバイト列(各文字の後にヌルバイト\000が続く形式)に置き換えられています。
変更前:
['video/x-ms-wmv', [[0..8192, b['Windows Media Video']], [0..8192, b['VC-1 Advanced Profile']], [0..8192, b['wmv2']]]],
['audio/x-ms-wma', [[0..8192, b['Windows Media Audio']]]],
変更後:
['video/x-ms-wmv', [[0..8192, b["W\000i\000n\000d\000o\000w\000s\000 \000M\000e\000d\000i\000a\000 \000V\000i\000d\000e\000o\000"]], [0..8192, b["V\000C\000-\0001\000 \000A\000d\000v\000a\000n\000c\000e\000d\000 \000P\000r\000o\000f\000i\000l\000e\000"]], [0..8192, b["w\000m\000v\0002\000"]]]],
['audio/x-ms-wma', [[0..8192, b["W\000i\000n\000d\000o\000w\000s\000 \000M\000e\000d\000i\000a\000 \000A\000u\000d\000i\000o\000"]]]],
テストフィクスチャとしてtest/fixtures/magic/video/x-ms-wmv/sample.wmv、test/fixtures/magic/audio/x-ms-wma/sample.wma、test/fixtures/magic/audio/mpeg/audio2.mp3、および問題を再現するPDFファイルtest/fixtures/name/application/pdf/wmv.pdfが追加されています。
設計判断
テーブルの事前生成(pre-generation)方式を採用していることが、この変更のポイントです。tables.rbには実行時にエンコード処理を行うコードではなく、生成済みのバイト列が直接書き込まれています。script/generate_tables.rbでUnicode変換ロジックを実装し、その出力結果をコミットする構成になっているため、実行時のオーバーヘッドが発生せず、マッチング処理はバイナリ比較のみで完結します。
また、ASCII文字列パターンからUTF-16LEバイト列への変更は、ASFコンテナフォーマットの仕様に沿った修正です。ASF(Advanced Systems Format)ではメタデータ文字列がUTF-16LEで格納されることが規定されており、今回の変更はその仕様に準拠したものといえます。これにより、WMV/WMAとは無関係なファイルがこれらのMIMEタイプに誤判定されるリスクが大幅に低減されます。
まとめ
ASCII文字列パターンをASFフォーマット仕様に準拠したUTF-16LEバイト列に修正したことで、WMV/WMAの誤検出問題が根本から解消されます。テーブル生成スクリプトレベルでUnicode型を処理する仕組みを導入したことで、今後同様のUnicode文字列マッチングが必要になった場合にも同じアプローチを適用できる基盤が整いました。