paperclip 3.5.4 → 4.3.7

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.

Files changed (198) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -6
  3. data/.hound.yml +1066 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +11 -17
  6. data/Appraisals +6 -14
  7. data/CONTRIBUTING.md +13 -8
  8. data/Gemfile +16 -3
  9. data/LICENSE +1 -3
  10. data/NEWS +167 -49
  11. data/README.md +294 -75
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +6 -8
  14. data/features/basic_integration.feature +24 -6
  15. data/features/step_definitions/attachment_steps.rb +30 -22
  16. data/features/step_definitions/html_steps.rb +2 -2
  17. data/features/step_definitions/rails_steps.rb +44 -14
  18. data/features/step_definitions/web_steps.rb +1 -103
  19. data/features/support/env.rb +2 -2
  20. data/features/support/file_helpers.rb +2 -2
  21. data/features/support/fixtures/gemfile.txt +1 -1
  22. data/features/support/rails.rb +2 -1
  23. data/gemfiles/3.2.gemfile +14 -6
  24. data/gemfiles/4.1.gemfile +19 -0
  25. data/gemfiles/4.2.gemfile +19 -0
  26. data/lib/generators/paperclip/paperclip_generator.rb +0 -2
  27. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +1 -1
  28. data/lib/paperclip/attachment.rb +132 -38
  29. data/lib/paperclip/attachment_registry.rb +1 -1
  30. data/lib/paperclip/callbacks.rb +11 -1
  31. data/lib/paperclip/content_type_detector.rb +25 -22
  32. data/lib/paperclip/deprecations.rb +42 -0
  33. data/lib/paperclip/errors.rb +5 -0
  34. data/lib/paperclip/file_command_content_type_detector.rb +6 -8
  35. data/lib/paperclip/geometry_detector_factory.rb +3 -1
  36. data/lib/paperclip/geometry_parser_factory.rb +1 -1
  37. data/lib/paperclip/has_attached_file.rb +10 -0
  38. data/lib/paperclip/interpolations/plural_cache.rb +6 -5
  39. data/lib/paperclip/interpolations.rb +25 -12
  40. data/lib/paperclip/io_adapters/abstract_adapter.rb +3 -1
  41. data/lib/paperclip/io_adapters/attachment_adapter.rb +4 -4
  42. data/lib/paperclip/io_adapters/data_uri_adapter.rb +5 -10
  43. data/lib/paperclip/io_adapters/stringio_adapter.rb +6 -10
  44. data/lib/paperclip/io_adapters/uri_adapter.rb +30 -11
  45. data/lib/paperclip/locales/de.yml +18 -0
  46. data/lib/paperclip/locales/en.yml +1 -0
  47. data/lib/paperclip/locales/es.yml +18 -0
  48. data/lib/paperclip/locales/ja.yml +18 -0
  49. data/lib/paperclip/locales/pt-BR.yml +18 -0
  50. data/lib/paperclip/locales/zh-CN.yml +18 -0
  51. data/lib/paperclip/locales/zh-HK.yml +18 -0
  52. data/lib/paperclip/locales/zh-TW.yml +18 -0
  53. data/lib/paperclip/matchers/have_attached_file_matcher.rb +2 -1
  54. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +2 -1
  55. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +2 -1
  56. data/lib/paperclip/media_type_spoof_detector.rb +89 -0
  57. data/lib/paperclip/processor.rb +0 -37
  58. data/lib/paperclip/processor_helpers.rb +50 -0
  59. data/lib/paperclip/rails_environment.rb +25 -0
  60. data/lib/paperclip/schema.rb +10 -2
  61. data/lib/paperclip/storage/filesystem.rb +1 -1
  62. data/lib/paperclip/storage/fog.rb +18 -7
  63. data/lib/paperclip/storage/s3.rb +53 -22
  64. data/lib/paperclip/style.rb +8 -2
  65. data/lib/paperclip/tempfile_factory.rb +5 -1
  66. data/lib/paperclip/thumbnail.rb +12 -10
  67. data/lib/paperclip/url_generator.rb +11 -3
  68. data/lib/paperclip/validators/attachment_content_type_validator.rb +4 -0
  69. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  70. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  71. data/lib/paperclip/validators/attachment_presence_validator.rb +4 -0
  72. data/lib/paperclip/validators/attachment_size_validator.rb +11 -3
  73. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  74. data/lib/paperclip/validators.rb +10 -3
  75. data/lib/paperclip/version.rb +1 -1
  76. data/lib/paperclip.rb +26 -8
  77. data/lib/tasks/paperclip.rake +17 -2
  78. data/paperclip.gemspec +16 -14
  79. data/shoulda_macros/paperclip.rb +0 -1
  80. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  81. data/{test/attachment_processing_test.rb → spec/paperclip/attachment_processing_spec.rb} +20 -21
  82. data/spec/paperclip/attachment_registry_spec.rb +130 -0
  83. data/{test/attachment_test.rb → spec/paperclip/attachment_spec.rb} +438 -397
  84. data/{test/content_type_detector_test.rb → spec/paperclip/content_type_detector_spec.rb} +16 -19
  85. data/spec/paperclip/deprecations_spec.rb +65 -0
  86. data/{test/file_command_content_type_detector_test.rb → spec/paperclip/file_command_content_type_detector_spec.rb} +5 -6
  87. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  88. data/spec/paperclip/geometry_detector_spec.rb +39 -0
  89. data/{test/geometry_parser_test.rb → spec/paperclip/geometry_parser_spec.rb} +27 -27
  90. data/{test/geometry_test.rb → spec/paperclip/geometry_spec.rb} +50 -52
  91. data/spec/paperclip/glue_spec.rb +44 -0
  92. data/{test/has_attached_file_test.rb → spec/paperclip/has_attached_file_spec.rb} +45 -28
  93. data/{test/integration_test.rb → spec/paperclip/integration_spec.rb} +134 -126
  94. data/{test/interpolations_test.rb → spec/paperclip/interpolations_spec.rb} +70 -46
  95. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +78 -0
  96. data/{test/io_adapters/attachment_adapter_test.rb → spec/paperclip/io_adapters/attachment_adapter_spec.rb} +27 -29
  97. data/{test/io_adapters/data_uri_adapter_test.rb → spec/paperclip/io_adapters/data_uri_adapter_spec.rb} +26 -17
  98. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  99. data/{test/io_adapters/file_adapter_test.rb → spec/paperclip/io_adapters/file_adapter_spec.rb} +36 -40
  100. data/{test/io_adapters/http_url_proxy_adapter_test.rb → spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb} +31 -29
  101. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  102. data/{test/io_adapters/nil_adapter_test.rb → spec/paperclip/io_adapters/nil_adapter_spec.rb} +7 -7
  103. data/{test/io_adapters/registry_test.rb → spec/paperclip/io_adapters/registry_spec.rb} +10 -7
  104. data/{test/io_adapters/stringio_adapter_test.rb → spec/paperclip/io_adapters/stringio_adapter_spec.rb} +20 -17
  105. data/{test/io_adapters/uploaded_file_adapter_test.rb → spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb} +41 -41
  106. data/{test/io_adapters/uri_adapter_test.rb → spec/paperclip/io_adapters/uri_adapter_spec.rb} +53 -28
  107. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  108. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +99 -0
  109. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  110. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  111. data/spec/paperclip/media_type_spoof_detector_spec.rb +79 -0
  112. data/spec/paperclip/meta_class_spec.rb +30 -0
  113. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  114. data/{test/paperclip_test.rb → spec/paperclip/paperclip_spec.rb} +53 -48
  115. data/spec/paperclip/plural_cache_spec.rb +37 -0
  116. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  117. data/{test/processor_test.rb → spec/paperclip/processor_spec.rb} +5 -5
  118. data/spec/paperclip/rails_environment_spec.rb +33 -0
  119. data/{test/rake_test.rb → spec/paperclip/rake_spec.rb} +15 -15
  120. data/spec/paperclip/schema_spec.rb +248 -0
  121. data/{test/storage/filesystem_test.rb → spec/paperclip/storage/filesystem_spec.rb} +18 -18
  122. data/spec/paperclip/storage/fog_spec.rb +535 -0
  123. data/spec/paperclip/storage/s3_live_spec.rb +182 -0
  124. data/spec/paperclip/storage/s3_spec.rb +1526 -0
  125. data/spec/paperclip/style_spec.rb +255 -0
  126. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  127. data/{test/thumbnail_test.rb → spec/paperclip/thumbnail_spec.rb} +123 -107
  128. data/spec/paperclip/url_generator_spec.rb +211 -0
  129. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  130. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  131. data/{test/validators/attachment_presence_validator_test.rb → spec/paperclip/validators/attachment_presence_validator_spec.rb} +20 -20
  132. data/{test/validators/attachment_size_validator_test.rb → spec/paperclip/validators/attachment_size_validator_spec.rb} +65 -58
  133. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  134. data/spec/paperclip/validators_spec.rb +164 -0
  135. data/spec/spec_helper.rb +43 -0
  136. data/spec/support/assertions.rb +71 -0
  137. data/spec/support/deprecations.rb +9 -0
  138. data/spec/support/fake_model.rb +25 -0
  139. data/spec/support/fake_rails.rb +12 -0
  140. data/spec/support/fixtures/empty.html +1 -0
  141. data/spec/support/fixtures/empty.xlsx +0 -0
  142. data/spec/support/fixtures/spaced file.jpg +0 -0
  143. data/spec/support/matchers/accept.rb +5 -0
  144. data/spec/support/matchers/exist.rb +5 -0
  145. data/spec/support/matchers/have_column.rb +23 -0
  146. data/spec/support/model_reconstruction.rb +60 -0
  147. data/spec/support/rails_helpers.rb +7 -0
  148. data/spec/support/test_data.rb +13 -0
  149. data/spec/support/version_helper.rb +9 -0
  150. metadata +334 -219
  151. data/RUNNING_TESTS.md +0 -4
  152. data/gemfiles/3.0.gemfile +0 -11
  153. data/gemfiles/3.1.gemfile +0 -11
  154. data/gemfiles/4.0.gemfile +0 -11
  155. data/test/attachment_definitions_test.rb +0 -12
  156. data/test/attachment_registry_test.rb +0 -88
  157. data/test/filename_cleaner_test.rb +0 -14
  158. data/test/generator_test.rb +0 -84
  159. data/test/geometry_detector_test.rb +0 -24
  160. data/test/helper.rb +0 -232
  161. data/test/io_adapters/abstract_adapter_test.rb +0 -58
  162. data/test/io_adapters/empty_string_adapter_test.rb +0 -18
  163. data/test/io_adapters/identity_adapter_test.rb +0 -8
  164. data/test/matchers/have_attached_file_matcher_test.rb +0 -24
  165. data/test/matchers/validate_attachment_content_type_matcher_test.rb +0 -110
  166. data/test/matchers/validate_attachment_presence_matcher_test.rb +0 -69
  167. data/test/matchers/validate_attachment_size_matcher_test.rb +0 -86
  168. data/test/meta_class_test.rb +0 -32
  169. data/test/paperclip_missing_attachment_styles_test.rb +0 -90
  170. data/test/plural_cache_test.rb +0 -36
  171. data/test/schema_test.rb +0 -200
  172. data/test/storage/fog_test.rb +0 -473
  173. data/test/storage/s3_live_test.rb +0 -179
  174. data/test/storage/s3_test.rb +0 -1356
  175. data/test/style_test.rb +0 -213
  176. data/test/support/mock_model.rb +0 -2
  177. data/test/tempfile_factory_test.rb +0 -17
  178. data/test/url_generator_test.rb +0 -187
  179. data/test/validators/attachment_content_type_validator_test.rb +0 -324
  180. data/test/validators_test.rb +0 -61
  181. /data/{test → spec}/database.yml +0 -0
  182. /data/{test → spec/support}/fixtures/12k.png +0 -0
  183. /data/{test → spec/support}/fixtures/50x50.png +0 -0
  184. /data/{test → spec/support}/fixtures/5k.png +0 -0
  185. /data/{test → spec/support}/fixtures/animated +0 -0
  186. /data/{test → spec/support}/fixtures/animated.gif +0 -0
  187. /data/{test → spec/support}/fixtures/animated.unknown +0 -0
  188. /data/{test → spec/support}/fixtures/bad.png +0 -0
  189. /data/{test → spec/support}/fixtures/fog.yml +0 -0
  190. /data/{test → spec/support}/fixtures/rotated.jpg +0 -0
  191. /data/{test → spec/support}/fixtures/s3.yml +0 -0
  192. /data/{test → spec/support}/fixtures/spaced file.png +0 -0
  193. /data/{test → spec/support}/fixtures/text.txt +0 -0
  194. /data/{test → spec/support}/fixtures/twopage.pdf +0 -0
  195. /data/{test → spec/support}/fixtures/uppercase.PNG +0 -0
  196. /data/{test → spec}/support/mock_attachment.rb +0 -0
  197. /data/{test → spec}/support/mock_interpolator.rb +0 -0
  198. /data/{test → spec}/support/mock_url_generator_builder.rb +0 -0
@@ -4,7 +4,7 @@ module Paperclip
4
4
  # distribution. You can find out more about it at http://aws.amazon.com/s3
5
5
  #
6
6
  # To use Paperclip with S3, include the +aws-sdk+ gem in your Gemfile:
7
- # gem 'aws-sdk'
7
+ # gem 'aws-sdk', '~> 1.6'
8
8
  # There are a few S3-specific options for has_attached_file:
9
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
@@ -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
@@ -51,7 +51,7 @@ module Paperclip
51
51
  # :s3_permissions => :private
52
52
  #
53
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'
54
+ # 'http', 'https', or an empty string to generate protocol-relative URLs. Defaults to 'http'
55
55
  # when your :s3_permissions are :public_read (the default), and 'https' when your
56
56
  # :s3_permissions are anything else.
57
57
  # * +s3_headers+: A hash of headers or a Proc. You may specify a hash such as
@@ -102,6 +102,14 @@ module Paperclip
102
102
  # Redundancy Storage. RRS enables customers to reduce their
103
103
  # costs by storing non-critical, reproducible data at lower
104
104
  # levels of redundancy than Amazon S3's standard storage.
105
+ #
106
+ # You can set storage class on a per style bases by doing the following:
107
+ # :s3_storage_class => {
108
+ # :thumb => :reduced_reduncancy
109
+ # }
110
+ # Or globally:
111
+ # :s3_storage_class => :reduced_redundancy
112
+
105
113
  module S3
106
114
  def self.extended base
107
115
  begin
@@ -133,25 +141,25 @@ module Paperclip
133
141
  Proc.new do |style, attachment|
134
142
  permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
135
143
  permission = permission.call(attachment, style) if permission.respond_to?(:call)
136
- (permission == :public_read) ? 'http' : 'https'
144
+ (permission == :public_read) ? 'http'.freeze : 'https'.freeze
137
145
  end
138
146
  @s3_metadata = @options[:s3_metadata] || {}
139
147
  @s3_headers = {}
140
148
  merge_s3_headers(@options[:s3_headers], @s3_headers, @s3_metadata)
141
149
 
142
- @s3_headers[:storage_class] = @options[:s3_storage_class] if @options[:s3_storage_class]
150
+ @s3_storage_class = set_storage_class(@options[:s3_storage_class])
143
151
 
144
152
  @s3_server_side_encryption = :aes256
145
153
  if @options[:s3_server_side_encryption].blank?
146
154
  @s3_server_side_encryption = false
147
155
  end
148
156
  if @s3_server_side_encryption
149
- @s3_server_side_encryption = @options[:s3_server_side_encryption].to_s.upcase
157
+ @s3_server_side_encryption = @options[:s3_server_side_encryption]
150
158
  end
151
159
 
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"
160
+ unless @options[:url].to_s.match(/\A:s3.*url\Z/) || @options[:url] == ":asset_host".freeze
161
+ @options[:path] = path_option.gsub(/:url/, @options[:url]).sub(/\A:rails_root\/public\/system/, "".freeze)
162
+ @options[:url] = ":s3_path_url".freeze
155
163
  end
156
164
  @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)
157
165
 
@@ -159,16 +167,16 @@ module Paperclip
159
167
  end
160
168
 
161
169
  Paperclip.interpolates(:s3_alias_url) do |attachment, style|
162
- "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{\A/}, "")}"
170
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
163
171
  end unless Paperclip::Interpolations.respond_to? :s3_alias_url
164
172
  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/}, "")}"
173
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
166
174
  end unless Paperclip::Interpolations.respond_to? :s3_path_url
167
175
  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/}, "")}"
176
+ "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
169
177
  end unless Paperclip::Interpolations.respond_to? :s3_domain_url
170
178
  Paperclip.interpolates(:asset_host) do |attachment, style|
171
- "#{attachment.path(style).gsub(%r{\A/}, "")}"
179
+ "#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
172
180
  end unless Paperclip::Interpolations.respond_to? :asset_host
173
181
  end
174
182
 
@@ -189,7 +197,7 @@ module Paperclip
189
197
  host_name = @options[:s3_host_name]
190
198
  host_name = host_name.call(self) if host_name.is_a?(Proc)
191
199
 
192
- host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com"
200
+ host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com".freeze
193
201
  end
194
202
 
195
203
  def s3_host_alias
@@ -272,11 +280,15 @@ module Paperclip
272
280
  permissions.merge :default => (permissions[:default] || :public_read)
273
281
  end
274
282
 
283
+ def set_storage_class(storage_class)
284
+ storage_class = {:default => storage_class} unless storage_class.respond_to?(:merge)
285
+ storage_class
286
+ end
287
+
275
288
  def parse_credentials creds
276
- creds = creds.respond_to?('call') ? creds.call(self) : creds
289
+ creds = creds.respond_to?(:call) ? creds.call(self) : creds
277
290
  creds = find_credentials(creds).stringify_keys
278
- env = Object.const_defined?(:Rails) ? Rails.env : nil
279
- (creds[env] || creds).symbolize_keys
291
+ (creds[RailsEnvironment.get] || creds).symbolize_keys
280
292
  end
281
293
 
282
294
  def exists?(style = default_style)
@@ -295,6 +307,10 @@ module Paperclip
295
307
  s3_permissions
296
308
  end
297
309
 
310
+ def s3_storage_class(style = default_style)
311
+ @s3_storage_class[style] || @s3_storage_class[:default]
312
+ end
313
+
298
314
  def s3_protocol(style = default_style, with_colon = false)
299
315
  protocol = @s3_protocol
300
316
  protocol = protocol.call(style, self) if protocol.respond_to?(:call)
@@ -312,6 +328,7 @@ module Paperclip
312
328
 
313
329
  def flush_writes #:nodoc:
314
330
  @queued_for_write.each do |style, file|
331
+ retries = 0
315
332
  begin
316
333
  log("saving #{path(style)}")
317
334
  acl = @s3_permissions[style] || @s3_permissions[:default]
@@ -320,6 +337,11 @@ module Paperclip
320
337
  :content_type => file.content_type,
321
338
  :acl => acl
322
339
  }
340
+
341
+ # add storage class for this style if defined
342
+ storage_class = s3_storage_class(style)
343
+ write_options.merge!(:storage_class => storage_class) if storage_class
344
+
323
345
  if @s3_server_side_encryption
324
346
  write_options[:server_side_encryption] = @s3_server_side_encryption
325
347
  end
@@ -335,9 +357,17 @@ module Paperclip
335
357
  write_options.merge!(@s3_headers)
336
358
 
337
359
  s3_object(style).write(file, write_options)
338
- rescue AWS::S3::Errors::NoSuchBucket => e
360
+ rescue AWS::S3::Errors::NoSuchBucket
339
361
  create_bucket
340
362
  retry
363
+ rescue AWS::S3::Errors::SlowDown
364
+ retries += 1
365
+ if retries <= 5
366
+ sleep((2 ** retries) * 0.5)
367
+ retry
368
+ else
369
+ raise
370
+ end
341
371
  ensure
342
372
  file.rewind
343
373
  end
@@ -362,10 +392,11 @@ module Paperclip
362
392
 
363
393
  def copy_to_local_file(style, local_dest_path)
364
394
  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
395
+ ::File.open(local_dest_path, 'wb') do |local_file|
396
+ s3_object(style).read do |chunk|
397
+ local_file.write(chunk)
398
+ end
399
+ end
369
400
  rescue AWS::Errors::Base => e
370
401
  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
371
402
  false
@@ -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
@@ -28,17 +28,16 @@ module Paperclip
28
28
  def initialize(file, options = {}, attachment = nil)
29
29
  super
30
30
 
31
- geometry = options[:geometry] # this is not an option
32
- @file = file
31
+ geometry = options[:geometry].to_s
33
32
  @crop = geometry[-1,1] == '#'
34
- @target_geometry = (options[:string_geometry_parser] || Geometry).parse(geometry)
35
- @current_geometry = (options[:file_geometry_parser] || Geometry).from_file(@file)
33
+ @target_geometry = options.fetch(:string_geometry_parser, Geometry).parse(geometry)
34
+ @current_geometry = options.fetch(:file_geometry_parser, Geometry).from_file(@file)
36
35
  @source_file_options = options[:source_file_options]
37
36
  @convert_options = options[:convert_options]
38
- @whiny = options[:whiny].nil? ? true : options[:whiny]
37
+ @whiny = options.fetch(:whiny, true)
39
38
  @format = options[:format]
40
- @animated = options[:animated].nil? ? true : options[:animated]
41
- @auto_orient = options[:auto_orient].nil? ? true : options[:auto_orient]
39
+ @animated = options.fetch(:animated, true)
40
+ @auto_orient = options.fetch(:auto_orient, true)
42
41
  if @auto_orient && @current_geometry.respond_to?(:auto_orient)
43
42
  @current_geometry.auto_orient
44
43
  end
@@ -64,8 +63,8 @@ module Paperclip
64
63
  # that contains the new image.
65
64
  def make
66
65
  src = @file
67
- dst = Tempfile.new([@basename, @format ? ".#{@format}" : ''])
68
- dst.binmode
66
+ filename = [@basename, @format ? ".#{@format}" : ""].join
67
+ dst = TempfileFactory.new.generate(filename)
69
68
 
70
69
  begin
71
70
  parameters = []
@@ -109,7 +108,10 @@ module Paperclip
109
108
 
110
109
  # Return true if ImageMagick's +identify+ returns an animated format
111
110
  def identified_as_animated?
112
- ANIMATED_FORMATS.include? identify("-format %m :file", :file => "#{@file.path}[0]").to_s.downcase.strip
111
+ if @identified_as_animated.nil?
112
+ @identified_as_animated = ANIMATED_FORMATS.include? identify("-format %m :file", :file => "#{@file.path}[0]").to_s.downcase.strip
113
+ end
114
+ @identified_as_animated
113
115
  rescue Cocaine::ExitStatusError => e
114
116
  raise Paperclip::Error, "There was an error running `identify` for #{@basename}" if @whiny
115
117
  rescue Cocaine::CommandNotFoundError => e
@@ -8,8 +8,8 @@ module Paperclip
8
8
  end
9
9
 
10
10
  def for(style_name, options)
11
- escape_url_as_needed(
12
- timestamp_as_needed(
11
+ timestamp_as_needed(
12
+ escape_url_as_needed(
13
13
  @attachment_options[:interpolator].interpolate(most_appropriate_url, @attachment, style_name),
14
14
  options
15
15
  ), options)
@@ -58,7 +58,15 @@ module Paperclip
58
58
  end
59
59
 
60
60
  def escape_url(url)
61
- (url.respond_to?(:escape) ? url.escape : URI.escape(url)).gsub(/(\/.+)\?(.+\.)/, '\1%3F\2')
61
+ if url.respond_to?(:escape)
62
+ url.escape
63
+ else
64
+ URI.escape(url).gsub(escape_regex){|m| "%#{m.ord.to_s(16).upcase}" }
65
+ end
66
+ end
67
+
68
+ def escape_regex
69
+ /[\?\(\)\[\]\+]/
62
70
  end
63
71
  end
64
72
  end
@@ -6,6 +6,10 @@ module Paperclip
6
6
  super
7
7
  end
8
8
 
9
+ def self.helper_method_name
10
+ :validates_attachment_content_type
11
+ end
12
+
9
13
  def validate_each(record, attribute, value)
10
14
  base_attribute = attribute.to_sym
11
15
  attribute = "#{attribute}_content_type".to_sym
@@ -0,0 +1,80 @@
1
+ module Paperclip
2
+ module Validators
3
+ class AttachmentFileNameValidator < ActiveModel::EachValidator
4
+ def initialize(options)
5
+ options[:allow_nil] = true unless options.has_key?(:allow_nil)
6
+ super
7
+ end
8
+
9
+ def self.helper_method_name
10
+ :validates_attachment_file_name
11
+ end
12
+
13
+ def validate_each(record, attribute, value)
14
+ base_attribute = attribute.to_sym
15
+ attribute = "#{attribute}_file_name".to_sym
16
+ value = record.send :read_attribute_for_validation, attribute
17
+
18
+ return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
19
+
20
+ validate_whitelist(record, attribute, value)
21
+ validate_blacklist(record, attribute, value)
22
+
23
+ if record.errors.include? attribute
24
+ record.errors[attribute].each do |error|
25
+ record.errors.add base_attribute, error
26
+ end
27
+ end
28
+ end
29
+
30
+ def validate_whitelist(record, attribute, value)
31
+ if allowed.present? && allowed.none? { |type| type === value }
32
+ mark_invalid record, attribute, allowed
33
+ end
34
+ end
35
+
36
+ def validate_blacklist(record, attribute, value)
37
+ if forbidden.present? && forbidden.any? { |type| type === value }
38
+ mark_invalid record, attribute, forbidden
39
+ end
40
+ end
41
+
42
+ def mark_invalid(record, attribute, patterns)
43
+ record.errors.add attribute, :invalid, options.merge(:names => patterns.join(', '))
44
+ end
45
+
46
+ def allowed
47
+ [options[:matches]].flatten.compact
48
+ end
49
+
50
+ def forbidden
51
+ [options[:not]].flatten.compact
52
+ end
53
+
54
+ def check_validity!
55
+ unless options.has_key?(:matches) || options.has_key?(:not)
56
+ raise ArgumentError, "You must pass in either :matches or :not to the validator"
57
+ end
58
+ end
59
+ end
60
+
61
+ module HelperMethods
62
+ # Places ActiveModel validations on the name of the file
63
+ # assigned. The possible options are:
64
+ # * +matches+: Allowed filename patterns as Regexps. Can be a single one
65
+ # or an array.
66
+ # * +not+: Forbidden file name patterns, specified the same was as +matches+.
67
+ # * +message+: The message to display when the uploaded file has an invalid
68
+ # name.
69
+ # * +if+: A lambda or name of an instance method. Validation will only
70
+ # be run is this lambda or method returns true.
71
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
72
+ def validates_attachment_file_name(*attr_names)
73
+ options = _merge_attributes(attr_names)
74
+ validates_with AttachmentFileNameValidator, options.dup
75
+ validate_before_processing AttachmentFileNameValidator, options.dup
76
+ end
77
+ end
78
+ end
79
+ end
80
+
@@ -0,0 +1,29 @@
1
+ require 'active_model/validations/presence'
2
+
3
+ module Paperclip
4
+ module Validators
5
+ class AttachmentFileTypeIgnoranceValidator < ActiveModel::EachValidator
6
+ def validate_each(record, attribute, value)
7
+ # This doesn't do anything. It's just to mark that you don't care about
8
+ # the file_names or content_types of your incoming attachments.
9
+ end
10
+
11
+ def self.helper_method_name
12
+ :do_not_validate_attachment_file_type
13
+ end
14
+ end
15
+
16
+ module HelperMethods
17
+ # Places ActiveModel validations on the presence of a file.
18
+ # Options:
19
+ # * +if+: A lambda or name of an instance method. Validation will only
20
+ # be run if this lambda or method returns true.
21
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
22
+ def do_not_validate_attachment_file_type(*attr_names)
23
+ options = _merge_attributes(attr_names)
24
+ validates_with AttachmentFileTypeIgnoranceValidator, options.dup
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -8,6 +8,10 @@ module Paperclip
8
8
  record.errors.add(attribute, :blank, options)
9
9
  end
10
10
  end
11
+
12
+ def self.helper_method_name
13
+ :validates_attachment_presence
14
+ end
11
15
  end
12
16
 
13
17
  module HelperMethods
@@ -10,6 +10,10 @@ module Paperclip
10
10
  super
11
11
  end
12
12
 
13
+ def self.helper_method_name
14
+ :validates_attachment_size
15
+ end
16
+
13
17
  def validate_each(record, attr_name, value)
14
18
  base_attr_name = attr_name
15
19
  attr_name = "#{attr_name}_file_size".to_sym
@@ -67,9 +71,13 @@ module Paperclip
67
71
  end
68
72
 
69
73
  def human_size(size)
70
- storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
71
- unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => size.to_i, :raise => true)
72
- storage_units_format.gsub(/%n/, size.to_i.to_s).gsub(/%u/, unit).html_safe
74
+ if defined?(ActiveSupport::NumberHelper) # Rails 4.0+
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
73
81
  end
74
82
 
75
83
  def min_value_in_human_size(record)
@@ -0,0 +1,27 @@
1
+ require 'active_model/validations/presence'
2
+
3
+ module Paperclip
4
+ module Validators
5
+ class MediaTypeSpoofDetectionValidator < ActiveModel::EachValidator
6
+ def validate_each(record, attribute, value)
7
+ adapter = Paperclip.io_adapters.for(value)
8
+ if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename, value.content_type).spoofed?
9
+ record.errors.add(attribute, :spoofed_media_type)
10
+ end
11
+ end
12
+ end
13
+
14
+ module HelperMethods
15
+ # Places ActiveModel validations on the presence of a file.
16
+ # Options:
17
+ # * +if+: A lambda or name of an instance method. Validation will only
18
+ # be run if this lambda or method returns true.
19
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
20
+ def validates_media_type_spoof_detection(*attr_names)
21
+ options = _merge_attributes(attr_names)
22
+ validates_with MediaTypeSpoofDetectionValidator, options.dup
23
+ validate_before_processing MediaTypeSpoofDetectionValidator, options.dup
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,8 +1,12 @@
1
1
  require 'active_model'
2
2
  require 'active_support/concern'
3
+ require 'active_support/core_ext/array/wrap'
3
4
  require 'paperclip/validators/attachment_content_type_validator'
5
+ require 'paperclip/validators/attachment_file_name_validator'
4
6
  require 'paperclip/validators/attachment_presence_validator'
5
7
  require 'paperclip/validators/attachment_size_validator'
8
+ require 'paperclip/validators/media_type_spoof_detection_validator'
9
+ require 'paperclip/validators/attachment_file_type_ignorance_validator'
6
10
 
7
11
  module Paperclip
8
12
  module Validators
@@ -13,6 +17,8 @@ module Paperclip
13
17
  include HelperMethods
14
18
  end
15
19
 
20
+ ::Paperclip::REQUIRED_VALIDATORS = [AttachmentFileNameValidator, AttachmentContentTypeValidator, AttachmentFileTypeIgnoranceValidator]
21
+
16
22
  module ClassMethods
17
23
  # This method is a shortcut to validator classes that is in
18
24
  # "Attachment...Validator" format. It is almost the same thing as the
@@ -36,10 +42,11 @@ module Paperclip
36
42
  if options.has_key?(validator_kind)
37
43
  validator_options = options.delete(validator_kind)
38
44
  validator_options = {} if validator_options == true
39
- local_options = attributes + [validator_options]
40
45
  conditional_options = options.slice(:if, :unless)
41
- local_options.last.merge!(conditional_options)
42
- send(:"validates_attachment_#{validator_kind}", *local_options)
46
+ Array.wrap(validator_options).each do |local_options|
47
+ method_name = Paperclip::Validators.const_get(constant.to_s).helper_method_name
48
+ send(method_name, attributes, local_options.merge(conditional_options))
49
+ end
43
50
  end
44
51
  end
45
52
  end
@@ -1,3 +1,3 @@
1
1
  module Paperclip
2
- VERSION = "3.5.4" unless defined? Paperclip::VERSION
2
+ VERSION = "4.3.7".freeze unless defined? Paperclip::VERSION
3
3
  end
data/lib/paperclip.rb CHANGED
@@ -33,6 +33,7 @@ require 'paperclip/geometry_parser_factory'
33
33
  require 'paperclip/geometry_detector_factory'
34
34
  require 'paperclip/geometry'
35
35
  require 'paperclip/processor'
36
+ require 'paperclip/processor_helpers'
36
37
  require 'paperclip/tempfile'
37
38
  require 'paperclip/thumbnail'
38
39
  require 'paperclip/interpolations/plural_cache'
@@ -43,6 +44,7 @@ require 'paperclip/attachment'
43
44
  require 'paperclip/storage'
44
45
  require 'paperclip/callbacks'
45
46
  require 'paperclip/file_command_content_type_detector'
47
+ require 'paperclip/media_type_spoof_detector'
46
48
  require 'paperclip/content_type_detector'
47
49
  require 'paperclip/glue'
48
50
  require 'paperclip/errors'
@@ -53,11 +55,22 @@ require 'paperclip/helpers'
53
55
  require 'paperclip/has_attached_file'
54
56
  require 'paperclip/attachment_registry'
55
57
  require 'paperclip/filename_cleaner'
56
- require 'mime/types'
58
+ require 'paperclip/rails_environment'
59
+ require "paperclip/deprecations"
60
+
61
+ begin
62
+ # Use mime/types/columnar if available, for reduced memory usage
63
+ require "mime/types/columnar"
64
+ rescue LoadError
65
+ require "mime/types"
66
+ end
67
+
68
+ require 'mimemagic'
69
+ require 'mimemagic/overlay'
57
70
  require 'logger'
58
71
  require 'cocaine'
59
72
 
60
- require 'paperclip/railtie' if defined?(Rails)
73
+ require 'paperclip/railtie' if defined?(Rails::Railtie)
61
74
 
62
75
  # The base module that gets included in ActiveRecord::Base. See the
63
76
  # documentation for Paperclip::ClassMethods for more useful information.
@@ -74,14 +87,18 @@ module Paperclip
74
87
  # * command_path: Defines the path at which to find the command line
75
88
  # programs if they are not visible to Rails the system's search path. Defaults to
76
89
  # nil, which uses the first executable found in the user's search path.
90
+ # * use_exif_orientation: Whether to inspect EXIF data to determine an
91
+ # image's orientation. Defaults to true.
77
92
  def self.options
78
93
  @options ||= {
79
- :whiny => true,
94
+ :whiny => true,
80
95
  :image_magick_path => nil,
81
- :command_path => nil,
82
- :log => true,
83
- :log_command => true,
84
- :swallow_stderr => true
96
+ :command_path => nil,
97
+ :log => true,
98
+ :log_command => true,
99
+ :swallow_stderr => true,
100
+ :content_type_mappings => {},
101
+ :use_exif_orientation => true
85
102
  }
86
103
  end
87
104
 
@@ -130,7 +147,7 @@ module Paperclip
130
147
  # user.avatar.url # => "/avatars/23/normal_me.png"
131
148
  # * +keep_old_files+: Keep the existing attachment files (original + resized) from
132
149
  # being automatically deleted when an attachment is cleared or updated. Defaults to +false+.
133
- # * +preserve_files+: Keep the existing attachment files in all cases, even if the parent
150
+ # * +preserve_files+: Keep the existing attachment files in all cases, even if the parent
134
151
  # record is destroyed. Defaults to +false+.
135
152
  # * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
136
153
  # to a command line error. This will override the global setting for this attachment.
@@ -175,6 +192,7 @@ module Paperclip
175
192
  # end
176
193
  # end
177
194
  def has_attached_file(name, options = {})
195
+ Paperclip::Deprecations.check
178
196
  HasAttachedFile.define_on(self, name, options)
179
197
  end
180
198
  end
@@ -78,8 +78,7 @@ namespace :paperclip do
78
78
 
79
79
  desc "Regenerates missing thumbnail styles for all classes using Paperclip."
80
80
  task :missing_styles => :environment do
81
- # Force loading all model classes to never miss any has_attached_file declaration:
82
- Dir[Rails.root + 'app/models/**/*.rb'].each { |path| load path }
81
+ Rails.application.eager_load!
83
82
  Paperclip.missing_attachments_styles.each do |klass, attachment_definitions|
84
83
  attachment_definitions.each do |attachment_name, missing_styles|
85
84
  puts "Regenerating #{klass} -> #{attachment_name} -> #{missing_styles.inspect}"
@@ -109,4 +108,20 @@ namespace :paperclip do
109
108
  end
110
109
  end
111
110
  end
111
+
112
+ desc "find missing attachments. Useful to know which attachments are broken"
113
+ task :find_broken_attachments => :environment do
114
+ klass = Paperclip::Task.obtain_class
115
+ names = Paperclip::Task.obtain_attachments(klass)
116
+ names.each do |name|
117
+ Paperclip.each_instance_with_attachment(klass, name) do |instance|
118
+ attachment = instance.send(name)
119
+ if attachment.exists?
120
+ print "."
121
+ else
122
+ Paperclip::Task.log_error("#{instance.class}##{attachment.name}, #{instance.id}, #{attachment.url}")
123
+ end
124
+ end
125
+ end
126
+ end
112
127
  end