file_validators 3.0.0.beta1 → 3.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|