`image/bmp;format=compressed` を `image/bmp` へ統一し後方互換性を回復
Marcel 1.2.0 でBMPファイルの検出結果が image/bmp;format=compressed に変更されたことで既存のRailsとの互換性が損なわれていた問題を、MIMEタイプを image/bmp に戻すことで解消しました。
背景
Marcel 1.2.0 はBMPファイルのMIMEタイプとして image/bmp;format=compressed を返すようになりました。これはMIMEタイプの仕様としては正確な表現ですが、多くの既存Railsアプリケーションが image/bmp を前提として動作しており、パラメータ付きの形式が予期せぬ互換性の問題を引き起こしていました。
rails/rails#57398 では、Active Storage の Blob#content_type がこの変更の影響を受けていることが報告されています。image/bmp;format=compressed という値がコンテンツタイプの比較やバリデーションで image/bmp と一致しなくなるため、ファイルアップロードや配信の処理に影響が生じていました。正確さよりも実用上の互換性を優先する判断として、本PRはMIMEタイプをシンプルな image/bmp に戻しています。
技術的な変更
変更は lib/marcel/tables.rb と、そのテーブルを生成するスクリプト script/generate_tables.rb の2ファイルに及んでいます。
lib/marcel/tables.rb ではBMPのマジックバイト定義のMIMEタイプ文字列を直接書き換えています。
変更前:
['image/bmp;format=compressed', [[0, b['BM'], [[26, b["\001\000"], ...]]]]],
変更後:
['image/bmp', [[0, b['BM'], [[26, b["\001\000"], ...]]]]],
ただし、tables.rb は script/generate_tables.rb によって自動生成されるファイルです。そのため、生成スクリプト側にも TYPE_RENAMES という変換テーブルが追加され、MIMEデータベースから取得した image/bmp;format=compressed を image/bmp に置換する仕組みが導入されています。
TYPE_RENAMES = {
"image/bmp;format=compressed" => "image/bmp",
}.freeze
type = TYPE_RENAMES[mime['type']] || mime['type']
この仕組みにより、将来的にMIMEデータベース(shared-mime-info)が再び image/bmp;format=compressed を返してきても、生成されるテーブルは常に image/bmp を使用するようになっています。
テスト側では raw_type ヘルパーメソッド(content_type.split(";").first でパラメータ部分を除去する処理)が削除されました。このヘルパーは image/bmp;format=compressed の問題を吸収するための一時的な措置として機能していましたが、MIMEタイプ自体を修正したことで不要になりました。
設計判断
MIMEタイプのパラメータ(format=compressed)をライブラリ内部で除去するのではなく、ソースとなるMIMEタイプそのものを置き換える方式 が採用されました。
raw_type ヘルパーのように、返却されたMIMEタイプからパラメータを後処理で取り除く方法も考えられます。しかしこのアプローチでは、Marcelを使用するすべての呼び出し側が同様の処理を行う必要があり、問題の根本的な解決にはなりません。今回は生成スクリプトの段階でリネームすることで、Marcelのあらゆる利用箇所が一貫して image/bmp を受け取れるよう設計されています。
TYPE_RENAMES をHashとして定義したことで、将来同様の互換性問題が他のMIMEタイプで発生した場合にも、エントリを追加するだけで対応できる拡張性が確保されています。
まとめ
本PRは、仕様上は正しいが実用上の互換性を損なう image/bmp;format=compressed を、生成スクリプトのリネームテーブルで吸収する変更です。テストヘルパーの削除も含めた一連の修正により、Marcelが image/bmp を一貫して返すことが保証され、既存のRailsアプリケーションへの影響が解消されます。