file_validators 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +32 -0
  3. data/.tool-versions +1 -0
  4. data/.travis.yml +36 -6
  5. data/Appraisals +8 -8
  6. data/CHANGELOG.md +20 -0
  7. data/Gemfile +2 -0
  8. data/README.md +21 -16
  9. data/Rakefile +3 -1
  10. data/file_validators.gemspec +12 -6
  11. data/gemfiles/activemodel_3.2.gemfile +0 -0
  12. data/gemfiles/activemodel_4.0.gemfile +0 -0
  13. data/gemfiles/activemodel_5.0.gemfile +0 -0
  14. data/gemfiles/{activemodel_4.1.gemfile → activemodel_6.0.gemfile} +1 -2
  15. data/gemfiles/{activemodel_4.2.gemfile → activemodel_6.1.gemfile} +1 -2
  16. data/lib/file_validators/error.rb +6 -0
  17. data/lib/file_validators/mime_type_analyzer.rb +106 -0
  18. data/lib/file_validators/validators/file_content_type_validator.rb +33 -42
  19. data/lib/file_validators/validators/file_size_validator.rb +43 -19
  20. data/lib/file_validators/version.rb +3 -1
  21. data/lib/file_validators.rb +6 -7
  22. data/spec/integration/combined_validators_integration_spec.rb +3 -1
  23. data/spec/integration/file_content_type_validation_integration_spec.rb +73 -17
  24. data/spec/integration/file_size_validator_integration_spec.rb +43 -16
  25. data/spec/lib/file_validators/mime_type_analyzer_spec.rb +139 -0
  26. data/spec/lib/file_validators/validators/file_content_type_validator_spec.rb +90 -32
  27. data/spec/lib/file_validators/validators/file_size_validator_spec.rb +78 -30
  28. data/spec/spec_helper.rb +4 -0
  29. data/spec/support/fakeio.rb +17 -0
  30. data/spec/support/helpers.rb +7 -0
  31. data/spec/support/matchers/allow_content_type.rb +2 -0
  32. data/spec/support/matchers/allow_file_size.rb +2 -0
  33. metadata +82 -23
  34. data/lib/file_validators/utils/content_type_detector.rb +0 -67
  35. data/lib/file_validators/utils/media_type_spoof_detector.rb +0 -46
  36. data/spec/lib/file_validators/utils/content_type_detector_spec.rb +0 -27
  37. data/spec/lib/file_validators/utils/media_type_spoof_detector_spec.rb +0 -31
@@ -1,67 +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. invalid file_path: returns 'application/octet-stream'
26
- # 2. empty file: returns 'inode/x-empty'
27
- # 3. valid file: returns the content type using file command
28
- # 4. valid file but file commoand raises error: returns 'application/octet-stream'
29
-
30
- def detect
31
- if !File.exist?(file_path)
32
- DEFAULT_CONTENT_TYPE
33
- elsif File.zero?(file_path)
34
- EMPTY_CONTENT_TYPE
35
- else
36
- content_type_from_content
37
- end
38
- end
39
-
40
- private
41
-
42
- def content_type_from_content
43
- content_type = type_from_file_command
44
-
45
- if FileValidators::Utils::MediaTypeSpoofDetector.new(content_type, file_name).spoofed?
46
- logger.warn('A file with a spoofed media type has been detected by the file validators.')
47
- else
48
- content_type
49
- end
50
- end
51
-
52
- def type_from_file_command
53
- begin
54
- Cocaine::CommandLine.new('file', '-b --mime-type :file').run(file: @file_path).strip
55
- rescue Cocaine::CommandLineError => e
56
- logger.info(e.message)
57
- DEFAULT_CONTENT_TYPE
58
- end
59
- end
60
-
61
- def logger
62
- Logger.new(STDOUT)
63
- end
64
- end
65
-
66
- end
67
- 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