アップロードされたファイルのMime-TypeをチェックできるGem 「marcel」の使い方

こんにちは!kossyです!




さて、今回はアップロードされたファイルの中身をチェックできるGem 「marcel」の使い方について、
ブログに残してみたいと思います。




GitHub - basecamp/marcel: Find the mime type of files, examining file, filename and declared type



Marcel Gemとは?

公式ドキュメントの説明をGoogle翻訳先生に訳してもらいます。

Marcel attempts to choose the most appropriate content type for a given file by looking at the binary data, the filename, and any declared type (perhaps passed as a request header)

Marcelは、バイナリデータ、ファイル名、および宣言されたタイプ(おそらく要求ヘッダーとして渡される)を調べて、特定のファイルに最も適切なコンテンツタイプを選択しようとします。

これは使い方まで見ないといまいち理解できなさそうです、、、

Marcel::MimeType.for Pathname.new("example.gif")
#  => "image/gif"

File.open "example.gif" do |file|
  Marcel::MimeType.for file
end
#  => "image/gif"

Marcel::MimeType.for Pathname.new("unrecognisable-data"), name: "example.pdf"
#  => "application/pdf"

Marcel::MimeType.for extension: ".pdf"
#  => "application/pdf"

Marcel::MimeType.for Pathname.new("unrecognisable-data"), name: "example", declared_type: "image/png"
#  => "image/png"

Marcel::MimeType.for StringIO.new(File.read "unrecognisable-data")
#  => "application/octet-stream"

forメソッドにファイルのパスを渡すと、そのファイルのMime-Typeを返すみたいですね。

さらにドキュメントを読んでみます。

By preference, the magic number data in any passed in file is used to determine the type. If this doesn't work, it uses the type gleaned from the filename, extension, and finally the declared type.

If no valid type is found in any of these, "application/octet-stream" is returned.

Some types aren't easily recognised solely by magic number data. For example Adobe Illustrator files have the same magic number as PDFs (and can usually even be viewed in PDF viewers!).

For these types, Marcel uses both the magic number data and the file name to work out the type:

優先的に、渡されたファイルのマジックナンバーデータを使用してタイプが決定されます。これが機能しない場合は、ファイル名、拡張子、最後に宣言された型から収集した型を使用します。

これらのいずれにも有効なタイプが見つからない場合は、「application / octet-stream」が返されます。

一部のタイプは、マジックナンバーデータだけでは簡単に認識できません。たとえば、Adobe IllustratorファイルのマジックナンバーはPDFと同じです(通常はPDFビューアで表示することもできます)。

これらのタイプの場合、Marcelはマジックナンバーデータとファイル名の両方を使用してタイプを計算します。

だいぶわかってきました。

例えばファイルアップロードの機能を実装する場合、拡張子に応じて制限をかけることはよくあると思うのですが、
拡張子だけで制限をかけても不十分で、きちんとファイルの中身まで見に行って制限をする必要がある場合に、Mime-Typeを見てくれるMarcelが役に立つわけですね。

# 以下は実装イメージです

mime_type = Marcel::MimeType.for(params[:upload_file])

if ["image/jpeg", "image/png", "application/pdf"].include?(mime_type)
  # 保存する処理
else
  raise "UnPermittedMimeTypeError"
end

ちなみにMarcelはrails newするとデフォルトでインストールされるようになっています。

    activestorage (6.0.3.5)
      actionpack (= 6.0.3.5)
      activejob (= 6.0.3.5)
      activerecord (= 6.0.3.5)
      marcel (~> 0.3.1)

ActiveStorageが依存しているようです。
RubyDoc公式にも記載がありました。

rubygems.org

ソースコードだとこの辺りですね。

rails/blob.rb at main · rails/rails · GitHub

    def extract_content_type(io)
      Marcel::MimeType.for io, name: filename.to_s, declared_type: content_type
    end

あとはここ

rails/identifiable.rb at 291a3d2ef29a3842d1156ada7526f4ee60dd2b59 · rails/rails · GitHub

    def identify_content_type
      Marcel::MimeType.for download_identifiable_chunk, name: filename.to_s, declared_type: content_type
    end

どちらもprivateメソッドになっているので、普通に使う分にはMarcelを使っていることを意識することはなさそうです。




勉強になりました。