file_validators 2.0.2 → 2.1.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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +10 -2
- data/CHANGELOG.mod +6 -0
- data/README.md +8 -6
- data/lib/file_validators.rb +12 -2
- data/lib/file_validators/locale/en.yml +0 -2
- data/lib/file_validators/utils/content_type_detector.rb +28 -10
- data/lib/file_validators/validators/file_content_type_validator.rb +2 -11
- data/lib/file_validators/version.rb +1 -1
- data/spec/lib/file_validators/utils/content_type_detector_spec.rb +5 -5
- data/spec/lib/file_validators/utils/media_type_spoof_detector_spec.rb +7 -7
- data/spec/lib/file_validators/validators/file_content_type_validator_spec.rb +3 -4
- data/spec/lib/file_validators/validators/file_size_validator_spec.rb +3 -4
- data/spec/spec_helper.rb +4 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 451e981eb130a49dc6c51834bd7ed4dd30652c9a
|
4
|
+
data.tar.gz: e6b28941ccf9ced999b0e201ad17738efb8bc767
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c40fa1dae6faf187cc4d3ba77db40691b9bfe58fe9fa99bc88b9fc36477b49ef06bd360ffa8a8af4e2d6f4d56421259b9d9623ab3732a06883b8e64c3f57eb4
|
7
|
+
data.tar.gz: 4c6aed01f6c15c42c3f917dcdbbbb4c28907ed662a16f823e33d7416005122e159f2504d4991f5fd4b7b20405b332566a1d52137164f224ce5c1504df1c45407
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
language: ruby
|
2
|
+
|
2
3
|
rvm:
|
3
|
-
-
|
4
|
-
- 2.2.
|
4
|
+
- 2.0
|
5
|
+
- 2.2.3
|
6
|
+
- ruby-head
|
7
|
+
- jruby-9.0.4.0
|
8
|
+
|
5
9
|
gemfile:
|
6
10
|
- gemfiles/activemodel_4.2.gemfile
|
7
11
|
- gemfiles/activemodel_4.1.gemfile
|
@@ -9,3 +13,7 @@ gemfile:
|
|
9
13
|
- gemfiles/activemodel_3.2.gemfile
|
10
14
|
- gemfiles/activemodel_3.1.gemfile
|
11
15
|
- gemfiles/activemodel_3.0.gemfile
|
16
|
+
|
17
|
+
matrix:
|
18
|
+
allow_failures:
|
19
|
+
- rvm: ruby-head
|
data/CHANGELOG.mod
ADDED
data/README.md
CHANGED
@@ -191,8 +191,6 @@ File Size Errors
|
|
191
191
|
* `file_size_is_greater_than_or_equal_to`: takes `count` as replacement
|
192
192
|
|
193
193
|
Content Type Errors
|
194
|
-
* `spoofed_file_media_type`: generated when file media type from its extension doesn't match the media type of its
|
195
|
-
content. learn more from [security](#Security).
|
196
194
|
* `allowed_file_content_types`: generated when you have specified allowed types but the content type
|
197
195
|
of the file doesn't match. takes `types` as replacement.
|
198
196
|
* `excluded_file_content_types`: generated when you have specified excluded types and the content type
|
@@ -249,10 +247,14 @@ uploaders start processing a file immediately after its assignment (even before
|
|
249
247
|
|
250
248
|
## Tests
|
251
249
|
|
252
|
-
```
|
253
|
-
rake
|
254
|
-
rake test:unit
|
255
|
-
rake test:integration
|
250
|
+
```Shell
|
251
|
+
$ rake
|
252
|
+
$ rake test:unit
|
253
|
+
$ rake test:integration
|
254
|
+
|
255
|
+
# test different active model versions
|
256
|
+
$ appraisal install
|
257
|
+
$ appraisal rake
|
256
258
|
```
|
257
259
|
|
258
260
|
## Problems
|
data/lib/file_validators.rb
CHANGED
@@ -1,6 +1,16 @@
|
|
1
1
|
require 'active_model'
|
2
|
-
require '
|
3
|
-
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module FileValidators
|
5
|
+
module Utils
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
|
8
|
+
autoload :ContentTypeDetector
|
9
|
+
autoload :MediaTypeSpoofDetector
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Dir[File.dirname(__FILE__) + "/file_validators/validators/*.rb"].each { |file| require file }
|
4
14
|
|
5
15
|
locale_path = Dir.glob(File.dirname(__FILE__) + '/file_validators/locale/*.yml')
|
6
16
|
I18n.load_path += locale_path unless I18n.load_path.include?(locale_path)
|
@@ -7,7 +7,5 @@ en:
|
|
7
7
|
file_size_is_greater_than: ! 'file size must be greater than %{count}'
|
8
8
|
file_size_is_greater_than_or_equal_to: ! 'file size must be greater than or equal to %{count}'
|
9
9
|
|
10
|
-
spoofed_file_media_type: file has an extension that does not match its contents
|
11
10
|
allowed_file_content_types: ! 'file should be one of %{types}'
|
12
11
|
excluded_file_content_types: ! 'file cannot be %{types}'
|
13
|
-
|
@@ -1,6 +1,9 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'cocaine'
|
3
5
|
rescue LoadError
|
6
|
+
puts "file_validators requires 'cocaine' gem as you are using file content type validations in strict mode"
|
4
7
|
end
|
5
8
|
|
6
9
|
module FileValidators
|
@@ -10,8 +13,11 @@ module FileValidators
|
|
10
13
|
EMPTY_CONTENT_TYPE = 'inode/x-empty'
|
11
14
|
DEFAULT_CONTENT_TYPE = 'application/octet-stream'
|
12
15
|
|
13
|
-
|
16
|
+
attr_accessor :file_path, :file_name
|
17
|
+
|
18
|
+
def initialize(file_path, file_name)
|
14
19
|
@file_path = file_path
|
20
|
+
@file_name = file_name
|
15
21
|
end
|
16
22
|
|
17
23
|
# content type detection strategy:
|
@@ -21,24 +27,36 @@ module FileValidators
|
|
21
27
|
# 3. invalid file: file command raises error and returns 'application/octet-stream'
|
22
28
|
|
23
29
|
def detect
|
24
|
-
empty_file? ? EMPTY_CONTENT_TYPE :
|
30
|
+
empty_file? ? EMPTY_CONTENT_TYPE : content_type_from_content
|
25
31
|
end
|
26
32
|
|
27
33
|
private
|
28
34
|
|
29
35
|
def empty_file?
|
30
|
-
File.exists?(
|
36
|
+
File.exists?(file_path) && File.size(file_path) == 0
|
31
37
|
end
|
32
38
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
38
52
|
rescue Cocaine::CommandLineError => e
|
39
|
-
|
53
|
+
logger.info(e.message)
|
40
54
|
DEFAULT_CONTENT_TYPE
|
41
|
-
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def logger
|
59
|
+
Logger.new(STDOUT)
|
42
60
|
end
|
43
61
|
end
|
44
62
|
|
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'file_validators/utils/content_type_detector'
|
2
|
-
require 'file_validators/utils/media_type_spoof_detector'
|
3
|
-
|
4
1
|
module ActiveModel
|
5
2
|
module Validations
|
6
3
|
|
@@ -19,7 +16,6 @@ module ActiveModel
|
|
19
16
|
allowed_types = option_content_types(record, :allow)
|
20
17
|
forbidden_types = option_content_types(record, :exclude)
|
21
18
|
|
22
|
-
validate_media_type(record, attribute, content_type, get_file_name(value)) if mode == :strict
|
23
19
|
validate_whitelist(record, attribute, content_type, allowed_types)
|
24
20
|
validate_blacklist(record, attribute, content_type, forbidden_types)
|
25
21
|
end
|
@@ -59,7 +55,8 @@ module ActiveModel
|
|
59
55
|
case mode
|
60
56
|
when :strict
|
61
57
|
file_path = get_file_path(value)
|
62
|
-
|
58
|
+
file_name = get_file_name(value)
|
59
|
+
FileValidators::Utils::ContentTypeDetector.new(file_path, file_name).detect
|
63
60
|
when :relaxed
|
64
61
|
file_name = get_file_name(value)
|
65
62
|
MIME::Types.type_for(file_name).first
|
@@ -77,12 +74,6 @@ module ActiveModel
|
|
77
74
|
options[key].is_a?(Proc) ? options[key].call(record) : options[key]
|
78
75
|
end
|
79
76
|
|
80
|
-
def validate_media_type(record, attribute, content_type, file_name)
|
81
|
-
if FileValidators::Utils::MediaTypeSpoofDetector.new(content_type, file_name).spoofed?
|
82
|
-
record.errors.add attribute, :spoofed_file_media_type
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
77
|
def validate_whitelist(record, attribute, content_type, allowed_types)
|
87
78
|
if allowed_types.present? and allowed_types.none? { |type| type === content_type }
|
88
79
|
mark_invalid record, attribute, :allowed_file_content_types, allowed_types
|
@@ -4,7 +4,7 @@ require 'tempfile'
|
|
4
4
|
describe FileValidators::Utils::ContentTypeDetector do
|
5
5
|
it 'returns the empty content type when the file is empty' do
|
6
6
|
tempfile = Tempfile.new('empty')
|
7
|
-
expect(
|
7
|
+
expect(described_class.new(tempfile.path, tempfile.path).detect).to eql('inode/x-empty')
|
8
8
|
tempfile.close
|
9
9
|
end
|
10
10
|
|
@@ -12,16 +12,16 @@ describe FileValidators::Utils::ContentTypeDetector do
|
|
12
12
|
tempfile = Tempfile.new('something')
|
13
13
|
tempfile.write('This is a file.')
|
14
14
|
tempfile.rewind
|
15
|
-
expect(
|
15
|
+
expect(described_class.new(tempfile.path, tempfile.path).detect).to eql('text/plain')
|
16
16
|
tempfile.close
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'returns a sensible default when the file path is empty' do
|
20
|
-
expect(
|
20
|
+
expect(described_class.new('', '').detect).to eql('application/octet-stream')
|
21
21
|
end
|
22
22
|
|
23
23
|
it 'returns a sensible default if the file path is invalid' do
|
24
|
-
|
25
|
-
expect(
|
24
|
+
file_path = '/path/to/nothing'
|
25
|
+
expect(described_class.new(file_path, file_path).detect).to eql('application/octet-stream')
|
26
26
|
end
|
27
27
|
end
|
@@ -2,30 +2,30 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe FileValidators::Utils::MediaTypeSpoofDetector do
|
4
4
|
it 'rejects a file with an extension .html and identifies as jpeg' do
|
5
|
-
expect(
|
5
|
+
expect(described_class.new('image/jpeg', 'sample.html')).to be_spoofed
|
6
6
|
end
|
7
7
|
|
8
8
|
it 'does not reject a file with an extension .jpg and identifies as png' do
|
9
|
-
expect(
|
9
|
+
expect(described_class.new('image/png', 'sample.jpg')).not_to be_spoofed
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'does not reject a file with an extension .txt and identifies as text' do
|
13
|
-
expect(
|
13
|
+
expect(described_class.new('text/plain', 'sample.txt')).not_to be_spoofed
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'does not reject a file that does not have any name' do
|
17
|
-
expect(
|
17
|
+
expect(described_class.new('text/plain', '')).not_to be_spoofed
|
18
18
|
end
|
19
19
|
|
20
20
|
it 'does not reject a file that does not have any extension' do
|
21
|
-
expect(
|
21
|
+
expect(described_class.new('text/plain', 'sample')).not_to be_spoofed
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'rejects a file that does not have a basename but has an extension with mismatched media type' do
|
25
|
-
expect(
|
25
|
+
expect(described_class.new('image/jpeg', '.html')).to be_spoofed
|
26
26
|
end
|
27
27
|
|
28
28
|
it 'does not reject a file that does not have a basename but has an extension with valid media type' do
|
29
|
-
expect(
|
29
|
+
expect(described_class.new('image/png', '.jpg')).not_to be_spoofed
|
30
30
|
end
|
31
31
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'file_validators/validators/file_content_type_validator'
|
3
2
|
|
4
3
|
describe ActiveModel::Validations::FileContentTypeValidator do
|
5
4
|
class Dummy
|
@@ -9,7 +8,7 @@ describe ActiveModel::Validations::FileContentTypeValidator do
|
|
9
8
|
subject { Dummy }
|
10
9
|
|
11
10
|
def build_validator(options)
|
12
|
-
@validator =
|
11
|
+
@validator = described_class.new(options.merge(attributes: :avatar))
|
13
12
|
end
|
14
13
|
|
15
14
|
context 'whitelist format' do
|
@@ -138,7 +137,7 @@ describe ActiveModel::Validations::FileContentTypeValidator do
|
|
138
137
|
before { Dummy.validates_file_content_type :avatar, allow: 'image/jpg' }
|
139
138
|
|
140
139
|
it 'adds the validator to the class' do
|
141
|
-
expect(Dummy.validators_on(:avatar)).to include(
|
140
|
+
expect(Dummy.validators_on(:avatar)).to include(described_class)
|
142
141
|
end
|
143
142
|
end
|
144
143
|
|
@@ -147,7 +146,7 @@ describe ActiveModel::Validations::FileContentTypeValidator do
|
|
147
146
|
expect { build_validator message: 'Some message' }.to raise_error(ArgumentError)
|
148
147
|
end
|
149
148
|
|
150
|
-
|
149
|
+
described_class::CHECKS.each do |argument|
|
151
150
|
it "does not raise error if :#{argument} is string, array, regexp or a proc" do
|
152
151
|
expect { build_validator argument => 'image/jpg' }.not_to raise_error
|
153
152
|
expect { build_validator argument => ['image/jpg'] }.not_to raise_error
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'file_validators/validators/file_size_validator'
|
3
2
|
|
4
3
|
describe ActiveModel::Validations::FileSizeValidator do
|
5
4
|
class Dummy
|
@@ -21,7 +20,7 @@ describe ActiveModel::Validations::FileSizeValidator do
|
|
21
20
|
subject { Dummy }
|
22
21
|
|
23
22
|
def build_validator(options)
|
24
|
-
@validator =
|
23
|
+
@validator = described_class.new(options.merge(attributes: :avatar))
|
25
24
|
end
|
26
25
|
|
27
26
|
context 'with :in option' do
|
@@ -174,7 +173,7 @@ describe ActiveModel::Validations::FileSizeValidator do
|
|
174
173
|
before { Dummy.validates_file_size :avatar, in: (5.kilobytes..10.kilobytes) }
|
175
174
|
|
176
175
|
it 'adds the validator to the class' do
|
177
|
-
expect(Dummy.validators_on(:avatar)).to include(
|
176
|
+
expect(Dummy.validators_on(:avatar)).to include(described_class)
|
178
177
|
end
|
179
178
|
end
|
180
179
|
|
@@ -183,7 +182,7 @@ describe ActiveModel::Validations::FileSizeValidator do
|
|
183
182
|
expect { build_validator message: 'Some message' }.to raise_error(ArgumentError)
|
184
183
|
end
|
185
184
|
|
186
|
-
(
|
185
|
+
(described_class::CHECKS.keys - [:in]).each do |argument|
|
187
186
|
it "does not raise argument error if :#{argument} is numeric or a proc" do
|
188
187
|
expect { build_validator argument => 5.kilobytes }.not_to raise_error
|
189
188
|
expect { build_validator argument => lambda { |record| 5.kilobytes } }.not_to raise_error
|
data/spec/spec_helper.rb
CHANGED
@@ -2,7 +2,7 @@ ENV['RAILS_ENV'] ||= 'test'
|
|
2
2
|
|
3
3
|
require 'active_support'
|
4
4
|
require 'active_support/core_ext'
|
5
|
-
|
5
|
+
require 'file_validators'
|
6
6
|
require 'rspec'
|
7
7
|
require 'coveralls'
|
8
8
|
|
@@ -10,8 +10,11 @@ Coveralls.wear!
|
|
10
10
|
|
11
11
|
locale_path = Dir.glob(File.dirname(__FILE__) + '/locale/*.yml')
|
12
12
|
I18n.load_path += locale_path unless I18n.load_path.include?(locale_path)
|
13
|
+
I18n.enforce_available_locales = false
|
13
14
|
|
14
15
|
Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require f }
|
15
16
|
|
16
17
|
RSpec.configure do |config|
|
18
|
+
# Suppress stdout in the console
|
19
|
+
config.before { allow($stdout).to receive(:write) }
|
17
20
|
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: 2.0
|
4
|
+
version: 2.1.0
|
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: 2016-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -119,6 +119,7 @@ files:
|
|
119
119
|
- ".rspec"
|
120
120
|
- ".travis.yml"
|
121
121
|
- Appraisals
|
122
|
+
- CHANGELOG.mod
|
122
123
|
- Gemfile
|
123
124
|
- MIT-LICENSE
|
124
125
|
- README.md
|
@@ -174,7 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
175
|
version: '0'
|
175
176
|
requirements: []
|
176
177
|
rubyforge_project:
|
177
|
-
rubygems_version: 2.
|
178
|
+
rubygems_version: 2.5.1
|
178
179
|
signing_key:
|
179
180
|
specification_version: 4
|
180
181
|
summary: ActiveModel file validators
|