file_validators 3.0.0.beta1 → 3.0.0.beta2
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/.tool-versions +1 -0
- data/.travis.yml +18 -4
- data/Appraisals +4 -0
- data/CHANGELOG.md +5 -0
- data/README.md +10 -5
- data/file_validators.gemspec +4 -1
- data/gemfiles/activemodel_3.2.gemfile +5 -7
- data/gemfiles/activemodel_4.0.gemfile +5 -7
- data/gemfiles/activemodel_4.1.gemfile +5 -7
- data/gemfiles/activemodel_4.2.gemfile +5 -7
- data/gemfiles/activemodel_5.0.gemfile +4 -6
- data/gemfiles/activemodel_5.2.gemfile +8 -0
- data/lib/file_validators.rb +3 -6
- 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 +18 -27
- data/lib/file_validators/validators/file_size_validator.rb +7 -1
- data/lib/file_validators/version.rb +1 -1
- data/spec/integration/file_content_type_validation_integration_spec.rb +30 -0
- data/spec/integration/file_size_validator_integration_spec.rb +6 -1
- data/spec/lib/file_validators/mime_type_analyzer_spec.rb +139 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/fakeio.rb +17 -0
- data/spec/support/helpers.rb +7 -0
- metadata +68 -23
- data/lib/file_validators/utils/content_type_detector.rb +0 -66
- data/lib/file_validators/utils/media_type_spoof_detector.rb +0 -46
- data/spec/lib/file_validators/utils/content_type_detector_spec.rb +0 -29
- data/spec/lib/file_validators/utils/media_type_spoof_detector_spec.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7536b453f528e82937d43e4349c6cb99f990832023a5567d125b817b59f3b71a
|
4
|
+
data.tar.gz: e33270b079d15c08aed6a2b3e1f2aab9f61d39fc222f9c42e15a0a9a34f82885
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c47e7fe802dc65553c2dfa5a72c2d05d97d4d14dcdf2c6826f7ae18e586a8754b1b4dc07c20abec2038b55c6d1766df96c19f968217630270586c92bab4101f
|
7
|
+
data.tar.gz: b8b0c693d4314614c2cd8f72a699b48c666487c4b44221f25daf9f70cc0d27d1b09766b9ef8e035ed14690ea9d24aac10cdbcc62d1e89bf2a683e46a7e39fec0
|
data/.tool-versions
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby 2.3.1
|
data/.travis.yml
CHANGED
@@ -1,25 +1,39 @@
|
|
1
1
|
language: ruby
|
2
2
|
|
3
3
|
rvm:
|
4
|
-
- 2.0
|
5
4
|
- 2.2.3
|
5
|
+
- 2.5.0
|
6
6
|
- ruby-head
|
7
7
|
- jruby-9.0.4.0
|
8
8
|
- jruby-9.1.7.0
|
9
9
|
|
10
10
|
gemfile:
|
11
|
+
- gemfiles/activemodel_5.2.gemfile
|
11
12
|
- gemfiles/activemodel_5.0.gemfile
|
12
13
|
- gemfiles/activemodel_4.2.gemfile
|
13
|
-
- gemfiles/activemodel_4.1.gemfile
|
14
14
|
- gemfiles/activemodel_4.0.gemfile
|
15
15
|
- gemfiles/activemodel_3.2.gemfile
|
16
16
|
|
17
17
|
matrix:
|
18
18
|
exclude:
|
19
|
-
- rvm: 2.0
|
20
|
-
gemfile: gemfiles/
|
19
|
+
- rvm: 2.5.0
|
20
|
+
gemfile: gemfiles/activemodel_3.2.gemfile
|
21
|
+
- rvm: 2.5.0
|
22
|
+
gemfile: gemfiles/activemodel_4.0.gemfile
|
23
|
+
- rvm: 2.5.0
|
24
|
+
gemfile: gemfiles/activemodel_4.2.gemfile
|
25
|
+
|
26
|
+
- rvm: ruby-head
|
27
|
+
gemfile: gemfiles/activemodel_3.2.gemfile
|
28
|
+
- rvm: ruby-head
|
29
|
+
gemfile: gemfiles/activemodel_4.0.gemfile
|
30
|
+
- rvm: ruby-head
|
31
|
+
gemfile: gemfiles/activemodel_4.2.gemfile
|
32
|
+
|
21
33
|
- rvm: jruby-9.0.4.0
|
22
34
|
gemfile: gemfiles/activemodel_5.0.gemfile
|
35
|
+
- rvm: jruby-9.0.4.0
|
36
|
+
gemfile: gemfiles/activemodel_5.2.gemfile
|
23
37
|
|
24
38
|
allow_failures:
|
25
39
|
- rvm: ruby-head
|
data/Appraisals
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# 3.0.0.beta2
|
2
|
+
|
3
|
+
* [#32](https://github.com/musaffa/file_validators/pull/32) Removed terrapin. Added options for choosing MIME type analyzers with `:tool` option.
|
4
|
+
* Rubocop style guide
|
5
|
+
|
1
6
|
# 3.0.0.beta1
|
2
7
|
|
3
8
|
* [#29](https://github.com/musaffa/file_validators/pull/29) Upgrade cocaine to terrapin
|
data/README.md
CHANGED
@@ -142,8 +142,13 @@ validates :video, file_content_type: { allow: lambda { |record| record.content_t
|
|
142
142
|
can be a String or a Regexp. It also accepts `proc`. See `:allow` options examples.
|
143
143
|
* `mode`: `:strict` or `:relaxed`. `:strict` mode can detect content type based on the contents
|
144
144
|
of the files. It also detects media type spoofing (see more in [security](#security)).
|
145
|
-
`:relaxed` mode uses file name to detect
|
146
|
-
|
145
|
+
`:file` analyzer is used in `:strict` model. `:relaxed` mode uses file name to detect
|
146
|
+
the content type. `mime_types` analyzer is used in `relaxed` mode. If mode option is not
|
147
|
+
set then the validator uses form supplied content type.
|
148
|
+
* `tool`: `:file`, `:fastimage`, `:filemagic`, `:mimemagic`, `:marcel`, `:mime_types`, `:mini_mime`.
|
149
|
+
You can choose one of these built-in MIME type analyzers. You have to install the analyzer gem you choose.
|
150
|
+
By default supplied content type is used to determine the MIME type. This option takes precedence
|
151
|
+
over `mode` option.
|
147
152
|
```ruby
|
148
153
|
validates :avatar, file_content_type: { allow: 'image/jpeg', mode: :strict }
|
149
154
|
validates :avatar, file_content_type: { allow: 'image/jpeg', mode: :relaxed }
|
@@ -180,8 +185,8 @@ It will not allow a file having `image/jpeg` content type to be saved as `text/p
|
|
180
185
|
type mismatch, for example `text` of `text/plain` and `image` of `image/jpeg`. So it will not prevent
|
181
186
|
`image/jpeg` from saving as `image/png` as both have the same `image` media type.
|
182
187
|
|
183
|
-
**note**: This security feature is disabled by default. To enable it,
|
184
|
-
|
188
|
+
**note**: This security feature is disabled by default. To enable it, add `mode: :strict` option
|
189
|
+
in [content type validations](#file-content-type-validator).
|
185
190
|
`:strict` mode may not work in direct file uploading systems as the file is not passed along with the form.
|
186
191
|
|
187
192
|
## i18n Translations
|
@@ -201,7 +206,7 @@ of the file matches anyone of them. takes `types` as replacement.
|
|
201
206
|
|
202
207
|
This gem provides `en` translations for this errors under `errors.messages` namespace.
|
203
208
|
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.
|
209
|
+
check [this](https://github.com/musaffa/file_validators/blob/master/lib/file_validators/locale/en.yml) out to see how translations are done.
|
205
210
|
|
206
211
|
You can override all of them with the `:message` option.
|
207
212
|
|
data/file_validators.gemspec
CHANGED
@@ -23,9 +23,12 @@ Gem::Specification.new do |s|
|
|
23
23
|
s.add_dependency 'mime-types', '>= 1.0'
|
24
24
|
|
25
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'
|
26
30
|
s.add_development_dependency 'rack-test'
|
27
31
|
s.add_development_dependency 'rake'
|
28
32
|
s.add_development_dependency 'rspec', '~> 3.5.0'
|
29
33
|
s.add_development_dependency 'rubocop', '~> 0.58.2'
|
30
|
-
s.add_development_dependency 'terrapin', '~> 0.6'
|
31
34
|
end
|
@@ -1,11 +1,9 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
# This file was generated by Appraisal
|
4
2
|
|
5
|
-
source
|
3
|
+
source "https://rubygems.org"
|
6
4
|
|
7
|
-
gem
|
8
|
-
gem
|
9
|
-
gem
|
5
|
+
gem "appraisal"
|
6
|
+
gem "activemodel", "3.2.22.5"
|
7
|
+
gem "rack", "1.6.5"
|
10
8
|
|
11
|
-
gemspec path
|
9
|
+
gemspec :path => "../"
|
@@ -1,11 +1,9 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
# This file was generated by Appraisal
|
4
2
|
|
5
|
-
source
|
3
|
+
source "https://rubygems.org"
|
6
4
|
|
7
|
-
gem
|
8
|
-
gem
|
9
|
-
gem
|
5
|
+
gem "appraisal"
|
6
|
+
gem "activemodel", "4.0.13"
|
7
|
+
gem "rack", "1.6.5"
|
10
8
|
|
11
|
-
gemspec path
|
9
|
+
gemspec :path => "../"
|
@@ -1,11 +1,9 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
# This file was generated by Appraisal
|
4
2
|
|
5
|
-
source
|
3
|
+
source "https://rubygems.org"
|
6
4
|
|
7
|
-
gem
|
8
|
-
gem
|
9
|
-
gem
|
5
|
+
gem "appraisal"
|
6
|
+
gem "activemodel", "4.1.6"
|
7
|
+
gem "rack", "1.6.5"
|
10
8
|
|
11
|
-
gemspec path
|
9
|
+
gemspec :path => "../"
|
@@ -1,11 +1,9 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
# This file was generated by Appraisal
|
4
2
|
|
5
|
-
source
|
3
|
+
source "https://rubygems.org"
|
6
4
|
|
7
|
-
gem
|
8
|
-
gem
|
9
|
-
gem
|
5
|
+
gem "appraisal"
|
6
|
+
gem "activemodel", "4.2.7.1"
|
7
|
+
gem "rack", "1.6.5"
|
10
8
|
|
11
|
-
gemspec path
|
9
|
+
gemspec :path => "../"
|
@@ -1,10 +1,8 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
# This file was generated by Appraisal
|
4
2
|
|
5
|
-
source
|
3
|
+
source "https://rubygems.org"
|
6
4
|
|
7
|
-
gem
|
8
|
-
gem
|
5
|
+
gem "appraisal"
|
6
|
+
gem "activemodel", "5.0.1"
|
9
7
|
|
10
|
-
gemspec path
|
8
|
+
gemspec :path => "../"
|
data/lib/file_validators.rb
CHANGED
@@ -4,12 +4,9 @@ require 'active_model'
|
|
4
4
|
require 'ostruct'
|
5
5
|
|
6
6
|
module FileValidators
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
autoload :ContentTypeDetector
|
11
|
-
autoload :MediaTypeSpoofDetector
|
12
|
-
end
|
7
|
+
extend ActiveSupport::Autoload
|
8
|
+
autoload :Error
|
9
|
+
autoload :MimeTypeAnalyzer
|
13
10
|
end
|
14
11
|
|
15
12
|
Dir[File.dirname(__FILE__) + '/file_validators/validators/*.rb'].each { |file| require file }
|
@@ -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
|
@@ -4,21 +4,30 @@ module ActiveModel
|
|
4
4
|
module Validations
|
5
5
|
class FileContentTypeValidator < ActiveModel::EachValidator
|
6
6
|
CHECKS = %i[allow exclude].freeze
|
7
|
+
SUPPORTED_MODES = { relaxed: :mime_types, strict: :file }.freeze
|
7
8
|
|
8
9
|
def self.helper_method_name
|
9
10
|
:validates_file_content_type
|
10
11
|
end
|
11
12
|
|
12
13
|
def validate_each(record, attribute, value)
|
13
|
-
|
14
|
+
begin
|
15
|
+
values = parse_values(value)
|
16
|
+
rescue JSON::ParserError
|
17
|
+
record.errors.add attribute, :invalid
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
14
21
|
return if values.empty?
|
15
22
|
|
16
23
|
mode = option_value(record, :mode)
|
24
|
+
tool = option_value(record, :tool) || SUPPORTED_MODES[mode]
|
25
|
+
|
17
26
|
allowed_types = option_content_types(record, :allow)
|
18
27
|
forbidden_types = option_content_types(record, :exclude)
|
19
28
|
|
20
29
|
values.each do |val|
|
21
|
-
content_type = get_content_type(val,
|
30
|
+
content_type = get_content_type(val, tool)
|
22
31
|
validate_whitelist(record, attribute, content_type, allowed_types)
|
23
32
|
validate_blacklist(record, attribute, content_type, forbidden_types)
|
24
33
|
end
|
@@ -46,31 +55,9 @@ module ActiveModel
|
|
46
55
|
Array.wrap(value).reject(&:blank?)
|
47
56
|
end
|
48
57
|
|
49
|
-
def
|
50
|
-
if
|
51
|
-
value
|
52
|
-
else
|
53
|
-
raise ArgumentError, 'value must return a file path in order to validate file content type'
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def get_file_name(value)
|
58
|
-
if value.try(:original_filename)
|
59
|
-
value.original_filename
|
60
|
-
else
|
61
|
-
File.basename(get_file_path(value))
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def get_content_type(value, mode)
|
66
|
-
case mode
|
67
|
-
when :strict
|
68
|
-
file_path = get_file_path(value)
|
69
|
-
file_name = get_file_name(value)
|
70
|
-
FileValidators::Utils::ContentTypeDetector.new(file_path, file_name).detect
|
71
|
-
when :relaxed
|
72
|
-
file_name = get_file_name(value)
|
73
|
-
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)
|
74
61
|
else
|
75
62
|
value = OpenStruct.new(value) if value.is_a?(Hash)
|
76
63
|
value.content_type
|
@@ -124,6 +111,10 @@ module ActiveModel
|
|
124
111
|
# :relaxed validates the content type based on the file name using
|
125
112
|
# the mime-types gem. It's only for sanity check.
|
126
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
|
127
118
|
# * +if+: A lambda or name of an instance method. Validation will only
|
128
119
|
# be run is this lambda or method returns true.
|
129
120
|
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
@@ -14,7 +14,13 @@ module ActiveModel
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def validate_each(record, attribute, value)
|
17
|
-
|
17
|
+
begin
|
18
|
+
values = parse_values(value)
|
19
|
+
rescue JSON::ParserError
|
20
|
+
record.errors.add attribute, :invalid
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
18
24
|
return if values.empty?
|
19
25
|
|
20
26
|
options.slice(*CHECKS.keys).each do |option, option_value|
|
@@ -252,6 +252,31 @@ describe 'File Content Type integration with ActiveModel' do
|
|
252
252
|
end
|
253
253
|
end
|
254
254
|
|
255
|
+
context ':tool option' do
|
256
|
+
before :all do
|
257
|
+
Person.class_eval do
|
258
|
+
Person.reset_callbacks(:validate)
|
259
|
+
validates :avatar, file_content_type: { allow: 'image/jpeg', tool: :marcel }
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
subject { Person.new }
|
264
|
+
|
265
|
+
context 'with valid file' do
|
266
|
+
it 'validates the file' do
|
267
|
+
subject.avatar = Rack::Test::UploadedFile.new(@cute_path, 'image/jpeg')
|
268
|
+
expect(subject).to be_valid
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
context 'with spoofed file' do
|
273
|
+
it 'invalidates the file' do
|
274
|
+
subject.avatar = Rack::Test::UploadedFile.new(@spoofed_file_path, 'image/jpeg')
|
275
|
+
expect(subject).not_to be_valid
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
255
280
|
context ':mode option' do
|
256
281
|
context 'strict mode' do
|
257
282
|
before :all do
|
@@ -364,6 +389,11 @@ describe 'File Content Type integration with ActiveModel' do
|
|
364
389
|
before { subject.avatar = '' }
|
365
390
|
it { is_expected.to be_valid }
|
366
391
|
end
|
392
|
+
|
393
|
+
context 'invalid json string' do
|
394
|
+
before { subject.avatar = '{filename":"img140910_88338.jpg","content_type":"image/jpeg","size":13150}' }
|
395
|
+
it { is_expected.not_to be_valid }
|
396
|
+
end
|
367
397
|
end
|
368
398
|
|
369
399
|
context 'image data as hash' do
|
@@ -240,10 +240,15 @@ describe 'File Size Validator integration with ActiveModel' do
|
|
240
240
|
it { is_expected.to be_valid }
|
241
241
|
end
|
242
242
|
|
243
|
-
context 'empty
|
243
|
+
context 'empty string' do
|
244
244
|
before { subject.avatar = '' }
|
245
245
|
it { is_expected.to be_valid }
|
246
246
|
end
|
247
|
+
|
248
|
+
context 'invalid json string' do
|
249
|
+
before { subject.avatar = '{filename":"img140910_88338.GIF","content_type":"image/gif","size":33150}' }
|
250
|
+
it { is_expected.not_to be_valid }
|
251
|
+
end
|
247
252
|
end
|
248
253
|
|
249
254
|
context 'image data as hash' do
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'rack/test/uploaded_file'
|
5
|
+
|
6
|
+
describe FileValidators::MimeTypeAnalyzer do
|
7
|
+
it 'rises error when tool is invalid' do
|
8
|
+
expect { described_class.new(:invalid) }.to raise_error(FileValidators::Error)
|
9
|
+
end
|
10
|
+
|
11
|
+
before :all do
|
12
|
+
@cute_path = File.join(File.dirname(__FILE__), '../../fixtures/cute.jpg')
|
13
|
+
@spoofed_file_path = File.join(File.dirname(__FILE__), '../../fixtures/spoofed.jpg')
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:cute_image) { Rack::Test::UploadedFile.new(@cute_path, 'image/jpeg') }
|
17
|
+
let(:spoofed_file) { Rack::Test::UploadedFile.new(@spoofed_file_path, 'image/jpeg') }
|
18
|
+
|
19
|
+
describe ':file analyzer' do
|
20
|
+
let(:analyzer) { described_class.new(:file) }
|
21
|
+
|
22
|
+
it 'determines MIME type from file contents' do
|
23
|
+
expect(analyzer.call(cute_image)).to eq('image/jpeg')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns text/plain for unidentified MIME types' do
|
27
|
+
expect(analyzer.call(fakeio('a' * 5 * 1024 * 1024))).to eq('text/plain')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'is able to determine MIME type for spoofed files' do
|
31
|
+
expect(analyzer.call(spoofed_file)).to eq('text/plain')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'is able to determine MIME type for non-files' do
|
35
|
+
expect(analyzer.call(fakeio(cute_image.read))).to eq('image/jpeg')
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns nil for empty IOs' do
|
39
|
+
expect(analyzer.call(fakeio(''))).to eq(nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'raises error if file command is not found' do
|
43
|
+
allow(Open3).to receive(:popen3).and_raise(Errno::ENOENT)
|
44
|
+
expect { analyzer.call(fakeio) }.to raise_error(FileValidators::Error, 'file command-line tool is not installed')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe ':fastimage analyzer' do
|
49
|
+
let(:analyzer) { described_class.new(:fastimage) }
|
50
|
+
|
51
|
+
it 'extracts MIME type of any IO' do
|
52
|
+
expect(analyzer.call(cute_image)).to eq('image/jpeg')
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns nil for unidentified MIME types' do
|
56
|
+
expect(analyzer.call(fakeio('😃'))).to eq nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'returns nil for empty IOs' do
|
60
|
+
expect(analyzer.call(fakeio(''))).to eq nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe ':mimemagic analyzer' do
|
65
|
+
let(:analyzer) { described_class.new(:mimemagic) }
|
66
|
+
|
67
|
+
it 'extracts MIME type of any IO' do
|
68
|
+
expect(analyzer.call(cute_image)).to eq('image/jpeg')
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns nil for unidentified MIME types' do
|
72
|
+
expect(analyzer.call(fakeio('😃'))).to eq nil
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'returns nil for empty IOs' do
|
76
|
+
expect(analyzer.call(fakeio(''))).to eq nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if RUBY_VERSION >= '2.2.0'
|
81
|
+
describe ':marcel analyzer' do
|
82
|
+
let(:analyzer) { described_class.new(:marcel) }
|
83
|
+
|
84
|
+
it 'extracts MIME type of any IO' do
|
85
|
+
expect(analyzer.call(cute_image)).to eq('image/jpeg')
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'returns application/octet-stream for unidentified MIME types' do
|
89
|
+
expect(analyzer.call(fakeio('😃'))).to eq 'application/octet-stream'
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns nil for empty IOs' do
|
93
|
+
expect(analyzer.call(fakeio(''))).to eq nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe ':mime_types analyzer' do
|
99
|
+
let(:analyzer) { described_class.new(:mime_types) }
|
100
|
+
|
101
|
+
it 'extract MIME type from the file extension' do
|
102
|
+
expect(analyzer.call(fakeio(filename: 'image.png'))).to eq('image/png')
|
103
|
+
expect(analyzer.call(cute_image)).to eq('image/jpeg')
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'extracts MIME type from file extension when IO is empty' do
|
107
|
+
expect(analyzer.call(fakeio('', filename: 'image.png'))).to eq('image/png')
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'returns nil on unknown extension' do
|
111
|
+
expect(analyzer.call(fakeio(filename: 'file.foo'))).to eq(nil)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'returns nil when input is not a file' do
|
115
|
+
expect(analyzer.call(fakeio)).to eq(nil)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe ':mini_mime analyzer' do
|
120
|
+
let(:analyzer) { described_class.new(:mini_mime) }
|
121
|
+
|
122
|
+
it 'extract MIME type from the file extension' do
|
123
|
+
expect(analyzer.call(fakeio(filename: 'image.png'))).to eq('image/png')
|
124
|
+
expect(analyzer.call(cute_image)).to eq('image/jpeg')
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'extracts MIME type from file extension when IO is empty' do
|
128
|
+
expect(analyzer.call(fakeio('', filename: 'image.png'))).to eq('image/png')
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'returns nil on unkown extension' do
|
132
|
+
expect(analyzer.call(fakeio(filename: 'file.foo'))).to eq(nil)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'returns nil when input is not a file' do
|
136
|
+
expect(analyzer.call(fakeio)).to eq(nil)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -18,6 +18,8 @@ I18n.enforce_available_locales = false
|
|
18
18
|
Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require f }
|
19
19
|
|
20
20
|
RSpec.configure do |config|
|
21
|
+
config.include Helpers
|
22
|
+
|
21
23
|
# Suppress stdout in the console
|
22
24
|
config.before { allow($stdout).to receive(:write) }
|
23
25
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
class FakeIO
|
7
|
+
attr_reader :original_filename, :content_type
|
8
|
+
|
9
|
+
def initialize(content, filename: nil, content_type: nil)
|
10
|
+
@io = StringIO.new(content)
|
11
|
+
@original_filename = filename
|
12
|
+
@content_type = content_type
|
13
|
+
end
|
14
|
+
|
15
|
+
extend Forwardable
|
16
|
+
delegate %i[read rewind eof? close size] => :@io
|
17
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: file_validators
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.0.
|
4
|
+
version: 3.0.0.beta2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ahmad Musaffa
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-11-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: fastimage
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -67,61 +67,103 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: marcel
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mimemagic
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
87
|
- - ">="
|
74
88
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
89
|
+
version: 0.3.2
|
76
90
|
type: :development
|
77
91
|
prerelease: false
|
78
92
|
version_requirements: !ruby/object:Gem::Requirement
|
79
93
|
requirements:
|
80
94
|
- - ">="
|
81
95
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
96
|
+
version: 0.3.2
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
98
|
+
name: mini_mime
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
101
|
- - "~>"
|
88
102
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
103
|
+
version: '1.0'
|
90
104
|
type: :development
|
91
105
|
prerelease: false
|
92
106
|
version_requirements: !ruby/object:Gem::Requirement
|
93
107
|
requirements:
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
110
|
+
version: '1.0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
112
|
+
name: rack-test
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rake
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rspec
|
99
141
|
requirement: !ruby/object:Gem::Requirement
|
100
142
|
requirements:
|
101
143
|
- - "~>"
|
102
144
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
145
|
+
version: 3.5.0
|
104
146
|
type: :development
|
105
147
|
prerelease: false
|
106
148
|
version_requirements: !ruby/object:Gem::Requirement
|
107
149
|
requirements:
|
108
150
|
- - "~>"
|
109
151
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
152
|
+
version: 3.5.0
|
111
153
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
154
|
+
name: rubocop
|
113
155
|
requirement: !ruby/object:Gem::Requirement
|
114
156
|
requirements:
|
115
157
|
- - "~>"
|
116
158
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
159
|
+
version: 0.58.2
|
118
160
|
type: :development
|
119
161
|
prerelease: false
|
120
162
|
version_requirements: !ruby/object:Gem::Requirement
|
121
163
|
requirements:
|
122
164
|
- - "~>"
|
123
165
|
- !ruby/object:Gem::Version
|
124
|
-
version:
|
166
|
+
version: 0.58.2
|
125
167
|
description: Adds file validators to ActiveModel
|
126
168
|
email:
|
127
169
|
- musaffa_csemm@yahoo.com
|
@@ -132,6 +174,7 @@ files:
|
|
132
174
|
- ".gitignore"
|
133
175
|
- ".rspec"
|
134
176
|
- ".rubocop.yml"
|
177
|
+
- ".tool-versions"
|
135
178
|
- ".travis.yml"
|
136
179
|
- Appraisals
|
137
180
|
- CHANGELOG.md
|
@@ -146,10 +189,11 @@ files:
|
|
146
189
|
- gemfiles/activemodel_4.1.gemfile
|
147
190
|
- gemfiles/activemodel_4.2.gemfile
|
148
191
|
- gemfiles/activemodel_5.0.gemfile
|
192
|
+
- gemfiles/activemodel_5.2.gemfile
|
149
193
|
- lib/file_validators.rb
|
194
|
+
- lib/file_validators/error.rb
|
150
195
|
- lib/file_validators/locale/en.yml
|
151
|
-
- lib/file_validators/
|
152
|
-
- lib/file_validators/utils/media_type_spoof_detector.rb
|
196
|
+
- lib/file_validators/mime_type_analyzer.rb
|
153
197
|
- lib/file_validators/validators/file_content_type_validator.rb
|
154
198
|
- lib/file_validators/validators/file_size_validator.rb
|
155
199
|
- lib/file_validators/version.rb
|
@@ -161,12 +205,13 @@ files:
|
|
161
205
|
- spec/integration/combined_validators_integration_spec.rb
|
162
206
|
- spec/integration/file_content_type_validation_integration_spec.rb
|
163
207
|
- spec/integration/file_size_validator_integration_spec.rb
|
164
|
-
- spec/lib/file_validators/
|
165
|
-
- spec/lib/file_validators/utils/media_type_spoof_detector_spec.rb
|
208
|
+
- spec/lib/file_validators/mime_type_analyzer_spec.rb
|
166
209
|
- spec/lib/file_validators/validators/file_content_type_validator_spec.rb
|
167
210
|
- spec/lib/file_validators/validators/file_size_validator_spec.rb
|
168
211
|
- spec/locale/en.yml
|
169
212
|
- spec/spec_helper.rb
|
213
|
+
- spec/support/fakeio.rb
|
214
|
+
- spec/support/helpers.rb
|
170
215
|
- spec/support/matchers/allow_content_type.rb
|
171
216
|
- spec/support/matchers/allow_file_size.rb
|
172
217
|
homepage: https://github.com/musaffa/file_validators
|
@@ -188,8 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
188
233
|
- !ruby/object:Gem::Version
|
189
234
|
version: 1.3.1
|
190
235
|
requirements: []
|
191
|
-
|
192
|
-
rubygems_version: 2.5.1
|
236
|
+
rubygems_version: 3.1.2
|
193
237
|
signing_key:
|
194
238
|
specification_version: 4
|
195
239
|
summary: ActiveModel file validators
|
@@ -202,11 +246,12 @@ test_files:
|
|
202
246
|
- spec/integration/combined_validators_integration_spec.rb
|
203
247
|
- spec/integration/file_content_type_validation_integration_spec.rb
|
204
248
|
- spec/integration/file_size_validator_integration_spec.rb
|
205
|
-
- spec/lib/file_validators/
|
206
|
-
- spec/lib/file_validators/utils/media_type_spoof_detector_spec.rb
|
249
|
+
- spec/lib/file_validators/mime_type_analyzer_spec.rb
|
207
250
|
- spec/lib/file_validators/validators/file_content_type_validator_spec.rb
|
208
251
|
- spec/lib/file_validators/validators/file_size_validator_spec.rb
|
209
252
|
- spec/locale/en.yml
|
210
253
|
- spec/spec_helper.rb
|
254
|
+
- spec/support/fakeio.rb
|
255
|
+
- spec/support/helpers.rb
|
211
256
|
- spec/support/matchers/allow_content_type.rb
|
212
257
|
- spec/support/matchers/allow_file_size.rb
|
@@ -1,66 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'logger'
|
4
|
-
|
5
|
-
begin
|
6
|
-
require 'terrapin'
|
7
|
-
rescue LoadError
|
8
|
-
puts "file_validators requires 'terrapin' gem as you are using" \
|
9
|
-
' file content type validations in strict mode'
|
10
|
-
end
|
11
|
-
|
12
|
-
module FileValidators
|
13
|
-
module Utils
|
14
|
-
class ContentTypeDetector
|
15
|
-
EMPTY_CONTENT_TYPE = 'inode/x-empty'
|
16
|
-
DEFAULT_CONTENT_TYPE = 'application/octet-stream'
|
17
|
-
|
18
|
-
attr_accessor :file_path, :file_name
|
19
|
-
|
20
|
-
def initialize(file_path, file_name)
|
21
|
-
@file_path = file_path
|
22
|
-
@file_name = file_name
|
23
|
-
end
|
24
|
-
|
25
|
-
# content type detection strategy:
|
26
|
-
#
|
27
|
-
# 1. invalid file_path: returns 'application/octet-stream'
|
28
|
-
# 2. empty file: returns 'inode/x-empty'
|
29
|
-
# 3. valid file: returns the content type using file command
|
30
|
-
# 4. valid file but file commoand raises error: returns 'application/octet-stream'
|
31
|
-
|
32
|
-
def detect
|
33
|
-
if !File.exist?(file_path)
|
34
|
-
DEFAULT_CONTENT_TYPE
|
35
|
-
elsif File.zero?(file_path)
|
36
|
-
EMPTY_CONTENT_TYPE
|
37
|
-
else
|
38
|
-
content_type_from_content
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
def content_type_from_content
|
45
|
-
content_type = type_from_file_command
|
46
|
-
|
47
|
-
if FileValidators::Utils::MediaTypeSpoofDetector.new(content_type, file_name).spoofed?
|
48
|
-
logger.warn('A file with a spoofed media type has been detected by the file validators.')
|
49
|
-
else
|
50
|
-
content_type
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def type_from_file_command
|
55
|
-
Terrapin::CommandLine.new('file', '-b --mime-type :file').run(file: @file_path).strip
|
56
|
-
rescue Terrapin::CommandLineError => e
|
57
|
-
logger.info(e.message)
|
58
|
-
DEFAULT_CONTENT_TYPE
|
59
|
-
end
|
60
|
-
|
61
|
-
def logger
|
62
|
-
Logger.new(STDOUT)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'mime/types'
|
4
|
-
|
5
|
-
module FileValidators
|
6
|
-
module Utils
|
7
|
-
class MediaTypeSpoofDetector
|
8
|
-
def initialize(content_type, file_name)
|
9
|
-
@content_type = content_type
|
10
|
-
@file_name = file_name
|
11
|
-
end
|
12
|
-
|
13
|
-
# media type spoof detection strategy:
|
14
|
-
#
|
15
|
-
# 1. it will not identify as spoofed if file name doesn't have any extension
|
16
|
-
# 2. it will identify as spoofed if any of the file extension's media types
|
17
|
-
# matches the media type of the content type. So it will return true for
|
18
|
-
# `text` of `text/plain` mismatch with `image` of `image/jpeg`, but return false
|
19
|
-
# for `image` of `image/png` match with `image` of `image/jpeg`.
|
20
|
-
|
21
|
-
def spoofed?
|
22
|
-
extension? && media_type_mismatch?
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def extension?
|
28
|
-
# the following code replaced File.extname(@file_name).present? because it cannot
|
29
|
-
# return the extension of a extension-only file names, e.g. '.html', '.jpg' etc
|
30
|
-
@file_name.split('.').length > 1
|
31
|
-
end
|
32
|
-
|
33
|
-
def media_type_mismatch?
|
34
|
-
supplied_media_types.none? { |type| type == detected_media_type }
|
35
|
-
end
|
36
|
-
|
37
|
-
def supplied_media_types
|
38
|
-
MIME::Types.type_for(@file_name).collect(&:media_type)
|
39
|
-
end
|
40
|
-
|
41
|
-
def detected_media_type
|
42
|
-
@content_type.split('/').first
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
require 'tempfile'
|
5
|
-
|
6
|
-
describe FileValidators::Utils::ContentTypeDetector do
|
7
|
-
it 'returns the empty content type when the file is empty' do
|
8
|
-
tempfile = Tempfile.new('empty')
|
9
|
-
expect(described_class.new(tempfile.path, tempfile.path).detect).to eql('inode/x-empty')
|
10
|
-
tempfile.close
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'returns a content type based on the content of the file' do
|
14
|
-
tempfile = Tempfile.new('something')
|
15
|
-
tempfile.write('This is a file.')
|
16
|
-
tempfile.rewind
|
17
|
-
expect(described_class.new(tempfile.path, tempfile.path).detect).to eql('text/plain')
|
18
|
-
tempfile.close
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'returns a sensible default when the file path is empty' do
|
22
|
-
expect(described_class.new('', '').detect).to eql('application/octet-stream')
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'returns a sensible default if the file path is invalid' do
|
26
|
-
file_path = '/path/to/nothing'
|
27
|
-
expect(described_class.new(file_path, file_path).detect).to eql('application/octet-stream')
|
28
|
-
end
|
29
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe FileValidators::Utils::MediaTypeSpoofDetector do
|
6
|
-
it 'rejects a file with an extension .html and identifies as jpeg' do
|
7
|
-
expect(described_class.new('image/jpeg', 'sample.html')).to be_spoofed
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'does not reject a file with an extension .jpg and identifies as png' do
|
11
|
-
expect(described_class.new('image/png', 'sample.jpg')).not_to be_spoofed
|
12
|
-
end
|
13
|
-
|
14
|
-
it 'does not reject a file with an extension .txt and identifies as text' do
|
15
|
-
expect(described_class.new('text/plain', 'sample.txt')).not_to be_spoofed
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'does not reject a file that does not have any name' do
|
19
|
-
expect(described_class.new('text/plain', '')).not_to be_spoofed
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'does not reject a file that does not have any extension' do
|
23
|
-
expect(described_class.new('text/plain', 'sample')).not_to be_spoofed
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'rejects a file that does not have a basename but has an extension with mismatched media type' do
|
27
|
-
expect(described_class.new('image/jpeg', '.html')).to be_spoofed
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'does not reject a file that does not have a basename but has an extension with valid media type' do
|
31
|
-
expect(described_class.new('image/png', '.jpg')).not_to be_spoofed
|
32
|
-
end
|
33
|
-
end
|