file_validators 1.1.0 → 1.2.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/.rspec +1 -0
- data/README.md +34 -13
- data/file_validators.gemspec +2 -0
- data/lib/file_validators/locale/en.yml +1 -0
- data/lib/file_validators/utils/content_type_detector.rb +41 -0
- data/lib/file_validators/utils/media_type_spoof_detector.rb +46 -0
- data/lib/file_validators/validators/file_content_type_validator.rb +61 -19
- data/lib/file_validators/validators/file_size_validator.rb +11 -6
- data/lib/file_validators/version.rb +1 -1
- data/spec/lib/file_validators/utils/content_type_detector_spec.rb +27 -0
- data/spec/lib/file_validators/utils/media_type_spoof_detector_spec.rb +31 -0
- data/spec/lib/file_validators/validators/file_content_type_validator_spec.rb +10 -5
- data/spec/lib/file_validators/validators/file_size_validator_spec.rb +14 -4
- data/spec/support/matchers/allow_content_type.rb +2 -1
- metadata +37 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2c81af7c7d23418b0c6e37eea387759dda91a48
|
4
|
+
data.tar.gz: a50cac956a66ac32de2c01b15ffe3be6b5acfbfe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f6a8749bea73140878064b29e78a79b938076d28b42dcec160f955867d297d8d79cb01e3ec533cdef3571678f85f887071e267734f830fb095038f97b9eadb6
|
7
|
+
data.tar.gz: f1e3491b792d054b3e515944b64b0fe2fdb1bce5f1a62c2d1ad3b54494a8787e9536e8d5b0870a165f477a4b0d67d09c8d105bb376ca04e03eae387c4673731c
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/README.md
CHANGED
@@ -1,18 +1,23 @@
|
|
1
1
|
# File Validators
|
2
2
|
|
3
|
-
[](http://badge.fury.io/rb/file_validators)
|
4
4
|
[](https://travis-ci.org/musaffa/file_validators)
|
5
|
-
[](https://gemnasium.com/musaffa/file_validators)
|
6
|
+
[](https://coveralls.io/r/musaffa/file_validators)
|
7
|
+
[](https://codeclimate.com/github/musaffa/file_validators)
|
8
|
+
[](http://inch-ci.org/github/musaffa/file_validators)
|
8
9
|
|
9
|
-
File Validators gem adds file size and content type validations to ActiveModel.
|
10
|
+
File Validators gem adds file size and content type validations to ActiveModel.
|
11
|
+
Any module that uses ActiveModel, for example ActiveRecord, can use these file validators.
|
10
12
|
|
11
13
|
## Support
|
12
14
|
|
13
15
|
* ActiveModel versions: 3 and 4.
|
14
16
|
* Rails versions: 3 and 4.
|
15
17
|
|
18
|
+
It has been tested to work with Carrierwave, Paperclip, Dragonfly etc file uploading solutions.
|
19
|
+
Validations works both before and after uploads.
|
20
|
+
|
16
21
|
## Installation
|
17
22
|
|
18
23
|
Add the following to your Gemfile:
|
@@ -31,7 +36,7 @@ class Profile
|
|
31
36
|
|
32
37
|
attr_accessor :avatar
|
33
38
|
validates :avatar, file_size: { less_than_or_equal_to: 100.kilobytes },
|
34
|
-
file_content_type: { allow: ['image/jpeg', 'image/png'
|
39
|
+
file_content_type: { allow: ['image/jpeg', 'image/png'] }
|
35
40
|
end
|
36
41
|
```
|
37
42
|
ActiveRecord example:
|
@@ -39,10 +44,12 @@ ActiveRecord example:
|
|
39
44
|
```ruby
|
40
45
|
class Profile < ActiveRecord::Base
|
41
46
|
validates :avatar, file_size: { less_than_or_equal_to: 100.kilobytes },
|
42
|
-
file_content_type: { allow: ['image/jpeg', 'image/png'
|
47
|
+
file_content_type: { allow: ['image/jpeg', 'image/png'] }
|
43
48
|
end
|
44
49
|
```
|
45
50
|
|
51
|
+
You can also use `:validates_file_size` and `:validates_file_content_type` idioms.
|
52
|
+
|
46
53
|
## API
|
47
54
|
|
48
55
|
### File Size Validator:
|
@@ -73,11 +80,11 @@ With `:in` you will get `min` and `max` as replacements.
|
|
73
80
|
You can write error messages without using any replacement.
|
74
81
|
```ruby
|
75
82
|
validates :avatar, file_size: { less_than: 100.kilobytes,
|
76
|
-
message: 'avatar
|
83
|
+
message: 'avatar should be less than %{count}' }
|
77
84
|
```
|
78
85
|
```ruby
|
79
86
|
validates :document, file_size: { in: 1.kilobyte..1.megabyte,
|
80
|
-
message: '
|
87
|
+
message: 'must be within %{min} and %{max}' }
|
81
88
|
```
|
82
89
|
* `if`: A lambda or name of an instance method. Validation will only be run if this lambda or method returns true.
|
83
90
|
* `unless`: Same as `if` but validates if lambda or method returns false.
|
@@ -103,14 +110,14 @@ validates :avatar, file_size: { less_than: lambda { |record| record.size_in_byte
|
|
103
110
|
|
104
111
|
### File Content Type Validator
|
105
112
|
|
106
|
-
* `allow`: Allowed content types. Can be a single content type or an array. Each type can be a String or a Regexp. It also accepts proc
|
113
|
+
* `allow`: Allowed content types. Can be a single content type or an array. Each type can be a String or a Regexp. It also accepts `proc`. Allows all by default.
|
107
114
|
```ruby
|
108
115
|
# string
|
109
116
|
validates :avatar, file_content_type: { allow: 'image/jpeg' }
|
110
117
|
```
|
111
118
|
```ruby
|
112
119
|
# array of strings
|
113
|
-
validates :attachment, file_content_type: { allow: ['image/jpeg', '
|
120
|
+
validates :attachment, file_content_type: { allow: ['image/jpeg', 'text/plain'] }
|
114
121
|
```
|
115
122
|
```ruby
|
116
123
|
# regexp
|
@@ -133,11 +140,11 @@ validates :video, file_content_type: { allow: lambda { |record| record.content_t
|
|
133
140
|
You will get `types` as a replacement. You can write error messages without using any replacement.
|
134
141
|
```ruby
|
135
142
|
validates :avatar, file_content_type: { allow: ['image/jpeg', 'image/gif'],
|
136
|
-
message: '
|
143
|
+
message: 'only %{types} are allowed' }
|
137
144
|
```
|
138
145
|
```ruby
|
139
146
|
validates :avatar, file_content_type: { allow: ['image/jpeg', 'image/gif'],
|
140
|
-
message: 'Avatar only allows jpeg and gif
|
147
|
+
message: 'Avatar only allows jpeg and gif' }
|
141
148
|
```
|
142
149
|
* `if`: A lambda or name of an instance method. Validation will only be run is this lambda or method returns true.
|
143
150
|
* `unless`: Same as `if` but validates if lambda or method returns false.
|
@@ -148,6 +155,18 @@ You can combine `:allow` and `:exclude`:
|
|
148
155
|
validates :avatar, file_content_type: { allow: /^image\/.*/, exclude: ['image/png', 'image/gif'] }
|
149
156
|
```
|
150
157
|
|
158
|
+
## Security
|
159
|
+
|
160
|
+
This gem uses file command to get the content type based on the content of the file rather
|
161
|
+
than the extension. This prevents fake content types inserted in the request header.
|
162
|
+
|
163
|
+
It also prevents file media type spoofing. For example, user may upload a .html document as
|
164
|
+
a part of the EXIF header of a valid JPEG file. Content type validator will identify its content type
|
165
|
+
as `image/jpeg` and, without spoof detection, it may pass the validation and be saved as .html document
|
166
|
+
thus exposing your application to a security vulnerability. Media type spoof detector wont let that happen. It will not allow a file having `image/jpeg` content type to be saved as `text/plain`. It checks only media type mismatch, for example `text` of `text/plain` and `image` of `image/jpeg`. So it will not prevent `image/jpeg` from saving as `image/png` as both have the same `image` media type.
|
167
|
+
|
168
|
+
**note**: Media type spoof detection is integrated in the [content type validator](#file-content-type-validator). This means without content type validation spoof detection wont be enabled.
|
169
|
+
|
151
170
|
## i18n Translations
|
152
171
|
|
153
172
|
File Size Errors
|
@@ -158,6 +177,8 @@ File Size Errors
|
|
158
177
|
* `file_size_is_greater_than_or_equal_to`: takes `count` as replacement
|
159
178
|
|
160
179
|
Content Type Errors
|
180
|
+
* `spoofed_file_media_type`: generated when file media type from its extension doesn't match the media type of its
|
181
|
+
content. learn more from [security](#Security).
|
161
182
|
* `allowed_file_content_types`: generated when you have specified allowed types but the content type
|
162
183
|
of the file doesn't match. takes `types` as replacement.
|
163
184
|
* `excluded_file_content_types`: generated when you have specified excluded types and the content type
|
data/file_validators.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.require_paths = ['lib']
|
19
19
|
|
20
20
|
s.add_dependency 'activemodel', '>= 3.0'
|
21
|
+
s.add_dependency 'mime-types', '>= 1.0'
|
22
|
+
s.add_dependency 'cocaine', '~> 0.5.4'
|
21
23
|
|
22
24
|
s.add_development_dependency 'rake'
|
23
25
|
s.add_development_dependency 'rspec', '~> 3.1.0'
|
@@ -7,6 +7,7 @@ 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
|
10
11
|
allowed_file_content_types: ! 'file should be one of %{types}'
|
11
12
|
excluded_file_content_types: ! 'file cannot be %{types}'
|
12
13
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'cocaine'
|
2
|
+
|
3
|
+
module FileValidators
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
class ContentTypeDetector
|
7
|
+
EMPTY_CONTENT_TYPE = 'inode/x-empty'
|
8
|
+
DEFAULT_CONTENT_TYPE = 'application/octet-stream'
|
9
|
+
|
10
|
+
def initialize(file_path)
|
11
|
+
@file_path = file_path
|
12
|
+
end
|
13
|
+
|
14
|
+
# content type detection strategy:
|
15
|
+
#
|
16
|
+
# 1. empty file: returns 'inode/x-empty'
|
17
|
+
# 2. nonempty file: if the file is not empty then returns the content type using file command
|
18
|
+
# 3. invalid file: file command raises error and returns 'application/octet-stream'
|
19
|
+
|
20
|
+
def detect
|
21
|
+
empty_file? ? EMPTY_CONTENT_TYPE : content_type_from_file_command
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def empty_file?
|
27
|
+
File.exists?(@file_path) && File.size(@file_path) == 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def content_type_from_file_command
|
31
|
+
type = begin
|
32
|
+
Cocaine::CommandLine.new('file', '-b --mime-type :file').run(file: @file_path)
|
33
|
+
rescue Cocaine::CommandLineError => e
|
34
|
+
# TODO: log command failure
|
35
|
+
DEFAULT_CONTENT_TYPE
|
36
|
+
end.strip
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,46 @@
|
|
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 file named '.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,7 +1,10 @@
|
|
1
|
+
require 'file_validators/utils/content_type_detector'
|
2
|
+
require 'file_validators/utils/media_type_spoof_detector'
|
3
|
+
|
1
4
|
module ActiveModel
|
2
5
|
module Validations
|
3
6
|
|
4
|
-
class FileContentTypeValidator < EachValidator
|
7
|
+
class FileContentTypeValidator < ActiveModel::EachValidator
|
5
8
|
CHECKS = [:allow, :exclude].freeze
|
6
9
|
|
7
10
|
def self.helper_method_name
|
@@ -10,9 +13,15 @@ module ActiveModel
|
|
10
13
|
|
11
14
|
def validate_each(record, attribute, value)
|
12
15
|
unless value.blank?
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
file_path = get_file_path(value)
|
17
|
+
file_name = get_file_name(value)
|
18
|
+
content_type = detect_content_type(file_path)
|
19
|
+
allowed_types = option_content_types(record, :allow)
|
20
|
+
forbidden_types = option_content_types(record, :exclude)
|
21
|
+
|
22
|
+
validate_media_type(record, attribute, content_type, file_name)
|
23
|
+
validate_whitelist(record, attribute, content_type, allowed_types)
|
24
|
+
validate_blacklist(record, attribute, content_type, forbidden_types)
|
16
25
|
end
|
17
26
|
end
|
18
27
|
|
@@ -30,34 +39,67 @@ module ActiveModel
|
|
30
39
|
|
31
40
|
private
|
32
41
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
42
|
+
def get_file_path(value)
|
43
|
+
if value.try(:path)
|
44
|
+
value.path
|
45
|
+
else
|
46
|
+
raise ArgumentError, 'value must return a file path in order to validate file content type'
|
37
47
|
end
|
38
48
|
end
|
39
49
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
50
|
+
def get_file_name(value)
|
51
|
+
if value.try(:original_filename)
|
52
|
+
value.original_filename
|
53
|
+
else
|
54
|
+
File.basename(get_file_path(value))
|
44
55
|
end
|
45
56
|
end
|
46
57
|
|
47
|
-
def
|
58
|
+
def detect_content_type(file_path)
|
59
|
+
FileValidators::Utils::ContentTypeDetector.new(file_path).detect
|
60
|
+
end
|
61
|
+
|
62
|
+
def option_content_types(record, key)
|
63
|
+
[option_value(record, key)].flatten.compact
|
64
|
+
end
|
65
|
+
|
66
|
+
def option_value(record, key)
|
48
67
|
options[key].is_a?(Proc) ? options[key].call(record) : options[key]
|
49
68
|
end
|
69
|
+
|
70
|
+
def validate_media_type(record, attribute, content_type, file_name)
|
71
|
+
if FileValidators::Utils::MediaTypeSpoofDetector.new(content_type, file_name).spoofed?
|
72
|
+
record.errors.add attribute, :spoofed_file_media_type
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_whitelist(record, attribute, content_type, allowed_types)
|
77
|
+
if allowed_types.present? and allowed_types.none? { |type| type === content_type }
|
78
|
+
mark_invalid record, attribute, :allowed_file_content_types, allowed_types
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def validate_blacklist(record, attribute, content_type, forbidden_types)
|
83
|
+
if forbidden_types.any? { |type| type === content_type }
|
84
|
+
mark_invalid record, attribute, :excluded_file_content_types, forbidden_types
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def mark_invalid(record, attribute, error, option_types)
|
89
|
+
record.errors.add attribute, error, options.merge(types: option_types.join(', '))
|
90
|
+
end
|
50
91
|
end
|
51
92
|
|
52
93
|
module HelperMethods
|
53
94
|
# Places ActiveModel validations on the content type of the file
|
54
95
|
# assigned. The possible options are:
|
55
|
-
# * +allow+: Allowed content types.
|
56
|
-
# or an array.
|
57
|
-
# noted that Internet Explorer uploads
|
58
|
-
# may not expect. For example,
|
59
|
-
# PNGs are image/x-png, so keep
|
60
|
-
#
|
96
|
+
# * +allow+: Allowed content types. Can be a single content type
|
97
|
+
# or an array. Each type can be a String or a Regexp. It can also
|
98
|
+
# be a proc/lambda. It should be noted that Internet Explorer uploads
|
99
|
+
# files with content_types that you may not expect. For example,
|
100
|
+
# JPEG images are given image/pjpeg and PNGs are image/x-png, so keep
|
101
|
+
# that in mind when determining how you match.
|
102
|
+
# Allows all by default.
|
61
103
|
# * +exclude+: Forbidden content types.
|
62
104
|
# * +message+: The message to display when the uploaded file has an invalid
|
63
105
|
# content type.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActiveModel
|
2
2
|
module Validations
|
3
3
|
|
4
|
-
class FileSizeValidator < EachValidator
|
4
|
+
class FileSizeValidator < ActiveModel::EachValidator
|
5
5
|
CHECKS = { in: :===,
|
6
6
|
less_than: :<,
|
7
7
|
less_than_or_equal_to: :<=,
|
@@ -31,15 +31,20 @@ module ActiveModel
|
|
31
31
|
:less_than_or_equal_to, :greater_than and :greater_than_or_equal_to'
|
32
32
|
end
|
33
33
|
|
34
|
-
options.slice(*CHECKS.keys
|
35
|
-
|
36
|
-
raise ArgumentError, ":#{option} must be a number, a range or a proc"
|
37
|
-
end
|
38
|
-
end
|
34
|
+
check_options(Numeric, options.slice(*(CHECKS.keys - [:in])))
|
35
|
+
check_options(Range, options.slice(:in))
|
39
36
|
end
|
40
37
|
|
41
38
|
private
|
42
39
|
|
40
|
+
def check_options(klass, options)
|
41
|
+
options.each do |option, value|
|
42
|
+
unless value.is_a?(klass) || value.is_a?(Proc)
|
43
|
+
raise ArgumentError, ":#{option} must be a #{klass.name.to_s.downcase} or a proc"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
43
48
|
def valid_size?(size, option, option_value)
|
44
49
|
if option_value.is_a?(Range)
|
45
50
|
option_value.send(CHECKS[option], size)
|
@@ -0,0 +1,27 @@
|
|
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(FileValidators::Utils::ContentTypeDetector.new(tempfile).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(FileValidators::Utils::ContentTypeDetector.new(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(FileValidators::Utils::ContentTypeDetector.new('').detect).to eql('application/octet-stream')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns a sensible default if the file path is invalid' do
|
24
|
+
@filename = '/path/to/nothing'
|
25
|
+
expect(FileValidators::Utils::ContentTypeDetector.new(@filename).detect).to eql('application/octet-stream')
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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(FileValidators::Utils::MediaTypeSpoofDetector.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(FileValidators::Utils::MediaTypeSpoofDetector.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(FileValidators::Utils::MediaTypeSpoofDetector.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(FileValidators::Utils::MediaTypeSpoofDetector.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(FileValidators::Utils::MediaTypeSpoofDetector.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(FileValidators::Utils::MediaTypeSpoofDetector.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(FileValidators::Utils::MediaTypeSpoofDetector.new('image/png', '.jpg')).not_to be_spoofed
|
30
|
+
end
|
31
|
+
end
|
@@ -147,12 +147,17 @@ describe ActiveModel::Validations::FileContentTypeValidator do
|
|
147
147
|
expect { build_validator message: 'Some message' }.to raise_error(ArgumentError)
|
148
148
|
end
|
149
149
|
|
150
|
-
|
151
|
-
|
152
|
-
|
150
|
+
ActiveModel::Validations::FileContentTypeValidator::CHECKS.each do |argument|
|
151
|
+
it "does not raise error if :#{argument} is string, array, regexp or a proc" do
|
152
|
+
expect { build_validator argument => 'image/jpg' }.not_to raise_error
|
153
|
+
expect { build_validator argument => ['image/jpg'] }.not_to raise_error
|
154
|
+
expect { build_validator argument => /^image\/.*/ }.not_to raise_error
|
155
|
+
expect { build_validator argument => lambda { |record| 'image/jpg' } }.not_to raise_error
|
156
|
+
end
|
153
157
|
|
154
|
-
|
155
|
-
|
158
|
+
it "raises argument error if :#{argument} is neither a string, array, regexp nor proc" do
|
159
|
+
expect { build_validator argument => 5.kilobytes }.to raise_error(ArgumentError)
|
160
|
+
end
|
156
161
|
end
|
157
162
|
end
|
158
163
|
end
|
@@ -183,14 +183,24 @@ describe ActiveModel::Validations::FileSizeValidator do
|
|
183
183
|
expect { build_validator message: 'Some message' }.to raise_error(ArgumentError)
|
184
184
|
end
|
185
185
|
|
186
|
-
(ActiveModel::Validations::FileSizeValidator::CHECKS.keys).each do |argument|
|
187
|
-
it "does not raise argument error if
|
186
|
+
(ActiveModel::Validations::FileSizeValidator::CHECKS.keys - [:in]).each do |argument|
|
187
|
+
it "does not raise argument error if :#{argument} is numeric or a proc" do
|
188
188
|
expect { build_validator argument => 5.kilobytes }.not_to raise_error
|
189
|
+
expect { build_validator argument => lambda { |record| 5.kilobytes } }.not_to raise_error
|
189
190
|
end
|
191
|
+
|
192
|
+
it "raises error if :#{argument} is neither a number nor a proc" do
|
193
|
+
expect { build_validator argument => 5.kilobytes..10.kilobytes }.to raise_error(ArgumentError)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'does not raise argument error if :in is a range or a proc' do
|
198
|
+
expect { build_validator in: 5.kilobytes..10.kilobytes }.not_to raise_error
|
199
|
+
expect { build_validator in: lambda { |record| 5.kilobytes..10.kilobytes } }.not_to raise_error
|
190
200
|
end
|
191
201
|
|
192
|
-
it '
|
193
|
-
expect { build_validator in:
|
202
|
+
it 'raises error if :in is neither a range nor a proc' do
|
203
|
+
expect { build_validator in: 5.kilobytes }.to raise_error(ArgumentError)
|
194
204
|
end
|
195
205
|
end
|
196
206
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
RSpec::Matchers.define :allow_file_content_type do |content_type, validator, message|
|
2
2
|
match do |model|
|
3
|
-
value = double('file', content_type: content_type)
|
3
|
+
value = double('file', path: content_type, original_filename: content_type)
|
4
4
|
model.any_instance.stub(:read_attribute_for_validation).and_return(value)
|
5
|
+
validator.stub(:detect_content_type).and_return(content_type)
|
5
6
|
dummy = model.new
|
6
7
|
validator.validate(dummy)
|
7
8
|
if message.present?
|
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: 1.
|
4
|
+
version: 1.2.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: 2014-10-
|
11
|
+
date: 2014-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mime-types
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: cocaine
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.5.4
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.5.4
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rake
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -88,6 +116,7 @@ extensions: []
|
|
88
116
|
extra_rdoc_files: []
|
89
117
|
files:
|
90
118
|
- ".gitignore"
|
119
|
+
- ".rspec"
|
91
120
|
- ".travis.yml"
|
92
121
|
- Appraisals
|
93
122
|
- Gemfile
|
@@ -103,6 +132,8 @@ files:
|
|
103
132
|
- gemfiles/activemodel_4.1.gemfile
|
104
133
|
- lib/file_validators.rb
|
105
134
|
- lib/file_validators/locale/en.yml
|
135
|
+
- lib/file_validators/utils/content_type_detector.rb
|
136
|
+
- lib/file_validators/utils/media_type_spoof_detector.rb
|
106
137
|
- lib/file_validators/validators/file_content_type_validator.rb
|
107
138
|
- lib/file_validators/validators/file_size_validator.rb
|
108
139
|
- lib/file_validators/version.rb
|
@@ -113,6 +144,8 @@ files:
|
|
113
144
|
- spec/integration/combined_validators_integration_spec.rb
|
114
145
|
- spec/integration/file_content_type_validation_integration_spec.rb
|
115
146
|
- spec/integration/file_size_validator_integration_spec.rb
|
147
|
+
- spec/lib/file_validators/utils/content_type_detector_spec.rb
|
148
|
+
- spec/lib/file_validators/utils/media_type_spoof_detector_spec.rb
|
116
149
|
- spec/lib/file_validators/validators/file_content_type_validator_spec.rb
|
117
150
|
- spec/lib/file_validators/validators/file_size_validator_spec.rb
|
118
151
|
- spec/locale/en.yml
|
@@ -151,6 +184,8 @@ test_files:
|
|
151
184
|
- spec/integration/combined_validators_integration_spec.rb
|
152
185
|
- spec/integration/file_content_type_validation_integration_spec.rb
|
153
186
|
- spec/integration/file_size_validator_integration_spec.rb
|
187
|
+
- spec/lib/file_validators/utils/content_type_detector_spec.rb
|
188
|
+
- spec/lib/file_validators/utils/media_type_spoof_detector_spec.rb
|
154
189
|
- spec/lib/file_validators/validators/file_content_type_validator_spec.rb
|
155
190
|
- spec/lib/file_validators/validators/file_size_validator_spec.rb
|
156
191
|
- spec/locale/en.yml
|