file_validators 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.rubocop.yml +32 -0
- data/.tool-versions +1 -0
- data/.travis.yml +36 -6
- data/Appraisals +8 -8
- data/CHANGELOG.md +20 -0
- data/Gemfile +2 -0
- data/README.md +21 -16
- data/Rakefile +3 -1
- data/file_validators.gemspec +12 -6
- data/gemfiles/activemodel_3.2.gemfile +0 -0
- data/gemfiles/activemodel_4.0.gemfile +0 -0
- data/gemfiles/activemodel_5.0.gemfile +0 -0
- data/gemfiles/{activemodel_4.1.gemfile → activemodel_6.0.gemfile} +1 -2
- data/gemfiles/{activemodel_4.2.gemfile → activemodel_6.1.gemfile} +1 -2
- data/lib/file_validators/error.rb +6 -0
- data/lib/file_validators/mime_type_analyzer.rb +106 -0
- data/lib/file_validators/validators/file_content_type_validator.rb +33 -42
- data/lib/file_validators/validators/file_size_validator.rb +43 -19
- data/lib/file_validators/version.rb +3 -1
- data/lib/file_validators.rb +6 -7
- data/spec/integration/combined_validators_integration_spec.rb +3 -1
- data/spec/integration/file_content_type_validation_integration_spec.rb +73 -17
- data/spec/integration/file_size_validator_integration_spec.rb +43 -16
- data/spec/lib/file_validators/mime_type_analyzer_spec.rb +139 -0
- data/spec/lib/file_validators/validators/file_content_type_validator_spec.rb +90 -32
- data/spec/lib/file_validators/validators/file_size_validator_spec.rb +78 -30
- data/spec/spec_helper.rb +4 -0
- data/spec/support/fakeio.rb +17 -0
- data/spec/support/helpers.rb +7 -0
- data/spec/support/matchers/allow_content_type.rb +2 -0
- data/spec/support/matchers/allow_file_size.rb +2 -0
- metadata +82 -23
- data/lib/file_validators/utils/content_type_detector.rb +0 -67
- data/lib/file_validators/utils/media_type_spoof_detector.rb +0 -46
- data/spec/lib/file_validators/utils/content_type_detector_spec.rb +0 -27
- data/spec/lib/file_validators/utils/media_type_spoof_detector_spec.rb +0 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3a55a08bc74303b69e964f61e3726e8ee00278c4cd0cd6fc217691c85114bcfb
|
|
4
|
+
data.tar.gz: be3b6d628de09d262a97abf3099ff66fd2e89dd27eec3030523a7f36c82e17df
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cf5b75fb78eaf84f42abef75255572b1917012fbdbdd70d771da14c366619677fb69f4c45d688b8851d5b99dbba44ccd08da6c7025c3819c40259e23502f5c61
|
|
7
|
+
data.tar.gz: 92e9c49f94d8cbc1c2f8aa7425527e84555e94d25c12b6ca1b51e4d5e51f765defb274264996174e6757d8606270f5513232be64d234717075abab8680d8f7c9
|
data/.rubocop.yml
ADDED
|
@@ -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
|
data/.tool-versions
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby 2.3.1
|
data/.travis.yml
CHANGED
|
@@ -1,25 +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
8
|
- jruby-9.1.7.0
|
|
9
|
+
- jruby-9.2.13.0
|
|
9
10
|
|
|
10
11
|
gemfile:
|
|
12
|
+
- gemfiles/activemodel_6.1.gemfile
|
|
13
|
+
- gemfiles/activemodel_6.0.gemfile
|
|
11
14
|
- gemfiles/activemodel_5.0.gemfile
|
|
12
|
-
- gemfiles/activemodel_4.2.gemfile
|
|
13
|
-
- gemfiles/activemodel_4.1.gemfile
|
|
14
15
|
- gemfiles/activemodel_4.0.gemfile
|
|
15
16
|
- gemfiles/activemodel_3.2.gemfile
|
|
16
17
|
|
|
17
18
|
matrix:
|
|
18
19
|
exclude:
|
|
19
|
-
- rvm: 2.
|
|
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
|
|
20
35
|
gemfile: gemfiles/activemodel_5.0.gemfile
|
|
21
|
-
|
|
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
|
|
22
42
|
gemfile: gemfiles/activemodel_5.0.gemfile
|
|
23
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
|
+
|
|
24
54
|
allow_failures:
|
|
25
55
|
- rvm: ruby-head
|
data/Appraisals
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
appraise 'activemodel-3.2' do
|
|
2
4
|
gem 'activemodel', '3.2.22.5'
|
|
3
5
|
gem 'rack', '1.6.5'
|
|
@@ -8,16 +10,14 @@ appraise 'activemodel-4.0' do
|
|
|
8
10
|
gem 'rack', '1.6.5'
|
|
9
11
|
end
|
|
10
12
|
|
|
11
|
-
appraise 'activemodel-
|
|
12
|
-
gem 'activemodel', '
|
|
13
|
-
gem 'rack', '1.6.5'
|
|
13
|
+
appraise 'activemodel-5.0' do
|
|
14
|
+
gem 'activemodel', '5.0.1'
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
appraise 'activemodel-
|
|
17
|
-
gem 'activemodel', '
|
|
18
|
-
gem 'rack', '1.6.5'
|
|
17
|
+
appraise 'activemodel-6.0' do
|
|
18
|
+
gem 'activemodel', '6.0.3'
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
appraise 'activemodel-
|
|
22
|
-
gem 'activemodel', '
|
|
21
|
+
appraise 'activemodel-6.1' do
|
|
22
|
+
gem 'activemodel', '6.1.0'
|
|
23
23
|
end
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
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
|
+
|
|
1
21
|
# 2.2.0-beta.1
|
|
2
22
|
|
|
3
23
|
* [#17](https://github.com/musaffa/file_validators/pull/17) Now Supports multiple file uploads
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/rb/file_validators)
|
|
4
4
|
[](https://travis-ci.org/musaffa/file_validators)
|
|
5
|
-
[](https://gemnasium.com/musaffa/file_validators)
|
|
6
5
|
[](https://coveralls.io/r/musaffa/file_validators)
|
|
7
6
|
[](https://codeclimate.com/github/musaffa/file_validators)
|
|
8
7
|
[](http://inch-ci.org/github/musaffa/file_validators)
|
|
@@ -12,8 +11,8 @@ Any module that uses ActiveModel, for example ActiveRecord, can use these file v
|
|
|
12
11
|
|
|
13
12
|
## Support
|
|
14
13
|
|
|
15
|
-
* ActiveModel versions: 3.2, 4 and
|
|
16
|
-
* Rails versions: 3.2, 4 and
|
|
14
|
+
* ActiveModel versions: 3.2, 4, 5 and 6.
|
|
15
|
+
* Rails versions: 3.2, 4, 5 and 6.
|
|
17
16
|
|
|
18
17
|
As of version `2.2`, activemodel 3.0 and 3.1 will no longer be supported.
|
|
19
18
|
For activemodel 3.0 and 3.1, please use file_validators version `<= 2.1`.
|
|
@@ -39,7 +38,7 @@ class Profile
|
|
|
39
38
|
|
|
40
39
|
attr_accessor :avatar
|
|
41
40
|
validates :avatar, file_size: { less_than_or_equal_to: 100.kilobytes },
|
|
42
|
-
file_content_type: { allow: ['image/jpeg', 'image/png'] }
|
|
41
|
+
file_content_type: { allow: ['image/jpeg', 'image/png'] }
|
|
43
42
|
end
|
|
44
43
|
```
|
|
45
44
|
ActiveRecord example:
|
|
@@ -67,23 +66,23 @@ validates :avatar, file_size: { less_than: 2.gigabytes }
|
|
|
67
66
|
```
|
|
68
67
|
* `less_than_or_equal_to`: Less than or equal to a number in bytes or a proc that returns a number
|
|
69
68
|
```ruby
|
|
70
|
-
validates :avatar, file_size: { less_than_or_equal_to: 50.bytes }
|
|
69
|
+
validates :avatar, file_size: { less_than_or_equal_to: 50.bytes }
|
|
71
70
|
```
|
|
72
71
|
* `greater_than`: greater than a number in bytes or a proc that returns a number
|
|
73
72
|
```ruby
|
|
74
|
-
validates :avatar, file_size: { greater_than: 1.byte }
|
|
73
|
+
validates :avatar, file_size: { greater_than: 1.byte }
|
|
75
74
|
```
|
|
76
75
|
* `greater_than_or_equal_to`: Greater than or equal to a number in bytes or a proc that returns a number
|
|
77
76
|
```ruby
|
|
78
|
-
validates :avatar, file_size: { greater_than_or_equal_to: 50.bytes }
|
|
77
|
+
validates :avatar, file_size: { greater_than_or_equal_to: 50.bytes }
|
|
79
78
|
```
|
|
80
|
-
* `message`: Error message to display. With all the options above except `:in`, you will get `count` as a replacement.
|
|
81
|
-
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.
|
|
82
81
|
`count`, `min` and `max` each will have its value and unit together.
|
|
83
82
|
You can write error messages without using any replacement.
|
|
84
83
|
```ruby
|
|
85
84
|
validates :avatar, file_size: { less_than: 100.kilobytes,
|
|
86
|
-
message: 'avatar should be less than %{count}' }
|
|
85
|
+
message: 'avatar should be less than %{count}' }
|
|
87
86
|
```
|
|
88
87
|
```ruby
|
|
89
88
|
validates :document, file_size: { in: 1.kilobyte..1.megabyte,
|
|
@@ -142,8 +141,13 @@ validates :video, file_content_type: { allow: lambda { |record| record.content_t
|
|
|
142
141
|
can be a String or a Regexp. It also accepts `proc`. See `:allow` options examples.
|
|
143
142
|
* `mode`: `:strict` or `:relaxed`. `:strict` mode can detect content type based on the contents
|
|
144
143
|
of the files. It also detects media type spoofing (see more in [security](#security)).
|
|
145
|
-
`:relaxed` mode uses file name to detect
|
|
146
|
-
|
|
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.
|
|
147
151
|
```ruby
|
|
148
152
|
validates :avatar, file_content_type: { allow: 'image/jpeg', mode: :strict }
|
|
149
153
|
validates :avatar, file_content_type: { allow: 'image/jpeg', mode: :relaxed }
|
|
@@ -172,7 +176,7 @@ validates :avatar, file_content_type: { allow: /^image\/.*/, exclude: ['image/pn
|
|
|
172
176
|
This gem can use Unix file command to get the content type based on the content of the file rather
|
|
173
177
|
than the extension. This prevents fake content types inserted in the request header.
|
|
174
178
|
|
|
175
|
-
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
|
|
176
180
|
a part of the EXIF header of a valid JPEG file. Content type validator will identify its content type
|
|
177
181
|
as `image/jpeg` and, without spoof detection, it may pass the validation and be saved as .html document
|
|
178
182
|
thus exposing your application to a security vulnerability. Media type spoof detector wont let that happen.
|
|
@@ -180,8 +184,8 @@ It will not allow a file having `image/jpeg` content type to be saved as `text/p
|
|
|
180
184
|
type mismatch, for example `text` of `text/plain` and `image` of `image/jpeg`. So it will not prevent
|
|
181
185
|
`image/jpeg` from saving as `image/png` as both have the same `image` media type.
|
|
182
186
|
|
|
183
|
-
**note**: This security feature is disabled by default. To enable it,
|
|
184
|
-
|
|
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).
|
|
185
189
|
`:strict` mode may not work in direct file uploading systems as the file is not passed along with the form.
|
|
186
190
|
|
|
187
191
|
## i18n Translations
|
|
@@ -201,7 +205,7 @@ of the file matches anyone of them. takes `types` as replacement.
|
|
|
201
205
|
|
|
202
206
|
This gem provides `en` translations for this errors under `errors.messages` namespace.
|
|
203
207
|
If you want to override and/or create other locales, you can
|
|
204
|
-
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.
|
|
205
209
|
|
|
206
210
|
You can override all of them with the `:message` option.
|
|
207
211
|
|
|
@@ -254,6 +258,7 @@ uploaders start processing a file immediately after its assignment (even before
|
|
|
254
258
|
$ rake
|
|
255
259
|
$ rake test:unit
|
|
256
260
|
$ rake test:integration
|
|
261
|
+
$ rubocop
|
|
257
262
|
|
|
258
263
|
# test different active model versions
|
|
259
264
|
$ bundle exec appraisal install
|
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 :
|
|
21
|
+
task default: ['test:unit', 'test:integration']
|
|
20
22
|
|
|
21
23
|
# require 'rdoc/task'
|
|
22
24
|
|
data/file_validators.gemspec
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
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
|
|
20
|
+
s.require_paths = ['lib']
|
|
19
21
|
|
|
20
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.5.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -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
|
|
@@ -1,25 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveModel
|
|
2
4
|
module Validations
|
|
3
|
-
|
|
4
5
|
class FileContentTypeValidator < ActiveModel::EachValidator
|
|
5
|
-
CHECKS = [
|
|
6
|
+
CHECKS = %i[allow exclude].freeze
|
|
7
|
+
SUPPORTED_MODES = { relaxed: :mime_types, strict: :file }.freeze
|
|
6
8
|
|
|
7
9
|
def self.helper_method_name
|
|
8
10
|
:validates_file_content_type
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def validate_each(record, attribute, value)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
begin
|
|
15
|
+
values = parse_values(value)
|
|
16
|
+
rescue JSON::ParserError
|
|
17
|
+
record.errors.add attribute, :invalid
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
return if values.empty?
|
|
22
|
+
|
|
23
|
+
mode = option_value(record, :mode)
|
|
24
|
+
tool = option_value(record, :tool) || SUPPORTED_MODES[mode]
|
|
25
|
+
|
|
26
|
+
allowed_types = option_content_types(record, :allow)
|
|
27
|
+
forbidden_types = option_content_types(record, :exclude)
|
|
28
|
+
|
|
29
|
+
values.each do |val|
|
|
30
|
+
content_type = get_content_type(val, tool)
|
|
31
|
+
validate_whitelist(record, attribute, content_type, allowed_types)
|
|
32
|
+
validate_blacklist(record, attribute, content_type, forbidden_types)
|
|
23
33
|
end
|
|
24
34
|
end
|
|
25
35
|
|
|
@@ -42,34 +52,12 @@ module ActiveModel
|
|
|
42
52
|
|
|
43
53
|
value = JSON.parse(value) if value.is_a?(String)
|
|
44
54
|
|
|
45
|
-
Array.wrap(value).reject
|
|
55
|
+
Array.wrap(value).reject(&:blank?)
|
|
46
56
|
end
|
|
47
57
|
|
|
48
|
-
def
|
|
49
|
-
if
|
|
50
|
-
value
|
|
51
|
-
else
|
|
52
|
-
raise ArgumentError, 'value must return a file path in order to validate file content type'
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def get_file_name(value)
|
|
57
|
-
if value.try(:original_filename)
|
|
58
|
-
value.original_filename
|
|
59
|
-
else
|
|
60
|
-
File.basename(get_file_path(value))
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def get_content_type(value, mode)
|
|
65
|
-
case mode
|
|
66
|
-
when :strict
|
|
67
|
-
file_path = get_file_path(value)
|
|
68
|
-
file_name = get_file_name(value)
|
|
69
|
-
FileValidators::Utils::ContentTypeDetector.new(file_path, file_name).detect
|
|
70
|
-
when :relaxed
|
|
71
|
-
file_name = get_file_name(value)
|
|
72
|
-
MIME::Types.type_for(file_name).first
|
|
58
|
+
def get_content_type(value, tool)
|
|
59
|
+
if tool.present?
|
|
60
|
+
FileValidators::MimeTypeAnalyzer.new(tool).call(value)
|
|
73
61
|
else
|
|
74
62
|
value = OpenStruct.new(value) if value.is_a?(Hash)
|
|
75
63
|
value.content_type
|
|
@@ -85,7 +73,7 @@ module ActiveModel
|
|
|
85
73
|
end
|
|
86
74
|
|
|
87
75
|
def validate_whitelist(record, attribute, content_type, allowed_types)
|
|
88
|
-
if allowed_types.present?
|
|
76
|
+
if allowed_types.present? && allowed_types.none? { |type| type === content_type }
|
|
89
77
|
mark_invalid record, attribute, :allowed_file_content_types, allowed_types
|
|
90
78
|
end
|
|
91
79
|
end
|
|
@@ -99,7 +87,7 @@ module ActiveModel
|
|
|
99
87
|
def mark_invalid(record, attribute, error, option_types)
|
|
100
88
|
error_options = options.merge(types: option_types.join(', '))
|
|
101
89
|
unless record.errors.added?(attribute, error, error_options)
|
|
102
|
-
record.errors.add attribute, error, error_options
|
|
90
|
+
record.errors.add attribute, error, **error_options
|
|
103
91
|
end
|
|
104
92
|
end
|
|
105
93
|
end
|
|
@@ -123,6 +111,10 @@ module ActiveModel
|
|
|
123
111
|
# :relaxed validates the content type based on the file name using
|
|
124
112
|
# the mime-types gem. It's only for sanity check.
|
|
125
113
|
# If mode is not set then it uses form supplied content type.
|
|
114
|
+
# * +tool+: :file, :fastimage, :filemagic, :mimemagic, :marcel, :mime_types, :mini_mime
|
|
115
|
+
# You can choose a different built-in MIME type analyzer
|
|
116
|
+
# By default supplied content type is used to determine the MIME type
|
|
117
|
+
# This option have precedence over mode option
|
|
126
118
|
# * +if+: A lambda or name of an instance method. Validation will only
|
|
127
119
|
# be run is this lambda or method returns true.
|
|
128
120
|
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
|
@@ -130,6 +122,5 @@ module ActiveModel
|
|
|
130
122
|
validates_with FileContentTypeValidator, _merge_attributes(attr_names)
|
|
131
123
|
end
|
|
132
124
|
end
|
|
133
|
-
|
|
134
125
|
end
|
|
135
126
|
end
|