CarrierWaveでファイルの内容をもとにcontent-typeの判定を行う
拡張子をもとに判定することの問題
ファイルのアップロードを行うアプリを実装する際に、ファイルのcontent-typeを判定したいことがある。
もっとも手軽にcontent-typeの判定を行う方法としては拡張子での判定がある。CarrierWaveではuploaderクラス内でextension_white_listメソッドを上書きすることで、ホワイトリスト形式でアップロードを許可する。
# 〜抜粋〜
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_white_list
%w(jpg jpeg gif png)
end
しかし、拡張子だけをもとに判定した場合は内容はPDFだけど拡張子が.jpgになっているようなファイルの内容と拡張子に相違があるファイルがバリデーションを通過してアップロードできてしまう。
このようなケースを防ぎたい時は、ファイルの内容をもとにcontent-typeを判定する必要がある。
ファイルの内容をもとに判定を行う方法
"ruby-filemagic"というGemがある。このGemではファイルの内容をもとにcontent-typeの判定を行うことができる。
ruby-filemagicの簡単な使用方法は以下の通りである。
まず、インストールを行う。
gem 'ruby-filemagic'
bundle install
続いて、rails console
上で適当なファイルに対してcontent-typeの判定を行ってみる。
require "filemagic"
FileMagic.new(FileMagic::MAGIC_MIME).file(File.join("/path/to/files/actually_pdf.jpg")) # ※actually_pdf.jpgは、内容はPDFだけど拡張子が.jpgのファイル
=> "application/pdf; charset=binary"
このように、拡張子にとらわれずファイルの内容によってcontent-typeが判定できる。
このruby-filemagicをCarrierWaveで利用できるようにするGemで"carrierwave-magic"というのがある。これを利用すると掲題の目的を達成することができる。
carrierwave-magicの使用方法(の一例)は以下の通りである。
まず、インストールを行う。
gem 'carrierwave-magic'
bundle install
続いてアップローダでcarrierwave-magicを利用できるように設定する。
class AvatarUploader < CarrierWave::Uploader::Base
include CarrierWave::Magic
process :set_magic_content_type => [true]
# 〜以下省略〜
end
基本的にはcarrierwave-magicのREADMEにあるusageに従った形だが、第一引数をtrueにすることで必ずファイル内容をもとにcontent_typeの判定が行われるようになる(※詳しくはcarrierwave-magicのソースを参照)。
これでAvatarUploaderのインスタンスではcontent_typeにファイル内容をもとにチェックした値が入るようになった。
あとはその値をもとに任意のバリデーションを行う。
# 〜抜粋〜
mount_uploader :avatar, AvatarUploader
validate :avatar_valid?, :if => Proc.new{ |user| user.avatar_changed? && user.errors[:avatar].blank? }
def avatar_valid?
if avatar.file.content_type != "image/jpeg"
errors.add(:avatar, "不正なファイルが添付されています。")
end
end
また、この方法は拡張子がついていないファイルのcontent-typeを確認したいときなどにも利用できる。