file_validators 2.1.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/.gitignore +2 -0
- data/.rubocop.yml +32 -0
- data/.tool-versions +1 -0
- data/.travis.yml +43 -7
- data/Appraisals +13 -13
- data/CHANGELOG.md +31 -0
- data/Gemfile +2 -0
- data/README.md +26 -18
- data/Rakefile +3 -1
- data/file_validators.gemspec +13 -7
- data/gemfiles/activemodel_3.2.gemfile +2 -1
- data/gemfiles/activemodel_4.0.gemfile +2 -1
- data/gemfiles/{activemodel_4.2.gemfile → activemodel_5.0.gemfile} +1 -1
- data/gemfiles/{activemodel_4.1.gemfile → activemodel_6.0.gemfile} +1 -1
- data/gemfiles/{activemodel_3.0.gemfile → activemodel_6.1.gemfile} +1 -1
- data/lib/file_validators.rb +6 -7
- 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 +37 -33
- data/lib/file_validators/validators/file_size_validator.rb +62 -19
- data/lib/file_validators/version.rb +3 -1
- data/spec/integration/combined_validators_integration_spec.rb +3 -1
- data/spec/integration/file_content_type_validation_integration_spec.rb +117 -11
- data/spec/integration/file_size_validator_integration_spec.rb +100 -10
- 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 +84 -30
- data/spec/spec_helper.rb +5 -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 +86 -28
- data/CHANGELOG.mod +0 -6
- data/gemfiles/activemodel_3.1.gemfile +0 -8
- data/lib/file_validators/utils/content_type_detector.rb +0 -64
- 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
data/CHANGELOG.mod
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
require 'logger'
|
2
|
-
|
3
|
-
begin
|
4
|
-
require 'cocaine'
|
5
|
-
rescue LoadError
|
6
|
-
puts "file_validators requires 'cocaine' gem as you are using file content type validations in strict mode"
|
7
|
-
end
|
8
|
-
|
9
|
-
module FileValidators
|
10
|
-
module Utils
|
11
|
-
|
12
|
-
class ContentTypeDetector
|
13
|
-
EMPTY_CONTENT_TYPE = 'inode/x-empty'
|
14
|
-
DEFAULT_CONTENT_TYPE = 'application/octet-stream'
|
15
|
-
|
16
|
-
attr_accessor :file_path, :file_name
|
17
|
-
|
18
|
-
def initialize(file_path, file_name)
|
19
|
-
@file_path = file_path
|
20
|
-
@file_name = file_name
|
21
|
-
end
|
22
|
-
|
23
|
-
# content type detection strategy:
|
24
|
-
#
|
25
|
-
# 1. empty file: returns 'inode/x-empty'
|
26
|
-
# 2. nonempty file: if the file is not empty then returns the content type using file command
|
27
|
-
# 3. invalid file: file command raises error and returns 'application/octet-stream'
|
28
|
-
|
29
|
-
def detect
|
30
|
-
empty_file? ? EMPTY_CONTENT_TYPE : content_type_from_content
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def empty_file?
|
36
|
-
File.exists?(file_path) && File.size(file_path) == 0
|
37
|
-
end
|
38
|
-
|
39
|
-
def content_type_from_content
|
40
|
-
content_type = type_from_file_command
|
41
|
-
|
42
|
-
if FileValidators::Utils::MediaTypeSpoofDetector.new(content_type, file_name).spoofed?
|
43
|
-
logger.warn('A file with a spoofed media type has been detected by the file validators.')
|
44
|
-
else
|
45
|
-
content_type
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def type_from_file_command
|
50
|
-
begin
|
51
|
-
Cocaine::CommandLine.new('file', '-b --mime-type :file').run(file: @file_path).strip
|
52
|
-
rescue Cocaine::CommandLineError => e
|
53
|
-
logger.info(e.message)
|
54
|
-
DEFAULT_CONTENT_TYPE
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def logger
|
59
|
-
Logger.new(STDOUT)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
require 'mime/types'
|
2
|
-
|
3
|
-
module FileValidators
|
4
|
-
module Utils
|
5
|
-
|
6
|
-
class MediaTypeSpoofDetector
|
7
|
-
def initialize(content_type, file_name)
|
8
|
-
@content_type = content_type
|
9
|
-
@file_name = file_name
|
10
|
-
end
|
11
|
-
|
12
|
-
# media type spoof detection strategy:
|
13
|
-
#
|
14
|
-
# 1. it will not identify as spoofed if file name doesn't have any extension
|
15
|
-
# 2. it will identify as spoofed if any of the file extension's media types
|
16
|
-
# matches the media type of the content type. So it will return true for
|
17
|
-
# `text` of `text/plain` mismatch with `image` of `image/jpeg`, but return false
|
18
|
-
# for `image` of `image/png` match with `image` of `image/jpeg`.
|
19
|
-
|
20
|
-
def spoofed?
|
21
|
-
has_extension? and media_type_mismatch?
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def has_extension?
|
27
|
-
# the following code replaced File.extname(@file_name).present? because it cannot
|
28
|
-
# return the extension of a extension-only file names, e.g. '.html', '.jpg' etc
|
29
|
-
@file_name.split('.').length > 1
|
30
|
-
end
|
31
|
-
|
32
|
-
def media_type_mismatch?
|
33
|
-
supplied_media_types.none? { |type| type == detected_media_type }
|
34
|
-
end
|
35
|
-
|
36
|
-
def supplied_media_types
|
37
|
-
MIME::Types.type_for(@file_name).collect(&:media_type)
|
38
|
-
end
|
39
|
-
|
40
|
-
def detected_media_type
|
41
|
-
@content_type.split('/').first
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
end
|
46
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'tempfile'
|
3
|
-
|
4
|
-
describe FileValidators::Utils::ContentTypeDetector do
|
5
|
-
it 'returns the empty content type when the file is empty' do
|
6
|
-
tempfile = Tempfile.new('empty')
|
7
|
-
expect(described_class.new(tempfile.path, tempfile.path).detect).to eql('inode/x-empty')
|
8
|
-
tempfile.close
|
9
|
-
end
|
10
|
-
|
11
|
-
it 'returns a content type based on the content of the file' do
|
12
|
-
tempfile = Tempfile.new('something')
|
13
|
-
tempfile.write('This is a file.')
|
14
|
-
tempfile.rewind
|
15
|
-
expect(described_class.new(tempfile.path, tempfile.path).detect).to eql('text/plain')
|
16
|
-
tempfile.close
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'returns a sensible default when the file path is empty' do
|
20
|
-
expect(described_class.new('', '').detect).to eql('application/octet-stream')
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'returns a sensible default if the file path is invalid' do
|
24
|
-
file_path = '/path/to/nothing'
|
25
|
-
expect(described_class.new(file_path, file_path).detect).to eql('application/octet-stream')
|
26
|
-
end
|
27
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe FileValidators::Utils::MediaTypeSpoofDetector do
|
4
|
-
it 'rejects a file with an extension .html and identifies as jpeg' do
|
5
|
-
expect(described_class.new('image/jpeg', 'sample.html')).to be_spoofed
|
6
|
-
end
|
7
|
-
|
8
|
-
it 'does not reject a file with an extension .jpg and identifies as png' do
|
9
|
-
expect(described_class.new('image/png', 'sample.jpg')).not_to be_spoofed
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'does not reject a file with an extension .txt and identifies as text' do
|
13
|
-
expect(described_class.new('text/plain', 'sample.txt')).not_to be_spoofed
|
14
|
-
end
|
15
|
-
|
16
|
-
it 'does not reject a file that does not have any name' do
|
17
|
-
expect(described_class.new('text/plain', '')).not_to be_spoofed
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'does not reject a file that does not have any extension' do
|
21
|
-
expect(described_class.new('text/plain', 'sample')).not_to be_spoofed
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'rejects a file that does not have a basename but has an extension with mismatched media type' do
|
25
|
-
expect(described_class.new('image/jpeg', '.html')).to be_spoofed
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'does not reject a file that does not have a basename but has an extension with valid media type' do
|
29
|
-
expect(described_class.new('image/png', '.jpg')).not_to be_spoofed
|
30
|
-
end
|
31
|
-
end
|