HTML検出ロジックの改善:ファイル先頭の厳密なマッチングで誤検知を解消

rails/marcel

JSONファイル内にHTMLタグが含まれる場合に text/html と誤検知される問題を修正しました。マジックバイトの照合をファイル先頭に限定することで、コンテンツの意味的な判定精度が向上しています。

背景

Issue #79 で報告されたように、{"a": "<html"} というJSONファイルがv1.0.0以降で text/html と判定されるという回帰バグが存在していました。v0.3.3では同じファイルを正しく application/json と判定できていたため、バージョンアップ時に導入された挙動の変化が原因でした。

Issueの再現コードによると、問題は検出範囲の設定にあります。変更前の定義では [0..64, "<html"] のように先頭64バイト以内に <html 文字列が含まれれば text/html と判定していました。JSONの値フィールドやCSVのセル内にHTMLタグが含まれるだけで誤検知が発生するのは、この広すぎる照合範囲が原因です。

この問題はファイルの拡張子情報が与えられない場合や、MIMEタイプをコンテンツのみから推論するケースで顕在化していました。

技術的な変更

lib/marcel/mime_type/definitions.rbtext/html 定義が全面的に書き直され、マジックバイトの照合範囲がオフセット 0(ファイル先頭)に限定されました。

変更前:

Marcel::MimeType.extend "text/html", magic: [[0..64, "<!DOCTYPE HTML"], [0..64, "<!DOCTYPE html"], [0..64, "<!doctype HTML"], [0..64, "<!doctype html"]]

変更後:

Marcel::Magic.remove("text/html")
Marcel::MimeType.extend "text/html", 
  extensions: %w( html htm ),
  magic: [
    [0, "<!DOCTYPE html"],
    [0, "<!DOCTYPE HTML"], 
    [0, "<!doctype html"],
    [0, "<!doctype HTML"],
    [0, "<html"],
    [0, "<HTML"],
    [0, " <!DOCTYPE html"],
    [0, "\n<!DOCTYPE html"],
    [0, "\r<!DOCTYPE html"], 
    [0, "\r\n<!DOCTYPE html"],
    [0, "\t<!DOCTYPE html"],
    [0, " <html"],
    [0, "\n<html"],
    [0, "\r<html"],
    [0, "\r\n<html"],
    [0, "\t<html"]
  ]

変更点は3点あります。第一に、Marcel::Magic.remove("text/html") を呼び出して既存の定義を完全にリセットしてから再定義しています。これにより、ベースとなるmagicデータベースのエントリと独自定義が混在しないようにしています。第二に、照合オフセットを 0..64 の範囲から 0(完全一致)に絞り込むことで、ファイル先頭以外に現れる <html タグを無視するようにしました。第三に、extensions: %w( html htm ) を明示的に追加し、拡張子ベースの判定も同じ定義エントリで管理するようになっています。

テストフィクスチャも更新されています。test/fixtures/name/application/json/json.json には "tag": "<html></html>" フィールドが追加され、test/fixtures/name/text/csv/csv.csv にも <html> タグを含むセルが追加されました。これらはいずれも、HTMLタグを含んでいても拡張子で正しくJSONやCSVと判定されるべきケースの回帰テストとして機能します。

設計判断

照合範囲を「先頭Nバイト以内」から「先頭完全一致」に変更するという方針が採用されました。

マジックバイト検出の本来の目的は、ファイルの先頭に現れるシグネチャを識別することです。HTMLファイルは <!DOCTYPE html><html> でドキュメントが始まるため、先頭以外に現れる場合はHTMLドキュメントとは見なさないという判断は理にかなっています。一方で、空白・タブ・改行・CRLFのバリエーションを個別のエントリとして列挙しているのは、先頭にBOM相当のホワイトスペースが入るケースへの対応です。

また、<html<!DOCTYPE html の両パターンをゼロオフセットで定義することで、DOCTYPE宣言なしのHTMLファイルも先頭タグから検出できるようにしています。変更前の定義にはこのパターンが 0..64 の範囲で含まれていましたが、今回の変更でオフセット 0 に厳格化されています。

まとめ

この変更は、マジックバイト照合の原則「シグネチャはファイル先頭にある」を再徹底したものです。JSONやCSVのようにHTMLタグを値として含み得るフォーマットとの共存において、拡張子ヒントがない場合でも誤検知しないための最小限の変更となっています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
c45fc908

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「リード文(総論)→ 背景・技術詳細・設計判断(各論)→ まとめ(結論)」という理想的な3部構成が明確に適用されています。各セクションが論理的に配置されており、非常に分かりやすいです。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きのシンタックスハイライト(```ruby:path```)と、PR・IssueへのGitHubリンク記法([#130])がガイドラインに沿って正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

「マジックバイト」「オフセット」「MIMEタイプ」といった専門用語を適切に使用しており、専門知識を持つエンジニアという対象読者に合致した内容です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論から始まる構成、各段落がトピックセンテンスで始まる構成になっており、非常に読みやすいです。1段落1トピックの原則も守られています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

Diffで示されたコードの変更(`definitions.rb`)が「変更前」「変更後」として正確に引用されています。テストフィクスチャの変更点に関する説明もDiff内容と一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「マジックバイト」「オフセット」「シグネチャ」「回帰バグ」などの技術用語が、文脈に応じて正確かつ効果的に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

ファイル先頭64バイト以内という緩い条件が誤検知の原因であったこと、それをオフセット0に限定することで解決したという説明は、Diffの内容と完全に一致しており、技術的に正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(Issue #79の報告内容、v1.0.0での回帰、テストの更新など)は、提供されたPR情報(Description、Diff、リンク先Issue)で裏付けられており、ハルシネーションは見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#130)、Issue番号(#79)、バージョン番号(v1.0.0, v0.3.3)など、記事中のすべての数値・固有名詞はPR情報と一致しており、正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「HTML検出ロジックの改善:ファイル先頭の厳密なマッチングで誤検知を解消」は、PRのタイトル「improve HTML detection」の内容をより具体的に、かつ正確に表現しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事で言及されているバージョン番号はPRで参照されているIssueに記載されている情報であり、文脈上必要な情報です。それ以外の無関係な外部知識は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「存在していました」「修正しました」など、過去形と現在形を適切に使い分けており、時間的な表現に誤りや歪曲は見られません。