paperclip 5.0.0.beta1 → 5.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/CONTRIBUTING.md +10 -4
- data/NEWS +22 -1
- data/README.md +49 -45
- data/UPGRADING +3 -0
- data/features/migration.feature +0 -24
- data/features/step_definitions/rails_steps.rb +0 -6
- data/features/step_definitions/s3_steps.rb +2 -6
- data/gemfiles/5.0.awsv2.0.gemfile +1 -1
- data/gemfiles/5.0.awsv2.1.gemfile +1 -1
- data/gemfiles/5.0.awsv2.gemfile +1 -6
- data/lib/paperclip/attachment.rb +3 -2
- data/lib/paperclip/content_type_detector.rb +3 -2
- data/lib/paperclip/errors.rb +3 -1
- data/lib/paperclip/helpers.rb +14 -10
- data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +1 -1
- data/lib/paperclip/io_adapters/uri_adapter.rb +3 -1
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +4 -4
- data/lib/paperclip/schema.rb +1 -6
- data/lib/paperclip/storage/fog.rb +9 -6
- data/lib/paperclip/storage/s3.rb +16 -38
- data/lib/paperclip/validators/attachment_size_validator.rb +1 -7
- data/lib/paperclip/version.rb +3 -1
- data/lib/paperclip.rb +2 -1
- data/lib/tasks/paperclip.rake +1 -1
- data/paperclip.gemspec +1 -2
- data/spec/paperclip/attachment_processing_spec.rb +2 -4
- data/spec/paperclip/attachment_spec.rb +1 -4
- data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +12 -0
- data/spec/paperclip/io_adapters/uri_adapter_spec.rb +27 -0
- data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +10 -0
- data/spec/paperclip/media_type_spoof_detector_spec.rb +12 -3
- data/spec/paperclip/paperclip_spec.rb +3 -1
- data/spec/paperclip/storage/fog_spec.rb +10 -0
- data/spec/paperclip/storage/s3_spec.rb +92 -215
- data/spec/paperclip/validators/attachment_size_validator_spec.rb +26 -20
- data/spec/paperclip/validators_spec.rb +1 -0
- metadata +10 -8
- data/cucumber/paperclip_steps.rb +0 -6
data/lib/paperclip/storage/s3.rb
CHANGED
@@ -113,37 +113,15 @@ module Paperclip
|
|
113
113
|
|
114
114
|
module S3
|
115
115
|
def self.extended base
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
defined?(::Aws) ? Aws::Errors::ServiceError : AWS::Errors::Base)
|
122
|
-
const_set('DEFAULT_PERMISSION',
|
123
|
-
defined?(::AWS) ? :public_read : :'public-read')
|
124
|
-
|
125
|
-
rescue LoadError => e
|
126
|
-
e.message << " (You may need to install the aws-sdk gem)"
|
127
|
-
raise e
|
128
|
-
end
|
129
|
-
if Gem::Version.new(AWS_CLASS::VERSION) >= Gem::Version.new(2) && Gem::Version.new(AWS_CLASS::VERSION) <= Gem::Version.new("2.0.33")
|
130
|
-
raise LoadError, "paperclip does not support aws-sdk versions 2.0.0 - 2.0.33. Please upgrade aws-sdk to a newer version."
|
131
|
-
end
|
116
|
+
begin
|
117
|
+
require 'aws-sdk'
|
118
|
+
rescue LoadError => e
|
119
|
+
e.message << " (You may need to install the aws-sdk gem)"
|
120
|
+
raise e
|
132
121
|
end
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
AWS_CLASS::Core::LogFormatter.class_eval do
|
137
|
-
def summarize_hash(hash)
|
138
|
-
hash.map { |key, value| ":#{key}=>#{summarize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
|
139
|
-
end
|
140
|
-
end
|
141
|
-
elsif defined?(AWS_CLASS::Core::ClientLogging)
|
142
|
-
AWS_CLASS::Core::ClientLogging.class_eval do
|
143
|
-
def sanitize_hash(hash)
|
144
|
-
hash.map { |key, value| "#{sanitize_value(key)}=>#{sanitize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
|
145
|
-
end
|
146
|
-
end
|
122
|
+
if Gem::Version.new(Aws::VERSION) >= Gem::Version.new(2) &&
|
123
|
+
Gem::Version.new(Aws::VERSION) <= Gem::Version.new("2.0.33")
|
124
|
+
raise LoadError, "paperclip does not support aws-sdk versions 2.0.0 - 2.0.33. Please upgrade aws-sdk to a newer version."
|
147
125
|
end
|
148
126
|
|
149
127
|
base.instance_eval do
|
@@ -153,7 +131,7 @@ module Paperclip
|
|
153
131
|
Proc.new do |style, attachment|
|
154
132
|
permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
|
155
133
|
permission = permission.call(attachment, style) if permission.respond_to?(:call)
|
156
|
-
(permission ==
|
134
|
+
(permission == :"public-read") ? 'http'.freeze : 'https'.freeze
|
157
135
|
end
|
158
136
|
@s3_metadata = @options[:s3_metadata] || {}
|
159
137
|
@s3_headers = {}
|
@@ -266,7 +244,7 @@ module Paperclip
|
|
266
244
|
|
267
245
|
def obtain_s3_instance_for(options)
|
268
246
|
instances = (Thread.current[:paperclip_s3_instances] ||= {})
|
269
|
-
instances[options] ||=
|
247
|
+
instances[options] ||= ::Aws::S3::Resource.new(options)
|
270
248
|
end
|
271
249
|
|
272
250
|
def s3_bucket
|
@@ -303,7 +281,7 @@ module Paperclip
|
|
303
281
|
|
304
282
|
def set_permissions permissions
|
305
283
|
permissions = { :default => permissions } unless permissions.respond_to?(:merge)
|
306
|
-
permissions.merge :default => (permissions[:default] ||
|
284
|
+
permissions.merge :default => (permissions[:default] || :"public-read")
|
307
285
|
end
|
308
286
|
|
309
287
|
def set_storage_class(storage_class)
|
@@ -323,7 +301,7 @@ module Paperclip
|
|
323
301
|
else
|
324
302
|
false
|
325
303
|
end
|
326
|
-
rescue
|
304
|
+
rescue Aws::Errors::ServiceError => e
|
327
305
|
false
|
328
306
|
end
|
329
307
|
|
@@ -381,10 +359,10 @@ module Paperclip
|
|
381
359
|
write_options.merge!(@s3_headers)
|
382
360
|
|
383
361
|
s3_object(style).upload_file(file.path, write_options)
|
384
|
-
rescue
|
362
|
+
rescue ::Aws::S3::Errors::NoSuchBucket
|
385
363
|
create_bucket
|
386
364
|
retry
|
387
|
-
rescue
|
365
|
+
rescue ::Aws::S3::Errors::SlowDown
|
388
366
|
retries += 1
|
389
367
|
if retries <= 5
|
390
368
|
sleep((2 ** retries) * 0.5)
|
@@ -407,7 +385,7 @@ module Paperclip
|
|
407
385
|
begin
|
408
386
|
log("deleting #{path}")
|
409
387
|
s3_bucket.object(path.sub(%r{\A/}, "")).delete
|
410
|
-
rescue
|
388
|
+
rescue Aws::Errors::ServiceError => e
|
411
389
|
# Ignore this.
|
412
390
|
end
|
413
391
|
end
|
@@ -421,7 +399,7 @@ module Paperclip
|
|
421
399
|
local_file.write(chunk)
|
422
400
|
end
|
423
401
|
end
|
424
|
-
rescue
|
402
|
+
rescue Aws::Errors::ServiceError => e
|
425
403
|
warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
|
426
404
|
false
|
427
405
|
end
|
@@ -71,13 +71,7 @@ module Paperclip
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def human_size(size)
|
74
|
-
|
75
|
-
ActiveSupport::NumberHelper.number_to_human_size(size)
|
76
|
-
else
|
77
|
-
storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
|
78
|
-
unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => size.to_i, :raise => true)
|
79
|
-
storage_units_format.gsub(/%n/, size.to_i.to_s).gsub(/%u/, unit).html_safe
|
80
|
-
end
|
74
|
+
ActiveSupport::NumberHelper.number_to_human_size(size)
|
81
75
|
end
|
82
76
|
|
83
77
|
def min_value_in_human_size(record)
|
data/lib/paperclip/version.rb
CHANGED
data/lib/paperclip.rb
CHANGED
data/lib/tasks/paperclip.rake
CHANGED
@@ -18,7 +18,7 @@ module Paperclip
|
|
18
18
|
raise "Class #{klass.name} has no attachments specified"
|
19
19
|
end
|
20
20
|
|
21
|
-
if
|
21
|
+
if name.present? && attachment_names.map(&:to_s).include?(name.to_s)
|
22
22
|
[ name ]
|
23
23
|
else
|
24
24
|
attachment_names
|
data/paperclip.gemspec
CHANGED
@@ -35,12 +35,11 @@ Gem::Specification.new do |s|
|
|
35
35
|
s.add_development_dependency('rspec', '~> 3.0')
|
36
36
|
s.add_development_dependency('appraisal')
|
37
37
|
s.add_development_dependency('mocha')
|
38
|
-
s.add_development_dependency('aws-sdk', '>= 2.0.
|
38
|
+
s.add_development_dependency('aws-sdk', '>= 2.0.34', '< 3.0')
|
39
39
|
s.add_development_dependency('bourne')
|
40
40
|
s.add_development_dependency('cucumber', '~> 1.3.18')
|
41
41
|
s.add_development_dependency('aruba', '~> 0.9.0')
|
42
42
|
s.add_development_dependency('nokogiri')
|
43
|
-
# Ruby version < 1.9.3 can't install capybara > 2.0.3.
|
44
43
|
s.add_development_dependency('capybara')
|
45
44
|
s.add_development_dependency('bundler')
|
46
45
|
s.add_development_dependency('fog-aws')
|
@@ -2,11 +2,9 @@
|
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
4
|
describe 'Attachment Processing' do
|
5
|
-
|
6
|
-
before do
|
7
|
-
rebuild_class
|
8
|
-
end
|
5
|
+
before { rebuild_class }
|
9
6
|
|
7
|
+
context 'using validates_attachment_content_type' do
|
10
8
|
it 'processes attachments given a valid assignment' do
|
11
9
|
file = File.new(fixture_file("5k.png"))
|
12
10
|
Dummy.validates_attachment_content_type :avatar, content_type: "image/png"
|
@@ -222,9 +222,6 @@ describe Paperclip::Attachment do
|
|
222
222
|
dummy.avatar_file_name = "fake.jpg"
|
223
223
|
dummy.stubs(:new_record?).returns(false)
|
224
224
|
expected_string = '{"avatar":"/system/dummies/avatars/000/001/234/original/fake.jpg"}'
|
225
|
-
if ActiveRecord::Base.include_root_in_json # This is true by default in Rails 3, and false in 4
|
226
|
-
expected_string = %({"dummy":#{expected_string}})
|
227
|
-
end
|
228
225
|
# active_model pre-3.2 checks only by calling any? on it, thus it doesn't work if it is empty
|
229
226
|
assert_equal expected_string, dummy.to_json(only: [:dummy_key_for_old_active_model], methods: [:avatar])
|
230
227
|
end
|
@@ -1124,7 +1121,7 @@ describe Paperclip::Attachment do
|
|
1124
1121
|
context "with a file assigned but not saved yet" do
|
1125
1122
|
it "clears out any attached files" do
|
1126
1123
|
@attachment.assign(@file)
|
1127
|
-
assert
|
1124
|
+
assert @attachment.queued_for_write.present?
|
1128
1125
|
@attachment.clear
|
1129
1126
|
assert @attachment.queued_for_write.blank?
|
1130
1127
|
end
|
@@ -98,4 +98,16 @@ describe Paperclip::HttpUrlProxyAdapter do
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
+
context "a url with special characters in the filename" do
|
102
|
+
it "returns a encoded filename" do
|
103
|
+
Paperclip::HttpUrlProxyAdapter.any_instance.stubs(:download_content).
|
104
|
+
returns(StringIO.new("x"))
|
105
|
+
url = "https://github.com/thoughtbot/paperclip-öäü字´½♥زÈ.png"
|
106
|
+
subject = Paperclip.io_adapters.for(url)
|
107
|
+
filename = "paperclip-%C3%B6%C3%A4%C3%BC%E5%AD%97%C2%B4%C2%BD%E2%99%A5"\
|
108
|
+
"%C3%98%C2%B2%C3%88.png"
|
109
|
+
|
110
|
+
assert_equal filename, subject.original_filename
|
111
|
+
end
|
112
|
+
end
|
101
113
|
end
|
@@ -99,4 +99,31 @@ describe Paperclip::UriAdapter do
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
describe "#download_content" do
|
103
|
+
before do
|
104
|
+
Paperclip::UriAdapter.any_instance.stubs(:open).returns(StringIO.new("xxx"))
|
105
|
+
@uri = URI.parse("https://github.com/thoughtbot/paper:clip.jpg")
|
106
|
+
@subject = Paperclip.io_adapters.for(@uri)
|
107
|
+
end
|
108
|
+
|
109
|
+
after do
|
110
|
+
@subject.send(:download_content)
|
111
|
+
end
|
112
|
+
|
113
|
+
context "with default read_timeout" do
|
114
|
+
it "calls open without options" do
|
115
|
+
@subject.expects(:open).with(@uri, {}).at_least_once
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "with custom read_timeout" do
|
120
|
+
before do
|
121
|
+
Paperclip.options[:read_timeout] = 120
|
122
|
+
end
|
123
|
+
|
124
|
+
it "calls open with read_timeout option" do
|
125
|
+
@subject.expects(:open).with(@uri, read_timeout: 120).at_least_once
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
102
129
|
end
|
@@ -17,22 +17,26 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
|
|
17
17
|
|
18
18
|
it "rejects a class with no validation" do
|
19
19
|
expect(matcher).to_not accept(Dummy)
|
20
|
+
expect { matcher.failure_message }.to_not raise_error
|
20
21
|
end
|
21
22
|
|
22
23
|
it 'rejects a class when the validation fails' do
|
23
24
|
Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}
|
24
25
|
expect(matcher).to_not accept(Dummy)
|
26
|
+
expect { matcher.failure_message }.to_not raise_error
|
25
27
|
end
|
26
28
|
|
27
29
|
it "accepts a class with a matching validation" do
|
28
30
|
Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
|
29
31
|
expect(matcher).to accept(Dummy)
|
32
|
+
expect { matcher.failure_message }.to_not raise_error
|
30
33
|
end
|
31
34
|
|
32
35
|
it "accepts a class with other validations but matching types" do
|
33
36
|
Dummy.validates_presence_of :title
|
34
37
|
Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
|
35
38
|
expect(matcher).to accept(Dummy)
|
39
|
+
expect { matcher.failure_message }.to_not raise_error
|
36
40
|
end
|
37
41
|
|
38
42
|
it "accepts a class that matches and a matcher that only specifies 'allowing'" do
|
@@ -40,6 +44,7 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
|
|
40
44
|
matcher = plain_matcher.allowing(%w(image/png image/jpeg))
|
41
45
|
|
42
46
|
expect(matcher).to accept(Dummy)
|
47
|
+
expect { matcher.failure_message }.to_not raise_error
|
43
48
|
end
|
44
49
|
|
45
50
|
it "rejects a class that does not match and a matcher that only specifies 'allowing'" do
|
@@ -47,6 +52,7 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
|
|
47
52
|
matcher = plain_matcher.allowing(%w(image/png image/jpeg))
|
48
53
|
|
49
54
|
expect(matcher).to_not accept(Dummy)
|
55
|
+
expect { matcher.failure_message }.to_not raise_error
|
50
56
|
end
|
51
57
|
|
52
58
|
it "accepts a class that matches and a matcher that only specifies 'rejecting'" do
|
@@ -54,6 +60,7 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
|
|
54
60
|
matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))
|
55
61
|
|
56
62
|
expect(matcher).to accept(Dummy)
|
63
|
+
expect { matcher.failure_message }.to_not raise_error
|
57
64
|
end
|
58
65
|
|
59
66
|
it "rejects a class that does not match and a matcher that only specifies 'rejecting'" do
|
@@ -61,6 +68,7 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
|
|
61
68
|
matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))
|
62
69
|
|
63
70
|
expect(matcher).to_not accept(Dummy)
|
71
|
+
expect { matcher.failure_message }.to_not raise_error
|
64
72
|
end
|
65
73
|
|
66
74
|
context "using an :if to control the validation" do
|
@@ -75,12 +83,14 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
|
|
75
83
|
dummy = Dummy.new
|
76
84
|
dummy.go = true
|
77
85
|
expect(matcher).to accept(dummy)
|
86
|
+
expect { matcher.failure_message }.to_not raise_error
|
78
87
|
end
|
79
88
|
|
80
89
|
it "does not run the validation if the control is false" do
|
81
90
|
dummy = Dummy.new
|
82
91
|
dummy.go = false
|
83
92
|
expect(matcher).to_not accept(dummy)
|
93
|
+
expect { matcher.failure_message }.to_not raise_error
|
84
94
|
end
|
85
95
|
end
|
86
96
|
|
@@ -44,9 +44,18 @@ describe Paperclip::MediaTypeSpoofDetector do
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
|
48
|
-
file
|
49
|
-
|
47
|
+
context "file named .html and is as HTML, but we're told JPG" do
|
48
|
+
let(:file) { File.open(fixture_file("empty.html")) }
|
49
|
+
let(:spoofed?) { Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "image/jpg").spoofed? }
|
50
|
+
|
51
|
+
it "rejects the file" do
|
52
|
+
assert spoofed?
|
53
|
+
end
|
54
|
+
|
55
|
+
it "logs info about the detected spoof" do
|
56
|
+
Paperclip.expects(:log).with('Content Type Spoof: Filename empty.html (image/jpg from Headers, ["text/html"] from Extension), content type discovered from file command: text/html. See documentation to allow this combination.')
|
57
|
+
spoofed?
|
58
|
+
end
|
50
59
|
end
|
51
60
|
|
52
61
|
it "does not reject if content_type is empty but otherwise checks out" do
|
@@ -29,7 +29,9 @@ describe Paperclip do
|
|
29
29
|
Paperclip.options[:command_path] = "/opt/my_app/bin"
|
30
30
|
Paperclip.run("convert", "stuff")
|
31
31
|
Paperclip.run("convert", "more_stuff")
|
32
|
-
|
32
|
+
|
33
|
+
cmd_path = Paperclip.options[:command_path]
|
34
|
+
assert_equal 1, Cocaine::CommandLine.path.scan(cmd_path).count
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
@@ -183,6 +183,13 @@ describe Paperclip::Storage::Fog do
|
|
183
183
|
tempfile.close
|
184
184
|
end
|
185
185
|
|
186
|
+
it 'is able to be handled when missing while copying to a local file' do
|
187
|
+
tempfile = Tempfile.new("known_location")
|
188
|
+
tempfile.binmode
|
189
|
+
assert_equal false, @dummy.avatar.copy_to_local_file(:original, tempfile.path)
|
190
|
+
tempfile.close
|
191
|
+
end
|
192
|
+
|
186
193
|
it "passes the content type to the Fog::Storage::AWS::Files instance" do
|
187
194
|
Fog::Storage::AWS::Files.any_instance.expects(:create).with do |hash|
|
188
195
|
hash[:content_type]
|
@@ -418,6 +425,9 @@ describe Paperclip::Storage::Fog do
|
|
418
425
|
assert @connection.directories.get(@dynamic_fog_directory).inspect
|
419
426
|
end
|
420
427
|
|
428
|
+
it "provides an url using dynamic bucket name" do
|
429
|
+
assert_match(/^https:\/\/dynamicpaperclip.s3.amazonaws.com\/avatars\/5k.png\?\d*$/, @dummy.avatar.url)
|
430
|
+
end
|
421
431
|
end
|
422
432
|
|
423
433
|
context "with a proc for the fog_host evaluating a model method" do
|