paperclip 3.5.2 → 5.2.1

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