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 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