Ruby
Rails
carrierwave

CarrierWaveでファイルの内容をもとにcontent-typeの判定を行う

More than 3 years have passed since last update.


CarrierWaveでファイルの内容をもとにcontent-typeの判定を行う


拡張子をもとに判定することの問題

ファイルのアップロードを行うアプリを実装する際に、ファイルのcontent-typeを判定したいことがある。

もっとも手軽にcontent-typeの判定を行う方法としては拡張子での判定がある。CarrierWaveではuploaderクラス内でextension_white_listメソッドを上書きすることで、ホワイトリスト形式でアップロードを許可する。


app/uploaders/avatar_uploader.rb

  # 〜抜粋〜

# 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の簡単な使用方法は以下の通りである。

まず、インストールを行う。


Gemfile

gem 'ruby-filemagic'


bundle install

続いて、rails console上で適当なファイルに対してcontent-typeの判定を行ってみる。


at_console.rb

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の使用方法(の一例)は以下の通りである。

まず、インストールを行う。


Gemfile

gem 'carrierwave-magic'


bundle install

続いてアップローダでcarrierwave-magicを利用できるように設定する。


app/uploaders/avatar_uploader.rb

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にファイル内容をもとにチェックした値が入るようになった。

あとはその値をもとに任意のバリデーションを行う。


app/models/user.rb

  # 〜抜粋〜

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を確認したいときなどにも利用できる。