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