file_validators 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e8e04639d14acd35e4d98938b786d372f5218bdd
4
- data.tar.gz: b045b75d86de521365bece751da3b3c24ea890c8
3
+ metadata.gz: b2c81af7c7d23418b0c6e37eea387759dda91a48
4
+ data.tar.gz: a50cac956a66ac32de2c01b15ffe3be6b5acfbfe
5
5
  SHA512:
6
- metadata.gz: a57497456ee76a9f96f1d74e3e5599e43d8d9e68ab120c0bd6b8fe6945a643cc04acb7b97c50ec65f33b34c1352a346bf6564f0c695b67940e10a459b2f5be19
7
- data.tar.gz: 4fd2174b5f6a7dae46f0f6ada517f4cb918279286918425948c845dd67d0a744f4e6086eb68ee4b2add6da9849c3a0d2033efa8559e664cdc11c828161dbe9c4
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
- [![Gem Version](http://img.shields.io/gem/v/file_validators.svg)](https://rubygems.org/gems/file_validators)
3
+ [![Gem Version](https://badge.fury.io/rb/file_validators.svg)](http://badge.fury.io/rb/file_validators)
4
4
  [![Build Status](https://travis-ci.org/musaffa/file_validators.svg)](https://travis-ci.org/musaffa/file_validators)
5
- [![Dependency Status](http://img.shields.io/gemnasium/musaffa/file_validators.svg)](https://gemnasium.com/musaffa/file_validators)
6
- [![Coverage Status](http://img.shields.io/coveralls/musaffa/file_validators.svg)](https://coveralls.io/r/musaffa/file_validators)
7
- [![Code Climate](http://img.shields.io/codeclimate/github/musaffa/file_validators.svg)](https://codeclimate.com/github/musaffa/file_validators)
5
+ [![Dependency Status](https://gemnasium.com/musaffa/file_validators.svg)](https://gemnasium.com/musaffa/file_validators)
6
+ [![Coverage Status](https://coveralls.io/repos/musaffa/file_validators/badge.png)](https://coveralls.io/r/musaffa/file_validators)
7
+ [![Code Climate](https://codeclimate.com/github/musaffa/file_validators/badges/gpa.svg)](https://codeclimate.com/github/musaffa/file_validators)
8
+ [![Inline docs](http://inch-ci.org/github/musaffa/file_validators.svg)](http://inch-ci.org/github/musaffa/file_validators)
8
9
 
9
- File Validators gem adds file size and content type validations to ActiveModel. Any module that uses ActiveModel, for example ActiveRecord, can use these file validators.
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', 'image/gif'] }
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', 'image/gif'] }
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 file size should be less than %{count}' }
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: 'document should be within %{min} and %{max}' }
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. Allows all by default.
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', 'image/png', 'text/plain'] }
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: 'should have content type %{types}' }
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 image files' }
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
@@ -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
- content_type = value.content_type
14
- validate_whitelist(record, attribute, content_type)
15
- validate_blacklist(record, attribute, content_type)
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 validate_whitelist(record, attribute, value)
34
- allowed_types = [options_call(record, :allow)].flatten.compact
35
- if allowed_types.present? && allowed_types.none? { |type| type === value }
36
- record.errors.add attribute, :allowed_file_content_types, options.merge(:types => allowed_types.join(', '))
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 validate_blacklist(record, attribute, value)
41
- forbidden_types = [options_call(record, :exclude)].flatten.compact
42
- if forbidden_types.present? && forbidden_types.any? { |type| type === value }
43
- record.errors.add attribute, :excluded_file_content_types, options.merge(:types => forbidden_types.join(', '))
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 options_call(record, key)
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. Can be a single content type
56
- # or an array. Each type can be a String or a Regexp. It should be
57
- # noted that Internet Explorer uploads files with content_types that you
58
- # may not expect. For example, JPEG images are given image/pjpeg and
59
- # PNGs are image/x-png, so keep that in mind when determining how you
60
- # match. Allows all by default.
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).each do |option, value|
35
- unless value.is_a?(Numeric) || value.is_a?(Range) || value.is_a?(Proc)
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)
@@ -1,3 +1,3 @@
1
1
  module FileValidators
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
@@ -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
- it 'does not raise argument error if :in was given' do
151
- expect { build_validator allow: 'image/jpg' }.not_to raise_error
152
- end
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
- it 'does not raise argument error if :exclude was given' do
155
- expect { build_validator exclude: 'image/jpg' }.not_to raise_error
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 #{argument} was given" do
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 'does not raise argument error if :in was given' do
193
- expect { build_validator in: (5.kilobytes..10.kilobytes) }.not_to raise_error
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.1.0
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 00:00:00.000000000 Z
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