Commit 2a0274d1 by Stephen Pike

Prefer the mimemagic gem for content type detection

Prioritize MimeMagic over the `file` binary for content type detection
when it finds a match. Fall back to `file` if MimeMagic can't match
anything.

`file` incorrectly detects Open Office XML files (e.g., xlsx, docx) as
zip since they're implemented as zipped archives of xml files. MimeMagic
detects them properly.
parent 55ce5602
......@@ -57,6 +57,8 @@ require 'paperclip/attachment_registry'
require 'paperclip/filename_cleaner'
require 'paperclip/rails_environment'
require 'mime/types'
require 'mimemagic'
require 'mimemagic/overlay'
require 'logger'
require 'cocaine'
......@@ -137,7 +139,7 @@ module Paperclip
# user.avatar.url # => "/avatars/23/normal_me.png"
# * +keep_old_files+: Keep the existing attachment files (original + resized) from
# being automatically deleted when an attachment is cleared or updated. Defaults to +false+.
# * +preserve_files+: Keep the existing attachment files in all cases, even if the parent
# * +preserve_files+: Keep the existing attachment files in all cases, even if the parent
# record is destroyed. Defaults to +false+.
# * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
# to a command line error. This will override the global setting for this attachment.
......
......@@ -33,7 +33,7 @@ module Paperclip
elsif calculated_type_matches.any?
calculated_type_matches.first
else
type_from_file_command || SENSIBLE_DEFAULT
type_from_file_contents || SENSIBLE_DEFAULT
end.to_s
end
......@@ -54,11 +54,24 @@ module Paperclip
end
def calculated_type_matches
possible_types.select{|content_type| content_type == type_from_file_command }
possible_types.select { |content_type| content_type == type_from_file_contents }
end
def type_from_file_contents
type = begin
type_from_mime_magic || type_from_file_command
rescue Errno::ENOENT => e
Paperclip.log("Error while determining content type: #{e}")
SENSIBLE_DEFAULT
end
end
def type_from_file_command
@type_from_file_command ||= FileCommandContentTypeDetector.new(@filename).detect
end
def type_from_mime_magic
@type_from_mime_magic ||= MimeMagic.by_magic(File.open(@filename)).try(:type)
end
end
end
......@@ -14,19 +14,17 @@ module Paperclip
def type_from_file_command
type = begin
# On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
Paperclip.run("file", "-b --mime :file", :file => @filename)
rescue Cocaine::CommandLineError => e
Paperclip.log("Error while determining content type: #{e}")
SENSIBLE_DEFAULT
end
# On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
Paperclip.run("file", "-b --mime :file", file: @filename)
rescue Cocaine::CommandLineError => e
Paperclip.log("Error while determining content type: #{e}")
SENSIBLE_DEFAULT
end
if type.nil? || type.match(/\(.*?\)/)
type = SENSIBLE_DEFAULT
end
type.split(/[:;\s]+/)[0]
end
end
end
......@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', '>= 3.0.0')
s.add_dependency('cocaine', '~> 0.5.5')
s.add_dependency('mime-types')
s.add_dependency('mimemagic', '0.3.0')
s.add_development_dependency('activerecord', '>= 3.0.0')
s.add_development_dependency('shoulda')
......
require 'spec_helper'
describe Paperclip::ContentTypeDetector do
it 'returns a meaningful content type for open xml spreadsheets' do
file = File.new(fixture_file("empty.xlsx"))
assert_equal "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
Paperclip::ContentTypeDetector.new(file.path).detect
end
it 'gives a sensible default when the name is empty' do
assert_equal "application/octet-stream", Paperclip::ContentTypeDetector.new("").detect
end
......@@ -13,7 +19,7 @@ describe Paperclip::ContentTypeDetector do
it 'returns content type of file if it is an acceptable type' do
MIME::Types.stubs(:type_for).returns([MIME::Type.new('application/mp4'), MIME::Type.new('video/mp4'), MIME::Type.new('audio/mp4')])
Paperclip.stubs(:run).returns("video/mp4")
Paperclip::ContentTypeDetector.any_instance.stubs(:type_from_file_contents).returns("video/mp4")
@filename = "my_file.mp4"
assert_equal "video/mp4", Paperclip::ContentTypeDetector.new(@filename).detect
end
......
......@@ -24,4 +24,3 @@ describe Paperclip::FileCommandContentTypeDetector do
Paperclip::FileCommandContentTypeDetector.new("windows").detect
end
end
......@@ -9,11 +9,12 @@ describe Paperclip::AbstractAdapter do
end
end
context "content type from file command" do
context "content type from file contents" do
before do
@adapter = TestAdapter.new
@adapter.stubs(:path).returns("image.png")
Paperclip.stubs(:run).returns("image/png\n")
Paperclip::ContentTypeDetector.any_instance.stubs(:type_from_mime_magic).returns("image/png")
end
it "returns the content type without newline" do
......
......@@ -73,10 +73,13 @@ describe Paperclip::FileAdapter do
end
end
context "file with content type derived from file command on *nix" do
context "file with content type derived from file contents on *nix" do
before do
MIME::Types.stubs(:type_for).returns([])
Paperclip.stubs(:run).returns("application/vnd.ms-office\n")
Paperclip::ContentTypeDetector.any_instance
.stubs(:type_from_mime_magic).returns("application/vnd.ms-office")
@subject = Paperclip.io_adapters.for(@file)
end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment