SVG検出ロジックの強化:誤検出を防ぐマジックバイト戦略
marcelのSVG検出において、ファイル先頭付近に <svg が含まれるだけで誤判定されていた問題が修正されました。検出ルールを構造的なパターンに絞り込むことで、JSやCSV、MP3が image/svg+xml と誤認識されるケースを防ぎます。
背景
SVGコンテンツを内包するファイルが、意図せずSVGとして誤検出される問題が複数のIssueで報告されていました。いずれも、<svg タグがファイル内容の一部として含まれているものの、ファイル自体はSVGではないというケースです。
具体的には以下の3パターンが報告されています:
-
#107: SVGを文字列リテラルとして含むJavaScriptファイルが
image/svg+xmlと判定される -
#111: SVGタグを含むCSVファイルが
image/svg+xmlと判定される -
#125: MP3ファイルが
image/svg+xmlと判定される
これらの根本原因は、旧来の検出ルールがファイル先頭4096バイト以内に <svg が存在するだけでSVGと判断していた点にあります。<svg はSVGファイルの必要条件ではあっても十分条件ではなく、他形式のファイルにも出現しうる文字列です。
技術的な変更
旧来の単一ルール(先頭4096バイト以内に <svg)を、SVGファイルの実際の構造に基づく複数のルールに置き換えました。data/custom.xml と lib/marcel/tables.rb の両方が更新されています。
変更前のルール(lib/marcel/tables.rb):
['image/svg+xml', [[0..4096, b['<svg']]]],
変更後のルール:
['image/svg+xml', [[0, b['<svg']]]],
# ...
['image/svg+xml', [[0, b['<?xml'], [[25..512, b['<svg']]]]]],
# ...
['image/svg+xml', [[0, b['<!DOCTYPE svg'], [[25..1024, b['<svg']]]]]],
# ...
['image/svg+xml', [[0, b['<!--'], [[4..1024, b['<svg']]]]]],
# ...
['image/svg+xml', [[0, b[' <svg']], [0, b["\n<svg"]], [0, b["\r<svg"]], [0, b["\r\n<svg"]], [0, b["\t<svg"]]]],
新ルールは、SVGファイルが実際に取りうる先頭パターンを列挙する方式です。優先度付きで整理すると以下の5パターンになります:
-
priority 80: ファイル先頭が即
<svgで始まる(最もシンプルなSVG) -
priority 75:
<?xml宣言で始まり、25〜512バイト以内に<svgが出現する -
priority 70:
<!DOCTYPE svgで始まり、25〜1024バイト以内に<svgが出現する -
priority 65:
<!--コメントで始まり、4〜1024バイト以内に<svgが出現する -
priority 60: スペース・改行・タブ等の空白文字の直後に
<svgが来る
旧ルールとの最大の違いは、<svg の出現位置を「先頭固定またはSVG固有の前置詞の直後」に限定した点です。JS文字列リテラル内やCSVフィールド内に埋め込まれた <svg は、これらのパターンにマッチしないため誤検出されなくなります。
テスト用フィクスチャとして test/fixtures/name/text/csv/svg.csv、test/fixtures/name/text/javascript/javascript.js、test/fixtures/magic/audio/mpeg/audio1.mp3 も追加されており、報告された3種類の誤検出ケースがそれぞれ回帰テストとして網羅されています。
設計判断
<svg の出現をそのまま検出するアプローチを維持しつつ、前置コンテキストで絞り込む方式が採用されました。
代替案として正規表現による柔軟なマッチングも考えられますが、marcelのマジックバイト検出はXFreeDesktop仕様に準拠した固定文字列マッチングを基本としています。本PRはその制約の中で、入れ子マッチ(親パターンにマッチした場合のみ子パターンを評価する仕組み)を活用して文脈依存の検出を実現しています。<?xml の後に <svg が来るという入れ子ルールは、XMLベースのSVGの構造を直接モデル化したものです。
また、ホワイトスペースパターン(スペース、\n、\r、\r\n、\t)を個別に列挙しているのは、正規表現が使えない固定文字列マッチングの制約への対応です。可読性よりも仕様への準拠を優先した選択といえます。
まとめ
本PRは、「<svg が存在する」という必要条件を「SVGファイルとして妥当な先頭構造を持つ」という十分条件に近い条件へと精緻化した変更です。入れ子マッチによる文脈依存検出を活用することで、マジックバイト検出の設計原則を維持しながら誤検出の主要ケースを排除しています。