file_validators 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +32 -0
  4. data/.tool-versions +1 -0
  5. data/.travis.yml +43 -7
  6. data/Appraisals +13 -13
  7. data/CHANGELOG.md +31 -0
  8. data/Gemfile +2 -0
  9. data/README.md +26 -18
  10. data/Rakefile +3 -1
  11. data/file_validators.gemspec +13 -7
  12. data/gemfiles/activemodel_3.2.gemfile +2 -1
  13. data/gemfiles/activemodel_4.0.gemfile +2 -1
  14. data/gemfiles/{activemodel_4.2.gemfile → activemodel_5.0.gemfile} +1 -1
  15. data/gemfiles/{activemodel_4.1.gemfile → activemodel_6.0.gemfile} +1 -1
  16. data/gemfiles/{activemodel_3.0.gemfile → activemodel_6.1.gemfile} +1 -1
  17. data/lib/file_validators.rb +6 -7
  18. data/lib/file_validators/error.rb +6 -0
  19. data/lib/file_validators/mime_type_analyzer.rb +106 -0
  20. data/lib/file_validators/validators/file_content_type_validator.rb +37 -33
  21. data/lib/file_validators/validators/file_size_validator.rb +62 -19
  22. data/lib/file_validators/version.rb +3 -1
  23. data/spec/integration/combined_validators_integration_spec.rb +3 -1
  24. data/spec/integration/file_content_type_validation_integration_spec.rb +117 -11
  25. data/spec/integration/file_size_validator_integration_spec.rb +100 -10
  26. data/spec/lib/file_validators/mime_type_analyzer_spec.rb +139 -0
  27. data/spec/lib/file_validators/validators/file_content_type_validator_spec.rb +90 -32
  28. data/spec/lib/file_validators/validators/file_size_validator_spec.rb +84 -30
  29. data/spec/spec_helper.rb +5 -0
  30. data/spec/support/fakeio.rb +17 -0
  31. data/spec/support/helpers.rb +7 -0
  32. data/spec/support/matchers/allow_content_type.rb +2 -0
  33. data/spec/support/matchers/allow_file_size.rb +2 -0
  34. metadata +86 -28
  35. data/CHANGELOG.mod +0 -6
  36. data/gemfiles/activemodel_3.1.gemfile +0 -8
  37. data/lib/file_validators/utils/content_type_detector.rb +0 -64
  38. data/lib/file_validators/utils/media_type_spoof_detector.rb +0 -46
  39. data/spec/lib/file_validators/utils/content_type_detector_spec.rb +0 -27
  40. data/spec/lib/file_validators/utils/media_type_spoof_detector_spec.rb +0 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 451e981eb130a49dc6c51834bd7ed4dd30652c9a
4
- data.tar.gz: e6b28941ccf9ced999b0e201ad17738efb8bc767
2
+ SHA256:
3
+ metadata.gz: 3a55a08bc74303b69e964f61e3726e8ee00278c4cd0cd6fc217691c85114bcfb
4
+ data.tar.gz: be3b6d628de09d262a97abf3099ff66fd2e89dd27eec3030523a7f36c82e17df
5
5
  SHA512:
6
- metadata.gz: 8c40fa1dae6faf187cc4d3ba77db40691b9bfe58fe9fa99bc88b9fc36477b49ef06bd360ffa8a8af4e2d6f4d56421259b9d9623ab3732a06883b8e64c3f57eb4
7
- data.tar.gz: 4c6aed01f6c15c42c3f917dcdbbbb4c28907ed662a16f823e33d7416005122e159f2504d4991f5fd4b7b20405b332566a1d52137164f224ce5c1504df1c45407
6
+ metadata.gz: cf5b75fb78eaf84f42abef75255572b1917012fbdbdd70d771da14c366619677fb69f4c45d688b8851d5b99dbba44ccd08da6c7025c3819c40259e23502f5c61
7
+ data.tar.gz: 92e9c49f94d8cbc1c2f8aa7425527e84555e94d25c12b6ca1b51e4d5e51f765defb274264996174e6757d8606270f5513232be64d234717075abab8680d8f7c9
data/.gitignore CHANGED
@@ -11,3 +11,5 @@ gemfiles/*.lock
11
11
  coverage/
12
12
  /.idea
13
13
  .ruby-version
14
+ .agignore
15
+ tags
@@ -0,0 +1,32 @@
1
+ Bundler/OrderedGems:
2
+ Enabled: false
3
+
4
+ Style/Documentation:
5
+ Enabled: false
6
+
7
+ Style/MissingRespondToMissing:
8
+ Enabled: false
9
+
10
+ Style/CaseEquality:
11
+ Enabled: false
12
+
13
+ Style/GuardClause:
14
+ Enabled: false
15
+
16
+ Style/RegexpLiteral:
17
+ Enabled: false
18
+
19
+ Style/Next:
20
+ Enabled: false
21
+
22
+ Metrics/LineLength:
23
+ Max: 110
24
+
25
+ Metrics/ModuleLength:
26
+ Enabled: false
27
+
28
+ Metrics/BlockLength:
29
+ Enabled: false
30
+
31
+ Metrics/MethodLength:
32
+ Enabled: false
@@ -0,0 +1 @@
1
+ ruby 2.3.1
@@ -1,19 +1,55 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 2.0
5
4
  - 2.2.3
5
+ - 2.5.0
6
+ - 3.0.0
6
7
  - ruby-head
7
- - jruby-9.0.4.0
8
+ - jruby-9.1.7.0
9
+ - jruby-9.2.13.0
8
10
 
9
11
  gemfile:
10
- - gemfiles/activemodel_4.2.gemfile
11
- - gemfiles/activemodel_4.1.gemfile
12
+ - gemfiles/activemodel_6.1.gemfile
13
+ - gemfiles/activemodel_6.0.gemfile
14
+ - gemfiles/activemodel_5.0.gemfile
12
15
  - gemfiles/activemodel_4.0.gemfile
13
16
  - gemfiles/activemodel_3.2.gemfile
14
- - gemfiles/activemodel_3.1.gemfile
15
- - gemfiles/activemodel_3.0.gemfile
16
17
 
17
18
  matrix:
19
+ exclude:
20
+ - rvm: 2.2.3
21
+ gemfile: gemfiles/activemodel_6.0.gemfile
22
+ - rvm: 2.2.3
23
+ gemfile: gemfiles/activemodel_6.1.gemfile
24
+
25
+ - rvm: 2.5.0
26
+ gemfile: gemfiles/activemodel_3.2.gemfile
27
+ - rvm: 2.5.0
28
+ gemfile: gemfiles/activemodel_4.0.gemfile
29
+
30
+ - rvm: 3.0.0
31
+ gemfile: gemfiles/activemodel_3.2.gemfile
32
+ - rvm: 3.0.0
33
+ gemfile: gemfiles/activemodel_4.0.gemfile
34
+ - rvm: 3.0.0
35
+ gemfile: gemfiles/activemodel_5.0.gemfile
36
+
37
+ - rvm: ruby-head
38
+ gemfile: gemfiles/activemodel_3.2.gemfile
39
+ - rvm: ruby-head
40
+ gemfile: gemfiles/activemodel_4.0.gemfile
41
+ - rvm: ruby-head
42
+ gemfile: gemfiles/activemodel_5.0.gemfile
43
+
44
+ - rvm: jruby-9.1.7.0
45
+ gemfile: gemfiles/activemodel_6.0.gemfile
46
+ - rvm: jruby-9.1.7.0
47
+ gemfile: gemfiles/activemodel_6.1.gemfile
48
+
49
+ - rvm: jruby-9.2.13.0
50
+ gemfile: gemfiles/activemodel_3.2.gemfile
51
+ - rvm: jruby-9.2.13.0
52
+ gemfile: gemfiles/activemodel_4.0.gemfile
53
+
18
54
  allow_failures:
19
- - rvm: ruby-head
55
+ - rvm: ruby-head
data/Appraisals CHANGED
@@ -1,23 +1,23 @@
1
- appraise 'activemodel-3.0' do
2
- gem 'activemodel', '3.0.20'
3
- end
4
-
5
- appraise 'activemodel-3.1' do
6
- gem 'activemodel', '3.1.12'
7
- end
1
+ # frozen_string_literal: true
8
2
 
9
3
  appraise 'activemodel-3.2' do
10
- gem 'activemodel', '3.2.18'
4
+ gem 'activemodel', '3.2.22.5'
5
+ gem 'rack', '1.6.5'
11
6
  end
12
7
 
13
8
  appraise 'activemodel-4.0' do
14
- gem 'activemodel', '4.0.10'
9
+ gem 'activemodel', '4.0.13'
10
+ gem 'rack', '1.6.5'
11
+ end
12
+
13
+ appraise 'activemodel-5.0' do
14
+ gem 'activemodel', '5.0.1'
15
15
  end
16
16
 
17
- appraise 'activemodel-4.1' do
18
- gem 'activemodel', '4.1.6'
17
+ appraise 'activemodel-6.0' do
18
+ gem 'activemodel', '6.0.3'
19
19
  end
20
20
 
21
- appraise 'activemodel-4.2' do
22
- gem 'activemodel', '4.2.3'
21
+ appraise 'activemodel-6.1' do
22
+ gem 'activemodel', '6.1.0'
23
23
  end
@@ -0,0 +1,31 @@
1
+ # 3.0.0
2
+
3
+ * [#32](https://github.com/musaffa/file_validators/pull/32) Removed cocaine/terrapin. Added options for choosing MIME type analyzers with `:tool` option.
4
+ * [#40](https://github.com/musaffa/file_validators/pull/40) Added Support for Ruby 3.
5
+ * Rubocop style guide
6
+
7
+ # 3.0.0.beta2
8
+
9
+ * [#32](https://github.com/musaffa/file_validators/pull/32) Removed terrapin. Added options for choosing MIME type analyzers with `:tool` option.
10
+
11
+ # 3.0.0.beta1
12
+
13
+ * [#29](https://github.com/musaffa/file_validators/pull/29) Upgrade cocaine to terrapin
14
+ * Rubocop style guide
15
+
16
+ # 2.3.0
17
+
18
+ * [#19](https://github.com/musaffa/file_validators/pull/19) Return false with blank size
19
+ * [#27](https://github.com/musaffa/file_validators/pull/27) Fix file size validator for ActiveStorage
20
+
21
+ # 2.2.0-beta.1
22
+
23
+ * [#17](https://github.com/musaffa/file_validators/pull/17) Now Supports multiple file uploads
24
+ * As activemodel 3.0 and 3.1 doesn't support `added?` method on the Errors class, the support for both of them have been deprecated in this release.
25
+
26
+ # 2.1.0
27
+
28
+ * Use autoload for lazy loading of libraries.
29
+ * Media type spoof valiation is moved to content type detector.
30
+ * `spoofed_file_media_type` message isn't needed anymore.
31
+ * Logger info and warning is added.
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Declare your gem's dependencies in file_validators.gemspec.
data/README.md CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/file_validators.svg)](http://badge.fury.io/rb/file_validators)
4
4
  [![Build Status](https://travis-ci.org/musaffa/file_validators.svg)](https://travis-ci.org/musaffa/file_validators)
5
- [![Dependency Status](https://gemnasium.com/musaffa/file_validators.svg)](https://gemnasium.com/musaffa/file_validators)
6
5
  [![Coverage Status](https://coveralls.io/repos/musaffa/file_validators/badge.png)](https://coveralls.io/r/musaffa/file_validators)
7
6
  [![Code Climate](https://codeclimate.com/github/musaffa/file_validators/badges/gpa.svg)](https://codeclimate.com/github/musaffa/file_validators)
8
7
  [![Inline docs](http://inch-ci.org/github/musaffa/file_validators.svg)](http://inch-ci.org/github/musaffa/file_validators)
@@ -12,8 +11,11 @@ Any module that uses ActiveModel, for example ActiveRecord, can use these file v
12
11
 
13
12
  ## Support
14
13
 
15
- * ActiveModel versions: 3 and 4.
16
- * Rails versions: 3 and 4.
14
+ * ActiveModel versions: 3.2, 4, 5 and 6.
15
+ * Rails versions: 3.2, 4, 5 and 6.
16
+
17
+ As of version `2.2`, activemodel 3.0 and 3.1 will no longer be supported.
18
+ For activemodel 3.0 and 3.1, please use file_validators version `<= 2.1`.
17
19
 
18
20
  It has been tested to work with Carrierwave, Paperclip, Dragonfly, Refile etc file uploading solutions.
19
21
  Validations works both before and after uploads.
@@ -36,7 +38,7 @@ class Profile
36
38
 
37
39
  attr_accessor :avatar
38
40
  validates :avatar, file_size: { less_than_or_equal_to: 100.kilobytes },
39
- file_content_type: { allow: ['image/jpeg', 'image/png'] }
41
+ file_content_type: { allow: ['image/jpeg', 'image/png'] }
40
42
  end
41
43
  ```
42
44
  ActiveRecord example:
@@ -64,23 +66,23 @@ validates :avatar, file_size: { less_than: 2.gigabytes }
64
66
  ```
65
67
  * `less_than_or_equal_to`: Less than or equal to a number in bytes or a proc that returns a number
66
68
  ```ruby
67
- validates :avatar, file_size: { less_than_or_equal_to: 50.bytes }
69
+ validates :avatar, file_size: { less_than_or_equal_to: 50.bytes }
68
70
  ```
69
71
  * `greater_than`: greater than a number in bytes or a proc that returns a number
70
72
  ```ruby
71
- validates :avatar, file_size: { greater_than: 1.byte }
73
+ validates :avatar, file_size: { greater_than: 1.byte }
72
74
  ```
73
75
  * `greater_than_or_equal_to`: Greater than or equal to a number in bytes or a proc that returns a number
74
76
  ```ruby
75
- validates :avatar, file_size: { greater_than_or_equal_to: 50.bytes }
77
+ validates :avatar, file_size: { greater_than_or_equal_to: 50.bytes }
76
78
  ```
77
- * `message`: Error message to display. With all the options above except `:in`, you will get `count` as a replacement.
78
- With `:in` you will get `min` and `max` as replacements.
79
+ * `message`: Error message to display. With all the options above except `:in`, you will get `count` as a replacement.
80
+ With `:in` you will get `min` and `max` as replacements.
79
81
  `count`, `min` and `max` each will have its value and unit together.
80
82
  You can write error messages without using any replacement.
81
83
  ```ruby
82
84
  validates :avatar, file_size: { less_than: 100.kilobytes,
83
- message: 'avatar should be less than %{count}' }
85
+ message: 'avatar should be less than %{count}' }
84
86
  ```
85
87
  ```ruby
86
88
  validates :document, file_size: { in: 1.kilobyte..1.megabyte,
@@ -139,8 +141,13 @@ validates :video, file_content_type: { allow: lambda { |record| record.content_t
139
141
  can be a String or a Regexp. It also accepts `proc`. See `:allow` options examples.
140
142
  * `mode`: `:strict` or `:relaxed`. `:strict` mode can detect content type based on the contents
141
143
  of the files. It also detects media type spoofing (see more in [security](#security)).
142
- `:relaxed` mode uses file name to detect the content type using `mime-types` gem.
143
- If mode option is not set then the validator uses form supplied content type.
144
+ `:file` analyzer is used in `:strict` mode. `:relaxed` mode uses file name to detect
145
+ the content type. `mime_types` analyzer is used in `relaxed` mode. If mode option is not
146
+ set then the validator uses form supplied content type.
147
+ * `tool`: `:file`, `:fastimage`, `:filemagic`, `:mimemagic`, `:marcel`, `:mime_types`, `:mini_mime`.
148
+ You can choose one of these built-in MIME type analyzers. You have to install the analyzer gem you choose.
149
+ By default supplied content type is used to determine the MIME type. This option takes precedence
150
+ over `mode` option.
144
151
  ```ruby
145
152
  validates :avatar, file_content_type: { allow: 'image/jpeg', mode: :strict }
146
153
  validates :avatar, file_content_type: { allow: 'image/jpeg', mode: :relaxed }
@@ -169,7 +176,7 @@ validates :avatar, file_content_type: { allow: /^image\/.*/, exclude: ['image/pn
169
176
  This gem can use Unix file command to get the content type based on the content of the file rather
170
177
  than the extension. This prevents fake content types inserted in the request header.
171
178
 
172
- It also prevents file media type spoofing. For example, user may upload a .html document as
179
+ It also prevents file media type spoofing. For example, user may upload a .html document as
173
180
  a part of the EXIF header of a valid JPEG file. Content type validator will identify its content type
174
181
  as `image/jpeg` and, without spoof detection, it may pass the validation and be saved as .html document
175
182
  thus exposing your application to a security vulnerability. Media type spoof detector wont let that happen.
@@ -177,8 +184,8 @@ It will not allow a file having `image/jpeg` content type to be saved as `text/p
177
184
  type mismatch, for example `text` of `text/plain` and `image` of `image/jpeg`. So it will not prevent
178
185
  `image/jpeg` from saving as `image/png` as both have the same `image` media type.
179
186
 
180
- **note**: This security feature is disabled by default. To enable it, first add `cocaine` gem in
181
- your Gemfile and then add `mode: :strict` option in [content type validations](#file-content-type-validator).
187
+ **note**: This security feature is disabled by default. To enable it, add `mode: :strict` option
188
+ in [content type validations](#file-content-type-validator).
182
189
  `:strict` mode may not work in direct file uploading systems as the file is not passed along with the form.
183
190
 
184
191
  ## i18n Translations
@@ -198,7 +205,7 @@ of the file matches anyone of them. takes `types` as replacement.
198
205
 
199
206
  This gem provides `en` translations for this errors under `errors.messages` namespace.
200
207
  If you want to override and/or create other locales, you can
201
- check [this](https://github.com/musaffa/file_validators/blob/master/lib/file_validators/locale/en.yml) out to see how translations are done.
208
+ check [this](https://github.com/musaffa/file_validators/blob/master/lib/file_validators/locale/en.yml) out to see how translations are done.
202
209
 
203
210
  You can override all of them with the `:message` option.
204
211
 
@@ -251,10 +258,11 @@ uploaders start processing a file immediately after its assignment (even before
251
258
  $ rake
252
259
  $ rake test:unit
253
260
  $ rake test:integration
261
+ $ rubocop
254
262
 
255
263
  # test different active model versions
256
- $ appraisal install
257
- $ appraisal rake
264
+ $ bundle exec appraisal install
265
+ $ bundle exec appraisal rake
258
266
  ```
259
267
 
260
268
  ## Problems
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'bundler/setup'
3
5
  rescue LoadError
@@ -16,7 +18,7 @@ namespace :test do
16
18
  end
17
19
  end
18
20
 
19
- task :default => ['test:unit', 'test:integration']
21
+ task default: ['test:unit', 'test:integration']
20
22
 
21
23
  # require 'rdoc/task'
22
24
 
@@ -1,4 +1,6 @@
1
- $:.push File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
2
4
 
3
5
  require 'file_validators/version'
4
6
 
@@ -12,17 +14,21 @@ Gem::Specification.new do |s|
12
14
  s.homepage = 'https://github.com/musaffa/file_validators'
13
15
  s.license = 'MIT'
14
16
 
15
- s.files = `git ls-files`.split($/)
17
+ s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
16
18
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
19
  s.test_files = s.files.grep(%r{^spec/})
18
- s.require_paths = ['lib']
20
+ s.require_paths = ['lib']
19
21
 
20
- s.add_dependency 'activemodel', '>= 3.0'
22
+ s.add_dependency 'activemodel', '>= 3.2'
21
23
  s.add_dependency 'mime-types', '>= 1.0'
22
24
 
23
- s.add_development_dependency 'cocaine', '~> 0.5.4'
24
- s.add_development_dependency 'rake'
25
- s.add_development_dependency 'rspec', '~> 3.1.0'
26
25
  s.add_development_dependency 'coveralls'
26
+ s.add_development_dependency 'fastimage'
27
+ s.add_development_dependency 'marcel', '~> 0.3' if RUBY_VERSION >= '2.2.0'
28
+ s.add_development_dependency 'mimemagic', '>= 0.3.2'
29
+ s.add_development_dependency 'mini_mime', '~> 1.0'
27
30
  s.add_development_dependency 'rack-test'
31
+ s.add_development_dependency 'rake'
32
+ s.add_development_dependency 'rspec', '~> 3.5.0'
33
+ s.add_development_dependency 'rubocop', '~> 0.58.2'
28
34
  end
@@ -3,6 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
- gem "activemodel", "3.2.18"
6
+ gem "activemodel", "3.2.22.5"
7
+ gem "rack", "1.6.5"
7
8
 
8
9
  gemspec :path => "../"
@@ -3,6 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
- gem "activemodel", "4.0.10"
6
+ gem "activemodel", "4.0.13"
7
+ gem "rack", "1.6.5"
7
8
 
8
9
  gemspec :path => "../"
@@ -3,6 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
- gem "activemodel", "4.2.3"
6
+ gem "activemodel", "5.0.1"
7
7
 
8
8
  gemspec :path => "../"
@@ -3,6 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
- gem "activemodel", "4.1.6"
6
+ gem "activemodel", "6.0.3"
7
7
 
8
8
  gemspec :path => "../"
@@ -3,6 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
- gem "activemodel", "3.0.20"
6
+ gem "activemodel", "6.1.0"
7
7
 
8
8
  gemspec :path => "../"
@@ -1,16 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_model'
2
4
  require 'ostruct'
3
5
 
4
6
  module FileValidators
5
- module Utils
6
- extend ActiveSupport::Autoload
7
-
8
- autoload :ContentTypeDetector
9
- autoload :MediaTypeSpoofDetector
10
- end
7
+ extend ActiveSupport::Autoload
8
+ autoload :Error
9
+ autoload :MimeTypeAnalyzer
11
10
  end
12
11
 
13
- Dir[File.dirname(__FILE__) + "/file_validators/validators/*.rb"].each { |file| require file }
12
+ Dir[File.dirname(__FILE__) + '/file_validators/validators/*.rb'].each { |file| require file }
14
13
 
15
14
  locale_path = Dir.glob(File.dirname(__FILE__) + '/file_validators/locale/*.yml')
16
15
  I18n.load_path += locale_path unless I18n.load_path.include?(locale_path)
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FileValidators
4
+ class Error < StandardError
5
+ end
6
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extracted from shrine/plugins/determine_mime_type.rb
4
+ module FileValidators
5
+ class MimeTypeAnalyzer
6
+ SUPPORTED_TOOLS = %i[fastimage file filemagic mimemagic marcel mime_types mini_mime].freeze
7
+ MAGIC_NUMBER = 256 * 1024
8
+
9
+ def initialize(tool)
10
+ raise Error, "unknown mime type analyzer #{tool.inspect}, supported analyzers are: #{SUPPORTED_TOOLS.join(',')}" unless SUPPORTED_TOOLS.include?(tool)
11
+
12
+ @tool = tool
13
+ end
14
+
15
+ def call(io)
16
+ mime_type = send(:"extract_with_#{@tool}", io)
17
+ io.rewind
18
+
19
+ mime_type
20
+ end
21
+
22
+ private
23
+
24
+ def extract_with_file(io)
25
+ require 'open3'
26
+
27
+ return nil if io.eof? # file command returns "application/x-empty" for empty files
28
+
29
+ Open3.popen3(*%W[file --mime-type --brief -]) do |stdin, stdout, stderr, thread|
30
+ begin
31
+ IO.copy_stream(io, stdin.binmode)
32
+ rescue Errno::EPIPE
33
+ end
34
+ stdin.close
35
+
36
+ status = thread.value
37
+
38
+ raise Error, "file command failed to spawn: #{stderr.read}" if status.nil?
39
+ raise Error, "file command failed: #{stderr.read}" unless status.success?
40
+ $stderr.print(stderr.read)
41
+
42
+ stdout.read.strip
43
+ end
44
+ rescue Errno::ENOENT
45
+ raise Error, 'file command-line tool is not installed'
46
+ end
47
+
48
+ def extract_with_fastimage(io)
49
+ require 'fastimage'
50
+
51
+ type = FastImage.type(io)
52
+ "image/#{type}" if type
53
+ end
54
+
55
+ def extract_with_filemagic(io)
56
+ require 'filemagic'
57
+
58
+ return nil if io.eof? # FileMagic returns "application/x-empty" for empty files
59
+
60
+ FileMagic.open(FileMagic::MAGIC_MIME_TYPE) do |filemagic|
61
+ filemagic.buffer(io.read(MAGIC_NUMBER))
62
+ end
63
+ end
64
+
65
+ def extract_with_mimemagic(io)
66
+ require 'mimemagic'
67
+
68
+ mime = MimeMagic.by_magic(io)
69
+ mime.type if mime
70
+ end
71
+
72
+ def extract_with_marcel(io)
73
+ require 'marcel'
74
+
75
+ return nil if io.eof? # marcel returns "application/octet-stream" for empty files
76
+
77
+ Marcel::MimeType.for(io)
78
+ end
79
+
80
+ def extract_with_mime_types(io)
81
+ require 'mime/types'
82
+
83
+ if filename = extract_filename(io)
84
+ mime_type = MIME::Types.of(filename).first
85
+ mime_type.content_type if mime_type
86
+ end
87
+ end
88
+
89
+ def extract_with_mini_mime(io)
90
+ require 'mini_mime'
91
+
92
+ if filename = extract_filename(io)
93
+ info = MiniMime.lookup_by_filename(filename)
94
+ info.content_type if info
95
+ end
96
+ end
97
+
98
+ def extract_filename(io)
99
+ if io.respond_to?(:original_filename)
100
+ io.original_filename
101
+ elsif io.respond_to?(:path)
102
+ File.basename(io.path)
103
+ end
104
+ end
105
+ end
106
+ end