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