paperclip 3.5.4 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of paperclip might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/LICENSE +1 -3
- data/NEWS +25 -21
- data/README.md +35 -1
- data/features/step_definitions/attachment_steps.rb +2 -0
- data/features/step_definitions/rails_steps.rb +1 -0
- data/lib/paperclip.rb +1 -0
- data/lib/paperclip/attachment.rb +25 -1
- data/lib/paperclip/callbacks.rb +1 -1
- data/lib/paperclip/content_type_detector.rb +1 -13
- data/lib/paperclip/errors.rb +5 -0
- data/lib/paperclip/has_attached_file.rb +5 -0
- data/lib/paperclip/io_adapters/abstract_adapter.rb +1 -1
- data/lib/paperclip/io_adapters/attachment_adapter.rb +4 -4
- data/lib/paperclip/io_adapters/data_uri_adapter.rb +4 -9
- data/lib/paperclip/io_adapters/stringio_adapter.rb +10 -8
- data/lib/paperclip/media_type_spoof_detector.rb +36 -0
- data/lib/paperclip/tempfile_factory.rb +5 -1
- data/lib/paperclip/validators.rb +6 -1
- data/lib/paperclip/validators/attachment_content_type_validator.rb +4 -0
- data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
- data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
- data/lib/paperclip/validators/attachment_presence_validator.rb +4 -0
- data/lib/paperclip/validators/attachment_size_validator.rb +4 -0
- data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
- data/lib/paperclip/version.rb +1 -1
- data/test/attachment_definitions_test.rb +1 -0
- data/test/attachment_test.rb +40 -43
- data/test/content_type_detector_test.rb +0 -10
- data/test/fixtures/empty.html +1 -0
- data/test/has_attached_file_test.rb +3 -1
- data/test/helper.rb +11 -4
- data/test/io_adapters/abstract_adapter_test.rb +1 -0
- data/test/io_adapters/attachment_adapter_test.rb +1 -1
- data/test/io_adapters/data_uri_adapter_test.rb +2 -2
- data/test/io_adapters/file_adapter_test.rb +0 -11
- data/test/io_adapters/http_url_proxy_adapter_test.rb +2 -3
- data/test/io_adapters/stringio_adapter_test.rb +1 -1
- data/test/matchers/have_attached_file_matcher_test.rb +3 -2
- data/test/matchers/validate_attachment_content_type_matcher_test.rb +13 -12
- data/test/matchers/validate_attachment_presence_matcher_test.rb +8 -7
- data/test/matchers/validate_attachment_size_matcher_test.rb +12 -11
- data/test/media_type_spoof_detector_test.rb +28 -0
- data/test/meta_class_test.rb +2 -2
- data/test/schema_test.rb +6 -0
- data/test/storage/fog_test.rb +4 -4
- data/test/storage/s3_test.rb +32 -31
- data/test/tempfile_factory_test.rb +13 -1
- data/test/validators/attachment_file_name_validator_test.rb +162 -0
- data/test/validators/attachment_presence_validator_test.rb +1 -1
- data/test/validators/media_type_spoof_detection_validator_test.rb +12 -0
- data/test/validators_test.rb +43 -3
- metadata +14 -2
data/lib/paperclip/validators.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require 'active_model'
|
2
2
|
require 'active_support/concern'
|
3
3
|
require 'paperclip/validators/attachment_content_type_validator'
|
4
|
+
require 'paperclip/validators/attachment_file_name_validator'
|
4
5
|
require 'paperclip/validators/attachment_presence_validator'
|
5
6
|
require 'paperclip/validators/attachment_size_validator'
|
7
|
+
require 'paperclip/validators/media_type_spoof_detection_validator'
|
8
|
+
require 'paperclip/validators/attachment_file_type_ignorance_validator'
|
6
9
|
|
7
10
|
module Paperclip
|
8
11
|
module Validators
|
@@ -13,6 +16,8 @@ module Paperclip
|
|
13
16
|
include HelperMethods
|
14
17
|
end
|
15
18
|
|
19
|
+
::Paperclip::REQUIRED_VALIDATORS = [AttachmentFileNameValidator, AttachmentContentTypeValidator, AttachmentFileTypeIgnoranceValidator]
|
20
|
+
|
16
21
|
module ClassMethods
|
17
22
|
# This method is a shortcut to validator classes that is in
|
18
23
|
# "Attachment...Validator" format. It is almost the same thing as the
|
@@ -39,7 +44,7 @@ module Paperclip
|
|
39
44
|
local_options = attributes + [validator_options]
|
40
45
|
conditional_options = options.slice(:if, :unless)
|
41
46
|
local_options.last.merge!(conditional_options)
|
42
|
-
send(
|
47
|
+
send(Paperclip::Validators.const_get(constant.to_s).helper_method_name, *local_options)
|
43
48
|
end
|
44
49
|
end
|
45
50
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Paperclip
|
2
|
+
module Validators
|
3
|
+
class AttachmentFileNameValidator < ActiveModel::EachValidator
|
4
|
+
def initialize(options)
|
5
|
+
options[:allow_nil] = true unless options.has_key?(:allow_nil)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.helper_method_name
|
10
|
+
:validates_attachment_file_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate_each(record, attribute, value)
|
14
|
+
base_attribute = attribute.to_sym
|
15
|
+
attribute = "#{attribute}_file_name".to_sym
|
16
|
+
value = record.send :read_attribute_for_validation, attribute
|
17
|
+
|
18
|
+
return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
19
|
+
|
20
|
+
validate_whitelist(record, attribute, value)
|
21
|
+
validate_blacklist(record, attribute, value)
|
22
|
+
|
23
|
+
if record.errors.include? attribute
|
24
|
+
record.errors[attribute].each do |error|
|
25
|
+
record.errors.add base_attribute, error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_whitelist(record, attribute, value)
|
31
|
+
if allowed.present? && allowed.none? { |type| type === value }
|
32
|
+
mark_invalid record, attribute, allowed
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_blacklist(record, attribute, value)
|
37
|
+
if forbidden.present? && forbidden.any? { |type| type === value }
|
38
|
+
mark_invalid record, attribute, forbidden
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def mark_invalid(record, attribute, patterns)
|
43
|
+
record.errors.add attribute, :invalid, options.merge(:names => patterns.join(', '))
|
44
|
+
end
|
45
|
+
|
46
|
+
def allowed
|
47
|
+
[options[:matches]].flatten.compact
|
48
|
+
end
|
49
|
+
|
50
|
+
def forbidden
|
51
|
+
[options[:not]].flatten.compact
|
52
|
+
end
|
53
|
+
|
54
|
+
def check_validity!
|
55
|
+
unless options.has_key?(:matches) || options.has_key?(:not)
|
56
|
+
raise ArgumentError, "You must pass in either :matches or :not to the validator"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module HelperMethods
|
62
|
+
# Places ActiveModel validations on the name of the file
|
63
|
+
# assigned. The possible options are:
|
64
|
+
# * +matches+: Allowed filename patterns as Regexps. Can be a single one
|
65
|
+
# or an array.
|
66
|
+
# * +not+: Forbidden file name patterns, specified the same was as +matches+.
|
67
|
+
# * +message+: The message to display when the uploaded file has an invalid
|
68
|
+
# name.
|
69
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
70
|
+
# be run is this lambda or method returns true.
|
71
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
72
|
+
def validates_attachment_file_name(*attr_names)
|
73
|
+
options = _merge_attributes(attr_names)
|
74
|
+
validates_with AttachmentFileNameValidator, options.dup
|
75
|
+
validate_before_processing AttachmentFileNameValidator, options.dup
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'active_model/validations/presence'
|
2
|
+
|
3
|
+
module Paperclip
|
4
|
+
module Validators
|
5
|
+
class AttachmentFileTypeIgnoranceValidator < ActiveModel::EachValidator
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
# This doesn't do anything. It's just to mark that you don't care about
|
8
|
+
# the file_names or content_types of your incoming attachments.
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.helper_method_name
|
12
|
+
:do_not_validate_attachment_file_type
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module HelperMethods
|
17
|
+
# Places ActiveModel validations on the presence of a file.
|
18
|
+
# Options:
|
19
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
20
|
+
# be run if this lambda or method returns true.
|
21
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
22
|
+
def do_not_validate_attachment_file_type(*attr_names)
|
23
|
+
options = _merge_attributes(attr_names)
|
24
|
+
validates_with AttachmentFileTypeIgnoranceValidator, options.dup
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'active_model/validations/presence'
|
2
|
+
|
3
|
+
module Paperclip
|
4
|
+
module Validators
|
5
|
+
class MediaTypeSpoofDetectionValidator < ActiveModel::EachValidator
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
adapter = Paperclip.io_adapters.for(value)
|
8
|
+
if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename).spoofed?
|
9
|
+
record.errors.add(attribute, :spoofed_media_type)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module HelperMethods
|
15
|
+
# Places ActiveModel validations on the presence of a file.
|
16
|
+
# Options:
|
17
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
18
|
+
# be run if this lambda or method returns true.
|
19
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
20
|
+
def validates_media_type_spoof_detection(*attr_names)
|
21
|
+
options = _merge_attributes(attr_names)
|
22
|
+
validates_with MediaTypeSpoofDetectionValidator, options.dup
|
23
|
+
validate_before_processing MediaTypeSpoofDetectionValidator, options.dup
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/paperclip/version.rb
CHANGED
@@ -5,6 +5,7 @@ class AttachmentDefinitionsTest < Test::Unit::TestCase
|
|
5
5
|
reset_class "Dummy"
|
6
6
|
Dummy.has_attached_file :avatar, {:path => "abc"}
|
7
7
|
Dummy.has_attached_file :other_attachment, {:url => "123"}
|
8
|
+
Dummy.do_not_validate_attachment_file_type :avatar
|
8
9
|
expected = {:avatar => {:path => "abc"}, :other_attachment => {:url => "123"}}
|
9
10
|
|
10
11
|
assert_equal expected, Dummy.attachment_definitions
|
data/test/attachment_test.rb
CHANGED
@@ -165,7 +165,7 @@ class AttachmentTest < Test::Unit::TestCase
|
|
165
165
|
:default_style => 'default style',
|
166
166
|
:url_generator => mock_url_generator_builder)
|
167
167
|
|
168
|
-
|
168
|
+
attachment.as_json
|
169
169
|
assert mock_url_generator_builder.has_generated_url_with_style_name?('default style')
|
170
170
|
end
|
171
171
|
|
@@ -346,11 +346,12 @@ class AttachmentTest < Test::Unit::TestCase
|
|
346
346
|
|
347
347
|
context "An attachment with :hash interpolations" do
|
348
348
|
setup do
|
349
|
-
@file = StringIO.new("
|
349
|
+
@file = StringIO.new("...\n")
|
350
350
|
end
|
351
351
|
|
352
352
|
should "raise if no secret is provided" do
|
353
|
-
|
353
|
+
rebuild_model :path => ":hash"
|
354
|
+
@attachment = Dummy.new.avatar
|
354
355
|
@attachment.assign @file
|
355
356
|
|
356
357
|
assert_raise ArgumentError do
|
@@ -360,29 +361,23 @@ class AttachmentTest < Test::Unit::TestCase
|
|
360
361
|
|
361
362
|
context "when secret is set" do
|
362
363
|
setup do
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
@attachment
|
364
|
+
rebuild_model :path => ":hash",
|
365
|
+
:hash_secret => "w00t",
|
366
|
+
:hash_data => ":class/:attachment/:style/:filename"
|
367
|
+
@attachment = Dummy.new.avatar
|
367
368
|
@attachment.assign @file
|
368
369
|
end
|
369
370
|
|
370
|
-
should "interpolate the hash data" do
|
371
|
-
@attachment.expects(:interpolate).with(@attachment.options[:hash_data],anything).returns("interpolated_stuff")
|
372
|
-
@attachment.hash_key
|
373
|
-
end
|
374
|
-
|
375
371
|
should "result in the correct interpolation" do
|
376
|
-
assert_equal "
|
372
|
+
assert_equal "dummies/avatars/original/data.txt",
|
373
|
+
@attachment.send(:interpolate, @attachment.options[:hash_data])
|
374
|
+
assert_equal "dummies/avatars/thumb/data.txt",
|
375
|
+
@attachment.send(:interpolate, @attachment.options[:hash_data], :thumb)
|
377
376
|
end
|
378
377
|
|
379
378
|
should "result in a correct hash" do
|
380
|
-
assert_equal "
|
381
|
-
|
382
|
-
|
383
|
-
should "generate a hash digest with the correct style" do
|
384
|
-
OpenSSL::HMAC.expects(:hexdigest).with(anything, anything, "fake_models/avatars/1234/medium/1234567890")
|
385
|
-
@attachment.path("medium")
|
379
|
+
assert_equal "e1079a5c34ddbd197ebd0280d07952d98a57fb30", @attachment.path
|
380
|
+
assert_equal "d740189bd3e49ef226fab84c8d45f7ae4126d043", @attachment.path(:thumb)
|
386
381
|
end
|
387
382
|
end
|
388
383
|
end
|
@@ -406,15 +401,16 @@ class AttachmentTest < Test::Unit::TestCase
|
|
406
401
|
|
407
402
|
context "An attachment with a default style and an extension interpolation" do
|
408
403
|
setup do
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
@
|
413
|
-
@file.
|
404
|
+
rebuild_model :path => ":basename.:extension",
|
405
|
+
:styles => { :default => ["100x100", :jpg] },
|
406
|
+
:default_style => :default
|
407
|
+
@attachment = Dummy.new.avatar
|
408
|
+
@file = File.open(fixture_file("5k.png"))
|
409
|
+
@file.stubs(:original_filename).returns("file.png")
|
414
410
|
end
|
415
411
|
should "return the right extension for the path" do
|
416
412
|
@attachment.assign(@file)
|
417
|
-
assert_equal "file.
|
413
|
+
assert_equal "file.jpg", @attachment.path
|
418
414
|
end
|
419
415
|
end
|
420
416
|
|
@@ -983,19 +979,16 @@ class AttachmentTest < Test::Unit::TestCase
|
|
983
979
|
:path => ":rails_root/:attachment/:class/:style/:id/:basename.:extension"
|
984
980
|
})
|
985
981
|
FileUtils.rm_rf("tmp")
|
986
|
-
rebuild_model
|
982
|
+
rebuild_model :styles => { :large => ["400x400", :jpg],
|
983
|
+
:medium => ["100x100", :jpg],
|
984
|
+
:small => ["32x32#", :jpg]},
|
985
|
+
:default_style => :small
|
987
986
|
@instance = Dummy.new
|
988
987
|
@instance.stubs(:id).returns 123
|
989
|
-
|
990
988
|
@file = File.new(fixture_file("uppercase.PNG"), 'rb')
|
991
989
|
|
992
|
-
|
993
|
-
|
994
|
-
:small => ["32x32#", :jpg]},
|
995
|
-
:default_style => :small}
|
996
|
-
@attachment = Paperclip::Attachment.new(:avatar,
|
997
|
-
@instance,
|
998
|
-
styles)
|
990
|
+
@attachment = @instance.avatar
|
991
|
+
|
999
992
|
now = Time.now
|
1000
993
|
Time.stubs(:now).returns(now)
|
1001
994
|
@attachment.assign(@file)
|
@@ -1027,7 +1020,8 @@ class AttachmentTest < Test::Unit::TestCase
|
|
1027
1020
|
rebuild_model
|
1028
1021
|
@instance = Dummy.new
|
1029
1022
|
@instance.stubs(:id).returns 123
|
1030
|
-
@attachment = Paperclip::Attachment.new(:avatar, @instance)
|
1023
|
+
# @attachment = Paperclip::Attachment.new(:avatar, @instance)
|
1024
|
+
@attachment = @instance.avatar
|
1031
1025
|
@file = File.new(fixture_file("5k.png"), 'rb')
|
1032
1026
|
end
|
1033
1027
|
|
@@ -1045,7 +1039,7 @@ class AttachmentTest < Test::Unit::TestCase
|
|
1045
1039
|
|
1046
1040
|
should 'clear out the previous assignment when assigned nil' do
|
1047
1041
|
@attachment.assign(@file)
|
1048
|
-
|
1042
|
+
@attachment.queued_for_write[:original]
|
1049
1043
|
@attachment.assign(nil)
|
1050
1044
|
assert_nil @attachment.queued_for_write[:original]
|
1051
1045
|
end
|
@@ -1093,12 +1087,15 @@ class AttachmentTest < Test::Unit::TestCase
|
|
1093
1087
|
|
1094
1088
|
context "when expecting three styles" do
|
1095
1089
|
setup do
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1090
|
+
rebuild_class :styles => {
|
1091
|
+
:large => ["400x400", :png],
|
1092
|
+
:medium => ["100x100", :gif],
|
1093
|
+
:small => ["32x32#", :jpg]
|
1094
|
+
}
|
1095
|
+
@instance = Dummy.new
|
1096
|
+
@instance.stubs(:id).returns 123
|
1097
|
+
@file = File.new(fixture_file("5k.png"), 'rb')
|
1098
|
+
@attachment = @instance.avatar
|
1102
1099
|
end
|
1103
1100
|
|
1104
1101
|
context "and assigned a file" do
|
@@ -1129,7 +1126,7 @@ class AttachmentTest < Test::Unit::TestCase
|
|
1129
1126
|
[:small, 32, 32, "JPEG"]].each do |style|
|
1130
1127
|
cmd = %Q[identify -format "%w %h %b %m" "#{@attachment.path(style.first)}"]
|
1131
1128
|
out = `#{cmd}`
|
1132
|
-
width, height,
|
1129
|
+
width, height, _size, format = out.split(" ")
|
1133
1130
|
assert_equal style[1].to_s, width.to_s
|
1134
1131
|
assert_equal style[2].to_s, height.to_s
|
1135
1132
|
assert_equal style[3].to_s, format.to_s
|
@@ -18,16 +18,6 @@ class ContentTypeDetectorTest < Test::Unit::TestCase
|
|
18
18
|
assert_equal "video/mp4", Paperclip::ContentTypeDetector.new(@filename).detect
|
19
19
|
end
|
20
20
|
|
21
|
-
should 'find the first result that matches from the official types' do
|
22
|
-
@filename = "/path/to/something.bmp"
|
23
|
-
assert_equal "image/bmp", Paperclip::ContentTypeDetector.new(@filename).detect
|
24
|
-
end
|
25
|
-
|
26
|
-
should 'find the first unofficial result for this filename if no official ones exist' do
|
27
|
-
@filename = "/path/to/something.aiff"
|
28
|
-
assert_equal "audio/x-aiff", Paperclip::ContentTypeDetector.new(@filename).detect
|
29
|
-
end
|
30
|
-
|
31
21
|
should 'find the right type in the list via the file command' do
|
32
22
|
@filename = "#{Dir.tmpdir}/something.hahalolnotreal"
|
33
23
|
File.open(@filename, "w+") do |file|
|
@@ -0,0 +1 @@
|
|
1
|
+
<html></html>
|
data/test/helper.rb
CHANGED
@@ -40,6 +40,7 @@ class Test::Unit::TestCase
|
|
40
40
|
Rails.stubs(:root).returns(Pathname.new(ROOT).join('tmp'))
|
41
41
|
Rails.stubs(:env).returns('test')
|
42
42
|
Rails.stubs(:const_defined?).with(:Railtie).returns(false)
|
43
|
+
ActiveSupport::Deprecation.silenced = true
|
43
44
|
end
|
44
45
|
end
|
45
46
|
|
@@ -125,6 +126,7 @@ end
|
|
125
126
|
def rebuild_class options = {}
|
126
127
|
reset_class("Dummy").tap do |klass|
|
127
128
|
klass.has_attached_file :avatar, options
|
129
|
+
klass.do_not_validate_attachment_file_type :avatar
|
128
130
|
Paperclip.reset_duplicate_clash_check!
|
129
131
|
end
|
130
132
|
end
|
@@ -132,6 +134,7 @@ end
|
|
132
134
|
def rebuild_meta_class_of obj, options = {}
|
133
135
|
(class << obj; self; end).tap do |metaklass|
|
134
136
|
metaklass.has_attached_file :avatar, options
|
137
|
+
metaklass.do_not_validate_attachment_file_type :avatar
|
135
138
|
Paperclip.reset_duplicate_clash_check!
|
136
139
|
end
|
137
140
|
end
|
@@ -169,21 +172,21 @@ end
|
|
169
172
|
|
170
173
|
def should_accept_dummy_class
|
171
174
|
should "accept the class" do
|
172
|
-
assert_accepts @matcher,
|
175
|
+
assert_accepts @matcher, Dummy
|
173
176
|
end
|
174
177
|
|
175
178
|
should "accept an instance of that class" do
|
176
|
-
assert_accepts @matcher,
|
179
|
+
assert_accepts @matcher, Dummy.new
|
177
180
|
end
|
178
181
|
end
|
179
182
|
|
180
183
|
def should_reject_dummy_class
|
181
184
|
should "reject the class" do
|
182
|
-
assert_rejects @matcher,
|
185
|
+
assert_rejects @matcher, Dummy
|
183
186
|
end
|
184
187
|
|
185
188
|
should "reject an instance of that class" do
|
186
|
-
assert_rejects @matcher,
|
189
|
+
assert_rejects @matcher, Dummy.new
|
187
190
|
end
|
188
191
|
end
|
189
192
|
|
@@ -197,6 +200,10 @@ def with_exitstatus_returning(code)
|
|
197
200
|
end
|
198
201
|
end
|
199
202
|
|
203
|
+
def stringy_file
|
204
|
+
StringIO.new('.\n')
|
205
|
+
end
|
206
|
+
|
200
207
|
def fixture_file(filename)
|
201
208
|
File.join(File.dirname(__FILE__), 'fixtures', filename)
|
202
209
|
end
|