paperclip 3.5.4 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -0
  3. data/.gitignore +0 -6
  4. data/.hound.yml +1055 -0
  5. data/.rubocop.yml +1 -0
  6. data/.travis.yml +17 -20
  7. data/Appraisals +4 -16
  8. data/CONTRIBUTING.md +29 -13
  9. data/Gemfile +11 -3
  10. data/LICENSE +1 -3
  11. data/NEWS +241 -49
  12. data/README.md +471 -166
  13. data/RELEASING.md +17 -0
  14. data/Rakefile +6 -8
  15. data/UPGRADING +12 -9
  16. data/features/basic_integration.feature +27 -8
  17. data/features/migration.feature +0 -24
  18. data/features/step_definitions/attachment_steps.rb +36 -28
  19. data/features/step_definitions/html_steps.rb +2 -2
  20. data/features/step_definitions/rails_steps.rb +68 -37
  21. data/features/step_definitions/s3_steps.rb +2 -2
  22. data/features/step_definitions/web_steps.rb +1 -103
  23. data/features/support/env.rb +3 -2
  24. data/features/support/file_helpers.rb +2 -2
  25. data/features/support/fixtures/gemfile.txt +1 -1
  26. data/features/support/paths.rb +1 -1
  27. data/features/support/rails.rb +2 -25
  28. data/gemfiles/4.2.gemfile +17 -0
  29. data/gemfiles/5.0.gemfile +17 -0
  30. data/lib/generators/paperclip/paperclip_generator.rb +9 -3
  31. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +2 -2
  32. data/lib/paperclip/attachment.rb +151 -46
  33. data/lib/paperclip/attachment_registry.rb +3 -2
  34. data/lib/paperclip/callbacks.rb +13 -1
  35. data/lib/paperclip/content_type_detector.rb +26 -22
  36. data/lib/paperclip/errors.rb +8 -1
  37. data/lib/paperclip/file_command_content_type_detector.rb +6 -8
  38. data/lib/paperclip/geometry_detector_factory.rb +5 -3
  39. data/lib/paperclip/geometry_parser_factory.rb +1 -1
  40. data/lib/paperclip/glue.rb +1 -1
  41. data/lib/paperclip/has_attached_file.rb +17 -1
  42. data/lib/paperclip/helpers.rb +15 -11
  43. data/lib/paperclip/interpolations/plural_cache.rb +6 -5
  44. data/lib/paperclip/interpolations.rb +26 -13
  45. data/lib/paperclip/io_adapters/abstract_adapter.rb +32 -4
  46. data/lib/paperclip/io_adapters/attachment_adapter.rb +13 -8
  47. data/lib/paperclip/io_adapters/data_uri_adapter.rb +11 -16
  48. data/lib/paperclip/io_adapters/empty_string_adapter.rb +5 -4
  49. data/lib/paperclip/io_adapters/file_adapter.rb +12 -6
  50. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +7 -7
  51. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -6
  52. data/lib/paperclip/io_adapters/nil_adapter.rb +8 -5
  53. data/lib/paperclip/io_adapters/registry.rb +6 -2
  54. data/lib/paperclip/io_adapters/stringio_adapter.rb +15 -16
  55. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +10 -6
  56. data/lib/paperclip/io_adapters/uri_adapter.rb +41 -19
  57. data/lib/paperclip/locales/en.yml +1 -0
  58. data/lib/paperclip/matchers/have_attached_file_matcher.rb +2 -1
  59. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +4 -4
  60. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +2 -1
  61. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +2 -1
  62. data/lib/paperclip/media_type_spoof_detector.rb +90 -0
  63. data/lib/paperclip/processor.rb +5 -41
  64. data/lib/paperclip/processor_helpers.rb +50 -0
  65. data/lib/paperclip/rails_environment.rb +25 -0
  66. data/lib/paperclip/schema.rb +9 -7
  67. data/lib/paperclip/storage/filesystem.rb +14 -3
  68. data/lib/paperclip/storage/fog.rb +37 -19
  69. data/lib/paperclip/storage/s3.rb +129 -69
  70. data/lib/paperclip/style.rb +8 -2
  71. data/lib/paperclip/tempfile_factory.rb +5 -1
  72. data/lib/paperclip/thumbnail.rb +30 -18
  73. data/lib/paperclip/url_generator.rb +26 -14
  74. data/lib/paperclip/validators/attachment_content_type_validator.rb +4 -0
  75. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  76. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  77. data/lib/paperclip/validators/attachment_presence_validator.rb +4 -0
  78. data/lib/paperclip/validators/attachment_size_validator.rb +5 -3
  79. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  80. data/lib/paperclip/validators.rb +11 -4
  81. data/lib/paperclip/version.rb +3 -1
  82. data/lib/paperclip.rb +30 -11
  83. data/lib/tasks/paperclip.rake +34 -5
  84. data/paperclip.gemspec +21 -16
  85. data/shoulda_macros/paperclip.rb +0 -1
  86. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  87. data/{test/attachment_processing_test.rb → spec/paperclip/attachment_processing_spec.rb} +17 -20
  88. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  89. data/{test/attachment_test.rb → spec/paperclip/attachment_spec.rb} +502 -407
  90. data/{test/content_type_detector_test.rb → spec/paperclip/content_type_detector_spec.rb} +17 -20
  91. data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
  92. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  93. data/spec/paperclip/geometry_detector_spec.rb +39 -0
  94. data/{test/geometry_parser_test.rb → spec/paperclip/geometry_parser_spec.rb} +27 -27
  95. data/{test/geometry_test.rb → spec/paperclip/geometry_spec.rb} +50 -52
  96. data/spec/paperclip/glue_spec.rb +44 -0
  97. data/spec/paperclip/has_attached_file_spec.rb +158 -0
  98. data/{test/integration_test.rb → spec/paperclip/integration_spec.rb} +137 -128
  99. data/{test/interpolations_test.rb → spec/paperclip/interpolations_spec.rb} +70 -46
  100. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +132 -0
  101. data/{test/io_adapters/attachment_adapter_test.rb → spec/paperclip/io_adapters/attachment_adapter_spec.rb} +33 -32
  102. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +89 -0
  103. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  104. data/{test/io_adapters/file_adapter_test.rb → spec/paperclip/io_adapters/file_adapter_spec.rb} +38 -42
  105. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +121 -0
  106. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  107. data/{test/io_adapters/nil_adapter_test.rb → spec/paperclip/io_adapters/nil_adapter_spec.rb} +7 -7
  108. data/{test/io_adapters/registry_test.rb → spec/paperclip/io_adapters/registry_spec.rb} +12 -9
  109. data/{test/io_adapters/stringio_adapter_test.rb → spec/paperclip/io_adapters/stringio_adapter_spec.rb} +21 -18
  110. data/{test/io_adapters/uploaded_file_adapter_test.rb → spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb} +46 -46
  111. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +172 -0
  112. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  113. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +109 -0
  114. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  115. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  116. data/spec/paperclip/media_type_spoof_detector_spec.rb +94 -0
  117. data/spec/paperclip/meta_class_spec.rb +30 -0
  118. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  119. data/spec/paperclip/paperclip_spec.rb +192 -0
  120. data/spec/paperclip/plural_cache_spec.rb +37 -0
  121. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  122. data/{test/processor_test.rb → spec/paperclip/processor_spec.rb} +7 -7
  123. data/spec/paperclip/rails_environment_spec.rb +33 -0
  124. data/{test/rake_test.rb → spec/paperclip/rake_spec.rb} +15 -15
  125. data/spec/paperclip/schema_spec.rb +248 -0
  126. data/{test/storage/filesystem_test.rb → spec/paperclip/storage/filesystem_spec.rb} +18 -18
  127. data/spec/paperclip/storage/fog_spec.rb +566 -0
  128. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  129. data/spec/paperclip/storage/s3_spec.rb +1693 -0
  130. data/spec/paperclip/style_spec.rb +255 -0
  131. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  132. data/spec/paperclip/tempfile_spec.rb +35 -0
  133. data/{test/thumbnail_test.rb → spec/paperclip/thumbnail_spec.rb} +153 -134
  134. data/spec/paperclip/url_generator_spec.rb +222 -0
  135. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  136. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  137. data/{test/validators/attachment_presence_validator_test.rb → spec/paperclip/validators/attachment_presence_validator_spec.rb} +20 -20
  138. data/{test/validators/attachment_size_validator_test.rb → spec/paperclip/validators/attachment_size_validator_spec.rb} +77 -64
  139. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  140. data/spec/paperclip/validators_spec.rb +164 -0
  141. data/spec/spec_helper.rb +47 -0
  142. data/spec/support/assertions.rb +82 -0
  143. data/spec/support/conditional_filter_helper.rb +5 -0
  144. data/spec/support/fake_model.rb +25 -0
  145. data/spec/support/fake_rails.rb +12 -0
  146. data/spec/support/fixtures/empty.html +1 -0
  147. data/spec/support/fixtures/empty.xlsx +0 -0
  148. data/spec/support/fixtures/spaced file.jpg +0 -0
  149. data/spec/support/matchers/accept.rb +5 -0
  150. data/spec/support/matchers/exist.rb +5 -0
  151. data/spec/support/matchers/have_column.rb +23 -0
  152. data/{test → spec}/support/mock_attachment.rb +2 -0
  153. data/{test → spec}/support/mock_url_generator_builder.rb +2 -2
  154. data/spec/support/model_reconstruction.rb +68 -0
  155. data/spec/support/reporting.rb +11 -0
  156. data/spec/support/test_data.rb +13 -0
  157. data/spec/support/version_helper.rb +9 -0
  158. metadata +348 -225
  159. data/RUNNING_TESTS.md +0 -4
  160. data/cucumber/paperclip_steps.rb +0 -6
  161. data/gemfiles/3.0.gemfile +0 -11
  162. data/gemfiles/3.1.gemfile +0 -11
  163. data/gemfiles/3.2.gemfile +0 -11
  164. data/gemfiles/4.0.gemfile +0 -11
  165. data/test/attachment_definitions_test.rb +0 -12
  166. data/test/attachment_registry_test.rb +0 -88
  167. data/test/file_command_content_type_detector_test.rb +0 -27
  168. data/test/filename_cleaner_test.rb +0 -14
  169. data/test/generator_test.rb +0 -84
  170. data/test/geometry_detector_test.rb +0 -24
  171. data/test/has_attached_file_test.rb +0 -125
  172. data/test/helper.rb +0 -232
  173. data/test/io_adapters/abstract_adapter_test.rb +0 -58
  174. data/test/io_adapters/data_uri_adapter_test.rb +0 -74
  175. data/test/io_adapters/empty_string_adapter_test.rb +0 -18
  176. data/test/io_adapters/http_url_proxy_adapter_test.rb +0 -102
  177. data/test/io_adapters/identity_adapter_test.rb +0 -8
  178. data/test/io_adapters/uri_adapter_test.rb +0 -102
  179. data/test/matchers/have_attached_file_matcher_test.rb +0 -24
  180. data/test/matchers/validate_attachment_content_type_matcher_test.rb +0 -110
  181. data/test/matchers/validate_attachment_presence_matcher_test.rb +0 -69
  182. data/test/matchers/validate_attachment_size_matcher_test.rb +0 -86
  183. data/test/meta_class_test.rb +0 -32
  184. data/test/paperclip_missing_attachment_styles_test.rb +0 -90
  185. data/test/paperclip_test.rb +0 -217
  186. data/test/plural_cache_test.rb +0 -36
  187. data/test/schema_test.rb +0 -200
  188. data/test/storage/fog_test.rb +0 -473
  189. data/test/storage/s3_live_test.rb +0 -179
  190. data/test/storage/s3_test.rb +0 -1356
  191. data/test/style_test.rb +0 -213
  192. data/test/support/mock_model.rb +0 -2
  193. data/test/tempfile_factory_test.rb +0 -17
  194. data/test/url_generator_test.rb +0 -187
  195. data/test/validators/attachment_content_type_validator_test.rb +0 -324
  196. data/test/validators_test.rb +0 -61
  197. /data/{test → spec}/database.yml +0 -0
  198. /data/{test → spec/support}/fixtures/12k.png +0 -0
  199. /data/{test → spec/support}/fixtures/50x50.png +0 -0
  200. /data/{test → spec/support}/fixtures/5k.png +0 -0
  201. /data/{test → spec/support}/fixtures/animated +0 -0
  202. /data/{test → spec/support}/fixtures/animated.gif +0 -0
  203. /data/{test → spec/support}/fixtures/animated.unknown +0 -0
  204. /data/{test → spec/support}/fixtures/bad.png +0 -0
  205. /data/{test → spec/support}/fixtures/fog.yml +0 -0
  206. /data/{test → spec/support}/fixtures/rotated.jpg +0 -0
  207. /data/{test → spec/support}/fixtures/s3.yml +0 -0
  208. /data/{test → spec/support}/fixtures/spaced file.png +0 -0
  209. /data/{test → spec/support}/fixtures/text.txt +0 -0
  210. /data/{test → spec/support}/fixtures/twopage.pdf +0 -0
  211. /data/{test → spec/support}/fixtures/uppercase.PNG +0 -0
  212. /data/{test → spec}/support/mock_interpolator.rb +0 -0
@@ -14,6 +14,7 @@ module Paperclip
14
14
  # aws_secret_access_key: '<your aws_secret_access_key>'
15
15
  # provider: 'AWS'
16
16
  # region: 'eu-west-1'
17
+ # scheme: 'https'
17
18
  # * +fog_directory+: This is the name of the S3 bucket that will
18
19
  # store your files. Remember that the bucket must be unique across
19
20
  # all of Amazon S3. If the bucket does not exist, Paperclip will
@@ -32,6 +33,10 @@ module Paperclip
32
33
  # that is the alias to the S3 domain of your bucket, e.g.
33
34
  # 'http://images.example.com'. This can also be used in
34
35
  # conjunction with Cloudfront (http://aws.amazon.com/cloudfront)
36
+ # * +fog_options+: (optional) A hash of options that are passed
37
+ # to fog when the file is created. For example, you could set
38
+ # the multipart-chunk size to 100MB with a hash:
39
+ # { :multipart_chunk_size => 104857600 }
35
40
 
36
41
  module Fog
37
42
  def self.extended base
@@ -43,7 +48,7 @@ module Paperclip
43
48
  end unless defined?(Fog)
44
49
 
45
50
  base.instance_eval do
46
- unless @options[:url].to_s.match(/\A:fog.*url\Z/)
51
+ unless @options[:url].to_s.match(/\A:fog.*url\z/)
47
52
  @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/\A:rails_root\/public\/system\//, '')
48
53
  @options[:url] = ':fog_public_url'
49
54
  end
@@ -53,7 +58,7 @@ module Paperclip
53
58
  end
54
59
  end
55
60
 
56
- AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX = /\A(?:[a-z]|\d(?!\d{0,2}(?:\.\d{1,3}){3}\Z))(?:[a-z0-9]|\.(?![\.\-])|\-(?![\.])){1,61}[a-z0-9]\Z/
61
+ AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX = /\A(?:[a-z]|\d(?!\d{0,2}(?:\.\d{1,3}){3}\z))(?:[a-z0-9]|\.(?![\.\-])|\-(?![\.])){1,61}[a-z0-9]\z/
57
62
 
58
63
  def exists?(style = default_style)
59
64
  if original_filename
@@ -81,11 +86,14 @@ module Paperclip
81
86
  end
82
87
 
83
88
  def fog_public(style = default_style)
84
- if @options.has_key?(:fog_public)
85
- if @options[:fog_public].respond_to?(:has_key?) && @options[:fog_public].has_key?(style)
86
- @options[:fog_public][style]
89
+ if @options.key?(:fog_public)
90
+ value = @options[:fog_public]
91
+ if value.respond_to?(:key?) && value.key?(style)
92
+ value[style]
93
+ elsif value.respond_to?(:call)
94
+ value.call(self)
87
95
  else
88
- @options[:fog_public]
96
+ value
89
97
  end
90
98
  else
91
99
  true
@@ -97,16 +105,19 @@ module Paperclip
97
105
  log("saving #{path(style)}")
98
106
  retried = false
99
107
  begin
100
- directory.files.create(fog_file.merge(
108
+ attributes = fog_file.merge(
101
109
  :body => file,
102
110
  :key => path(style),
103
111
  :public => fog_public(style),
104
112
  :content_type => file.content_type
105
- ))
113
+ )
114
+ attributes.merge!(@options[:fog_options]) if @options[:fog_options]
115
+ directory.files.create(attributes)
106
116
  rescue Excon::Errors::NotFound
107
117
  raise if retried
108
118
  retried = true
109
119
  directory.save
120
+ file.rewind
110
121
  retry
111
122
  ensure
112
123
  file.rewind
@@ -131,7 +142,7 @@ module Paperclip
131
142
  "#{dynamic_fog_host_for_style(style)}/#{path(style)}"
132
143
  else
133
144
  if fog_credentials[:provider] == 'AWS'
134
- "https://#{host_name_for_directory}/#{path(style)}"
145
+ "#{scheme}://#{host_name_for_directory}/#{path(style)}"
135
146
  else
136
147
  directory.files.new(:key => path(style)).public_url
137
148
  end
@@ -140,8 +151,9 @@ module Paperclip
140
151
 
141
152
  def expiring_url(time = (Time.now + 3600), style_name = default_style)
142
153
  time = convert_time(time)
143
- if path(style_name) && directory.files.respond_to?(:get_http_url)
144
- expiring_url = directory.files.get_http_url(path(style_name), time)
154
+ http_url_method = "get_#{scheme}_url"
155
+ if path(style_name) && directory.files.respond_to?(http_url_method)
156
+ expiring_url = directory.files.public_send(http_url_method, path(style_name), time)
145
157
 
146
158
  if @options[:fog_host]
147
159
  expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style_name))
@@ -155,14 +167,14 @@ module Paperclip
155
167
 
156
168
  def parse_credentials(creds)
157
169
  creds = find_credentials(creds).stringify_keys
158
- env = Object.const_defined?(:Rails) ? Rails.env : nil
159
- (creds[env] || creds).symbolize_keys
170
+ (creds[RailsEnvironment.get] || creds).symbolize_keys
160
171
  end
161
172
 
162
173
  def copy_to_local_file(style, local_dest_path)
163
174
  log("copying #{path(style)} to local file #{local_dest_path}")
164
175
  ::File.open(local_dest_path, 'wb') do |local_file|
165
176
  file = directory.files.get(path(style))
177
+ return false unless file
166
178
  local_file.write(file.body)
167
179
  end
168
180
  rescue ::Fog::Errors::Error => e
@@ -173,7 +185,7 @@ module Paperclip
173
185
  private
174
186
 
175
187
  def convert_time(time)
176
- if time.is_a?(Fixnum)
188
+ if time.is_a?(Integer)
177
189
  time = Time.now + time
178
190
  end
179
191
  time
@@ -188,10 +200,10 @@ module Paperclip
188
200
  end
189
201
 
190
202
  def host_name_for_directory
191
- if @options[:fog_directory].to_s =~ Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX
192
- "#{@options[:fog_directory]}.s3.amazonaws.com"
203
+ if directory_name.to_s =~ Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX
204
+ "#{directory_name}.s3.amazonaws.com"
193
205
  else
194
- "s3.amazonaws.com/#{@options[:fog_directory]}"
206
+ "s3.amazonaws.com/#{directory_name}"
195
207
  end
196
208
  end
197
209
 
@@ -217,13 +229,19 @@ module Paperclip
217
229
  end
218
230
 
219
231
  def directory
220
- dir = if @options[:fog_directory].respond_to?(:call)
232
+ @directory ||= connection.directories.new(key: directory_name)
233
+ end
234
+
235
+ def directory_name
236
+ if @options[:fog_directory].respond_to?(:call)
221
237
  @options[:fog_directory].call(self)
222
238
  else
223
239
  @options[:fog_directory]
224
240
  end
241
+ end
225
242
 
226
- @directory ||= connection.directories.new(:key => dir)
243
+ def scheme
244
+ @scheme ||= fog_credentials[:scheme] || 'https'
227
245
  end
228
246
  end
229
247
  end
@@ -26,7 +26,7 @@ module Paperclip
26
26
  # put your bucket name in this file, instead of adding it to the code directly.
27
27
  # This is useful when you want the same account but a different bucket for
28
28
  # development versus production.
29
- # When using a Proc it provides a single parameter which is the attachment itself. A
29
+ # When using a Proc it provides a single parameter which is the attachment itself. A
30
30
  # method #instance is available on the attachment which will take you back to your
31
31
  # code. eg.
32
32
  # class User
@@ -41,19 +41,18 @@ module Paperclip
41
41
  # * +s3_permissions+: This is a String that should be one of the "canned" access
42
42
  # policies that S3 provides (more information can be found here:
43
43
  # http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html)
44
- # The default for Paperclip is :public_read.
44
+ # The default for Paperclip is public-read.
45
45
  #
46
46
  # You can set permission on a per style bases by doing the following:
47
47
  # :s3_permissions => {
48
- # :original => :private
48
+ # :original => "private"
49
49
  # }
50
50
  # Or globally:
51
- # :s3_permissions => :private
51
+ # :s3_permissions => "private"
52
52
  #
53
- # * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
54
- # 'http', 'https', or an empty string to generate scheme-less URLs. Defaults to 'http'
55
- # when your :s3_permissions are :public_read (the default), and 'https' when your
56
- # :s3_permissions are anything else.
53
+ # * +s3_protocol+: The protocol for the URLs generated to your S3 assets.
54
+ # Can be either 'http', 'https', or an empty string to generate
55
+ # protocol-relative URLs. Defaults to empty string.
57
56
  # * +s3_headers+: A hash of headers or a Proc. You may specify a hash such as
58
57
  # {'Expires' => 1.year.from_now.httpdate}. If you use a Proc, headers are determined at
59
58
  # runtime. Paperclip will call that Proc with attachment as the only argument.
@@ -61,11 +60,14 @@ module Paperclip
61
60
  # * +bucket+: This is the name of the S3 bucket that will store your files. Remember
62
61
  # that the bucket must be unique across all of Amazon S3. If the bucket does not exist
63
62
  # Paperclip will attempt to create it. The bucket name will not be interpolated.
64
- # You can define the bucket as a Proc if you want to determine it's name at runtime.
63
+ # You can define the bucket as a Proc if you want to determine its name at runtime.
65
64
  # Paperclip will call that Proc with attachment as the only argument.
66
65
  # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
67
66
  # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
68
67
  # link in the +url+ entry for more information about S3 domains and buckets.
68
+ # * +s3_prefixes_in_alias+: The number of prefixes that is prepended by
69
+ # s3_host_alias. This will remove the prefixes from the path in
70
+ # :s3_alias_url url interpolation
69
71
  # * +url+: There are four options for the S3 url. You can choose to have the bucket's name
70
72
  # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
71
73
  # You can also specify a CNAME (which requires the CNAME to be specified as
@@ -92,16 +94,32 @@ module Paperclip
92
94
  # to interpolate. Keys should be unique, like filenames, and despite the fact that
93
95
  # S3 (strictly speaking) does not support directories, you can still use a / to
94
96
  # separate parts of your file name.
95
- # * +s3_host_name+: If you are using your bucket in Tokyo region etc, write host_name.
97
+ # * +s3_host_name+: If you are using your bucket in Tokyo region
98
+ # etc, write host_name (e.g., 's3-ap-northeast-1.amazonaws.com').
99
+ # * +s3_region+: For aws-sdk v2, s3_region is required.
96
100
  # * +s3_metadata+: These key/value pairs will be stored with the
97
101
  # object. This option works by prefixing each key with
98
102
  # "x-amz-meta-" before sending it as a header on the object
99
103
  # upload request. Can be defined both globally and within a style-specific hash.
100
104
  # * +s3_storage_class+: If this option is set to
101
- # <tt>:reduced_redundancy</tt>, the object will be stored using Reduced
102
- # Redundancy Storage. RRS enables customers to reduce their
105
+ # <tt>:REDUCED_REDUNDANCY</tt>, the object will be stored using Reduced
106
+ # Redundancy Storage. RRS enables customers to reduce their
103
107
  # costs by storing non-critical, reproducible data at lower
104
108
  # levels of redundancy than Amazon S3's standard storage.
109
+ # * +use_accelerate_endpoint+: Use accelerate endpoint
110
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
111
+ #
112
+ # You can set storage class on a per style bases by doing the following:
113
+ # :s3_storage_class => {
114
+ # :thumb => :REDUCED_REDUNDANCY
115
+ # }
116
+ #
117
+ # Or globally:
118
+ # :s3_storage_class => :REDUCED_REDUNDANCY
119
+ #
120
+ # Other storage classes, such as <tt>:STANDARD_IA</tt>, are also available—see the
121
+ # documentation for the <tt>aws-sdk</tt> gem for the full list.
122
+
105
123
  module S3
106
124
  def self.extended base
107
125
  begin
@@ -109,73 +127,73 @@ module Paperclip
109
127
  rescue LoadError => e
110
128
  e.message << " (You may need to install the aws-sdk gem)"
111
129
  raise e
112
- end unless defined?(AWS::Core)
113
-
114
- # Overriding log formatter to make sure it return a UTF-8 string
115
- if defined?(AWS::Core::LogFormatter)
116
- AWS::Core::LogFormatter.class_eval do
117
- def summarize_hash(hash)
118
- hash.map { |key, value| ":#{key}=>#{summarize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
119
- end
120
- end
121
- elsif defined?(AWS::Core::ClientLogging)
122
- AWS::Core::ClientLogging.class_eval do
123
- def sanitize_hash(hash)
124
- hash.map { |key, value| "#{sanitize_value(key)}=>#{sanitize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
125
- end
126
- end
130
+ end
131
+ if Gem::Version.new(Aws::VERSION) >= Gem::Version.new(2) &&
132
+ Gem::Version.new(Aws::VERSION) <= Gem::Version.new("2.0.33")
133
+ raise LoadError, "paperclip does not support aws-sdk versions 2.0.0 - 2.0.33. Please upgrade aws-sdk to a newer version."
127
134
  end
128
135
 
129
136
  base.instance_eval do
130
137
  @s3_options = @options[:s3_options] || {}
131
138
  @s3_permissions = set_permissions(@options[:s3_permissions])
132
- @s3_protocol = @options[:s3_protocol] ||
133
- Proc.new do |style, attachment|
134
- permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
135
- permission = permission.call(attachment, style) if permission.respond_to?(:call)
136
- (permission == :public_read) ? 'http' : 'https'
137
- end
139
+ @s3_protocol = @options[:s3_protocol] || "".freeze
138
140
  @s3_metadata = @options[:s3_metadata] || {}
139
141
  @s3_headers = {}
140
142
  merge_s3_headers(@options[:s3_headers], @s3_headers, @s3_metadata)
141
143
 
142
- @s3_headers[:storage_class] = @options[:s3_storage_class] if @options[:s3_storage_class]
144
+ @s3_storage_class = set_storage_class(@options[:s3_storage_class])
143
145
 
144
- @s3_server_side_encryption = :aes256
146
+ @s3_server_side_encryption = "AES256"
145
147
  if @options[:s3_server_side_encryption].blank?
146
148
  @s3_server_side_encryption = false
147
149
  end
148
150
  if @s3_server_side_encryption
149
- @s3_server_side_encryption = @options[:s3_server_side_encryption].to_s.upcase
151
+ @s3_server_side_encryption = @options[:s3_server_side_encryption]
150
152
  end
151
153
 
152
- unless @options[:url].to_s.match(/\A:s3.*url\Z/) || @options[:url] == ":asset_host"
153
- @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/\A:rails_root\/public\/system/, '')
154
- @options[:url] = ":s3_path_url"
154
+ unless @options[:url].to_s.match(/\A:s3.*url\z/) || @options[:url] == ":asset_host".freeze
155
+ @options[:path] = path_option.gsub(/:url/, @options[:url]).sub(/\A:rails_root\/public\/system/, "".freeze)
156
+ @options[:url] = ":s3_path_url".freeze
155
157
  end
156
158
  @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)
157
159
 
158
160
  @http_proxy = @options[:http_proxy] || nil
161
+
162
+ if @options.has_key?(:use_accelerate_endpoint) &&
163
+ Gem::Version.new(Aws::VERSION) < Gem::Version.new("2.3.0")
164
+ raise LoadError, ":use_accelerate_endpoint is only available from aws-sdk version 2.3.0. Please upgrade aws-sdk to a newer version."
165
+ end
166
+
167
+ @use_accelerate_endpoint = @options[:use_accelerate_endpoint]
159
168
  end
160
169
 
161
170
  Paperclip.interpolates(:s3_alias_url) do |attachment, style|
162
- "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{\A/}, "")}"
171
+ protocol = attachment.s3_protocol(style, true)
172
+ host = attachment.s3_host_alias
173
+ path = attachment.path(style).
174
+ split("/")[attachment.s3_prefixes_in_alias..-1].
175
+ join("/").
176
+ sub(%r{\A/}, "".freeze)
177
+ "#{protocol}//#{host}/#{path}"
163
178
  end unless Paperclip::Interpolations.respond_to? :s3_alias_url
164
179
  Paperclip.interpolates(:s3_path_url) do |attachment, style|
165
- "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{\A/}, "")}"
180
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
166
181
  end unless Paperclip::Interpolations.respond_to? :s3_path_url
167
182
  Paperclip.interpolates(:s3_domain_url) do |attachment, style|
168
- "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).gsub(%r{\A/}, "")}"
183
+ "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
169
184
  end unless Paperclip::Interpolations.respond_to? :s3_domain_url
170
185
  Paperclip.interpolates(:asset_host) do |attachment, style|
171
- "#{attachment.path(style).gsub(%r{\A/}, "")}"
186
+ "#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
172
187
  end unless Paperclip::Interpolations.respond_to? :asset_host
173
188
  end
174
189
 
175
190
  def expiring_url(time = 3600, style_name = default_style)
176
191
  if path(style_name)
177
- base_options = { :expires => time, :secure => use_secure_protocol?(style_name) }
178
- s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s
192
+ base_options = { expires_in: time }
193
+ s3_object(style_name).presigned_url(
194
+ :get,
195
+ base_options.merge(s3_url_options),
196
+ ).to_s
179
197
  else
180
198
  url(style_name)
181
199
  end
@@ -189,7 +207,14 @@ module Paperclip
189
207
  host_name = @options[:s3_host_name]
190
208
  host_name = host_name.call(self) if host_name.is_a?(Proc)
191
209
 
192
- host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com"
210
+ host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com".freeze
211
+ end
212
+
213
+ def s3_region
214
+ region = @options[:s3_region]
215
+ region = region.call(self) if region.is_a?(Proc)
216
+
217
+ region || s3_credentials[:s3_region]
193
218
  end
194
219
 
195
220
  def s3_host_alias
@@ -198,6 +223,10 @@ module Paperclip
198
223
  @s3_host_alias
199
224
  end
200
225
 
226
+ def s3_prefixes_in_alias
227
+ @s3_prefixes_in_alias ||= @options[:s3_prefixes_in_alias].to_i
228
+ end
229
+
201
230
  def s3_url_options
202
231
  s3_url_options = @options[:s3_url_options] || {}
203
232
  s3_url_options = s3_url_options.call(instance) if s3_url_options.respond_to?(:call)
@@ -212,7 +241,7 @@ module Paperclip
212
241
 
213
242
  def s3_interface
214
243
  @s3_interface ||= begin
215
- config = { :s3_endpoint => s3_host_name }
244
+ config = { region: s3_region }
216
245
 
217
246
  if using_http_proxy?
218
247
 
@@ -226,7 +255,9 @@ module Paperclip
226
255
  config[:proxy_uri] = URI::HTTP.build(proxy_opts)
227
256
  end
228
257
 
229
- [:access_key_id, :secret_access_key, :credential_provider].each do |opt|
258
+ config[:use_accelerate_endpoint] = use_accelerate_endpoint?
259
+
260
+ [:access_key_id, :secret_access_key, :credential_provider, :credentials].each do |opt|
230
261
  config[opt] = s3_credentials[opt] if s3_credentials[opt]
231
262
  end
232
263
 
@@ -236,15 +267,23 @@ module Paperclip
236
267
 
237
268
  def obtain_s3_instance_for(options)
238
269
  instances = (Thread.current[:paperclip_s3_instances] ||= {})
239
- instances[options] ||= AWS::S3.new(options)
270
+ instances[options] ||= ::Aws::S3::Resource.new(options)
240
271
  end
241
272
 
242
273
  def s3_bucket
243
- @s3_bucket ||= s3_interface.buckets[bucket_name]
274
+ @s3_bucket ||= s3_interface.bucket(bucket_name)
275
+ end
276
+
277
+ def style_name_as_path(style_name)
278
+ path(style_name).sub(%r{\A/},'')
244
279
  end
245
280
 
246
281
  def s3_object style_name = default_style
247
- s3_bucket.objects[path(style_name).sub(%r{\A/},'')]
282
+ s3_bucket.object style_name_as_path(style_name)
283
+ end
284
+
285
+ def use_accelerate_endpoint?
286
+ !!@use_accelerate_endpoint
248
287
  end
249
288
 
250
289
  def using_http_proxy?
@@ -269,14 +308,18 @@ module Paperclip
269
308
 
270
309
  def set_permissions permissions
271
310
  permissions = { :default => permissions } unless permissions.respond_to?(:merge)
272
- permissions.merge :default => (permissions[:default] || :public_read)
311
+ permissions.merge :default => (permissions[:default] || :"public-read")
312
+ end
313
+
314
+ def set_storage_class(storage_class)
315
+ storage_class = {:default => storage_class} unless storage_class.respond_to?(:merge)
316
+ storage_class
273
317
  end
274
318
 
275
319
  def parse_credentials creds
276
- creds = creds.respond_to?('call') ? creds.call(self) : creds
320
+ creds = creds.respond_to?(:call) ? creds.call(self) : creds
277
321
  creds = find_credentials(creds).stringify_keys
278
- env = Object.const_defined?(:Rails) ? Rails.env : nil
279
- (creds[env] || creds).symbolize_keys
322
+ (creds[RailsEnvironment.get] || creds).symbolize_keys
280
323
  end
281
324
 
282
325
  def exists?(style = default_style)
@@ -285,7 +328,7 @@ module Paperclip
285
328
  else
286
329
  false
287
330
  end
288
- rescue AWS::Errors::Base => e
331
+ rescue Aws::Errors::ServiceError => e
289
332
  false
290
333
  end
291
334
 
@@ -295,6 +338,10 @@ module Paperclip
295
338
  s3_permissions
296
339
  end
297
340
 
341
+ def s3_storage_class(style = default_style)
342
+ @s3_storage_class[style] || @s3_storage_class[:default]
343
+ end
344
+
298
345
  def s3_protocol(style = default_style, with_colon = false)
299
346
  protocol = @s3_protocol
300
347
  protocol = protocol.call(style, self) if protocol.respond_to?(:call)
@@ -307,19 +354,23 @@ module Paperclip
307
354
  end
308
355
 
309
356
  def create_bucket
310
- s3_interface.buckets.create(bucket_name)
357
+ s3_interface.bucket(bucket_name).create
311
358
  end
312
359
 
313
360
  def flush_writes #:nodoc:
314
361
  @queued_for_write.each do |style, file|
362
+ retries = 0
315
363
  begin
316
364
  log("saving #{path(style)}")
317
- acl = @s3_permissions[style] || @s3_permissions[:default]
318
- acl = acl.call(self, style) if acl.respond_to?(:call)
319
365
  write_options = {
320
366
  :content_type => file.content_type,
321
- :acl => acl
367
+ :acl => s3_permissions(style)
322
368
  }
369
+
370
+ # add storage class for this style if defined
371
+ storage_class = s3_storage_class(style)
372
+ write_options.merge!(:storage_class => storage_class) if storage_class
373
+
323
374
  if @s3_server_side_encryption
324
375
  write_options[:server_side_encryption] = @s3_server_side_encryption
325
376
  end
@@ -334,10 +385,18 @@ module Paperclip
334
385
  write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?
335
386
  write_options.merge!(@s3_headers)
336
387
 
337
- s3_object(style).write(file, write_options)
338
- rescue AWS::S3::Errors::NoSuchBucket => e
388
+ s3_object(style).upload_file(file.path, write_options)
389
+ rescue ::Aws::S3::Errors::NoSuchBucket
339
390
  create_bucket
340
391
  retry
392
+ rescue ::Aws::S3::Errors::SlowDown
393
+ retries += 1
394
+ if retries <= 5
395
+ sleep((2 ** retries) * 0.5)
396
+ retry
397
+ else
398
+ raise
399
+ end
341
400
  ensure
342
401
  file.rewind
343
402
  end
@@ -352,8 +411,8 @@ module Paperclip
352
411
  @queued_for_delete.each do |path|
353
412
  begin
354
413
  log("deleting #{path}")
355
- s3_bucket.objects[path.sub(%r{\A/},'')].delete
356
- rescue AWS::Errors::Base => e
414
+ s3_bucket.object(path.sub(%r{\A/}, "")).delete
415
+ rescue Aws::Errors::ServiceError => e
357
416
  # Ignore this.
358
417
  end
359
418
  end
@@ -362,11 +421,12 @@ module Paperclip
362
421
 
363
422
  def copy_to_local_file(style, local_dest_path)
364
423
  log("copying #{path(style)} to local file #{local_dest_path}")
365
- local_file = ::File.open(local_dest_path, 'wb')
366
- file = s3_object(style)
367
- local_file.write(file.read)
368
- local_file.close
369
- rescue AWS::Errors::Base => e
424
+ ::File.open(local_dest_path, 'wb') do |local_file|
425
+ s3_object(style).get do |chunk|
426
+ local_file.write(chunk)
427
+ end
428
+ end
429
+ rescue Aws::Errors::ServiceError => e
370
430
  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
371
431
  false
372
432
  end
@@ -29,7 +29,7 @@ module Paperclip
29
29
  @geometry, @format = [definition, nil].flatten[0..1]
30
30
  @other_args = {}
31
31
  end
32
- @format = nil if @format.blank?
32
+ @format = default_format if @format.blank?
33
33
  end
34
34
 
35
35
  # retrieves from the attachment the processors defined in the has_attached_file call
@@ -71,7 +71,7 @@ module Paperclip
71
71
  # Arguments other than the standard geometry, format etc are just passed through from
72
72
  # initialization and any procs are called here, just before post-processing.
73
73
  def processor_options
74
- args = {}
74
+ args = {:style => name}
75
75
  @other_args.each do |k,v|
76
76
  args[k] = v.respond_to?(:call) ? v.call(attachment) : v
77
77
  end
@@ -99,5 +99,11 @@ module Paperclip
99
99
  end
100
100
  end
101
101
 
102
+ # defaults to default format (nil by default)
103
+ def default_format
104
+ base = attachment.options[:default_format]
105
+ base.respond_to?(:call) ? base.call(attachment, name) : base
106
+ end
107
+
102
108
  end
103
109
  end
@@ -1,7 +1,7 @@
1
1
  module Paperclip
2
2
  class TempfileFactory
3
3
 
4
- def generate(name)
4
+ def generate(name = random_name)
5
5
  @name = name
6
6
  file = Tempfile.new([basename, extension])
7
7
  file.binmode
@@ -15,5 +15,9 @@ module Paperclip
15
15
  def basename
16
16
  Digest::MD5.hexdigest(File.basename(@name, extension))
17
17
  end
18
+
19
+ def random_name
20
+ SecureRandom.uuid
21
+ end
18
22
  end
19
23
  end