carrierwave 0.11.2 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of carrierwave might be problematic. Click here for more details.

Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +452 -178
  3. data/lib/carrierwave/compatibility/paperclip.rb +4 -4
  4. data/lib/carrierwave/downloader/base.rb +101 -0
  5. data/lib/carrierwave/downloader/remote_file.rb +68 -0
  6. data/lib/carrierwave/error.rb +1 -0
  7. data/lib/carrierwave/locale/en.yml +11 -5
  8. data/lib/carrierwave/mount.rb +212 -182
  9. data/lib/carrierwave/mounter.rb +255 -0
  10. data/lib/carrierwave/orm/activerecord.rb +22 -33
  11. data/lib/carrierwave/processing/mini_magick.rb +140 -84
  12. data/lib/carrierwave/processing/rmagick.rb +72 -21
  13. data/lib/carrierwave/processing/vips.rb +284 -0
  14. data/lib/carrierwave/processing.rb +1 -1
  15. data/lib/carrierwave/sanitized_file.rb +83 -84
  16. data/lib/carrierwave/storage/abstract.rb +16 -3
  17. data/lib/carrierwave/storage/file.rb +71 -3
  18. data/lib/carrierwave/storage/fog.rb +215 -57
  19. data/lib/carrierwave/storage.rb +1 -9
  20. data/lib/carrierwave/test/matchers.rb +88 -19
  21. data/lib/carrierwave/uploader/cache.rb +75 -45
  22. data/lib/carrierwave/uploader/callbacks.rb +1 -3
  23. data/lib/carrierwave/uploader/configuration.rb +80 -16
  24. data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
  25. data/lib/carrierwave/uploader/content_type_denylist.rb +62 -0
  26. data/lib/carrierwave/uploader/default_url.rb +3 -5
  27. data/lib/carrierwave/uploader/dimension.rb +66 -0
  28. data/lib/carrierwave/uploader/download.rb +4 -74
  29. data/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
  30. data/lib/carrierwave/uploader/extension_denylist.rb +64 -0
  31. data/lib/carrierwave/uploader/file_size.rb +43 -0
  32. data/lib/carrierwave/uploader/mountable.rb +13 -8
  33. data/lib/carrierwave/uploader/processing.rb +48 -13
  34. data/lib/carrierwave/uploader/proxy.rb +20 -9
  35. data/lib/carrierwave/uploader/remove.rb +0 -2
  36. data/lib/carrierwave/uploader/serialization.rb +2 -4
  37. data/lib/carrierwave/uploader/store.rb +59 -28
  38. data/lib/carrierwave/uploader/url.rb +8 -7
  39. data/lib/carrierwave/uploader/versions.rb +170 -122
  40. data/lib/carrierwave/uploader.rb +12 -10
  41. data/lib/carrierwave/utilities/file_name.rb +47 -0
  42. data/lib/carrierwave/utilities/uri.rb +14 -12
  43. data/lib/carrierwave/utilities.rb +1 -3
  44. data/lib/carrierwave/validations/active_model.rb +7 -11
  45. data/lib/carrierwave/version.rb +1 -1
  46. data/lib/carrierwave.rb +39 -21
  47. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -9
  48. data/lib/generators/uploader_generator.rb +3 -3
  49. metadata +132 -80
  50. data/lib/carrierwave/locale/cs.yml +0 -11
  51. data/lib/carrierwave/locale/de.yml +0 -11
  52. data/lib/carrierwave/locale/el.yml +0 -11
  53. data/lib/carrierwave/locale/es.yml +0 -11
  54. data/lib/carrierwave/locale/fr.yml +0 -11
  55. data/lib/carrierwave/locale/ja.yml +0 -11
  56. data/lib/carrierwave/locale/nb.yml +0 -11
  57. data/lib/carrierwave/locale/nl.yml +0 -11
  58. data/lib/carrierwave/locale/pl.yml +0 -11
  59. data/lib/carrierwave/locale/pt-BR.yml +0 -11
  60. data/lib/carrierwave/locale/pt-PT.yml +0 -11
  61. data/lib/carrierwave/locale/ru.yml +0 -11
  62. data/lib/carrierwave/locale/sk.yml +0 -11
  63. data/lib/carrierwave/locale/tr.yml +0 -11
  64. data/lib/carrierwave/processing/mime_types.rb +0 -74
  65. data/lib/carrierwave/uploader/content_type_blacklist.rb +0 -48
  66. data/lib/carrierwave/uploader/content_type_whitelist.rb +0 -48
  67. data/lib/carrierwave/uploader/extension_blacklist.rb +0 -47
  68. data/lib/carrierwave/uploader/extension_whitelist.rb +0 -49
  69. data/lib/carrierwave/utilities/deprecation.rb +0 -18
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Storage
5
3
 
@@ -18,6 +16,8 @@ module CarrierWave
18
16
  # [:fog_authenticated_url_expiration] (optional) time (in seconds) that authenticated urls
19
17
  # will be valid, when fog_public is false and provider is AWS or Google, defaults to 600
20
18
  # [:fog_use_ssl_for_aws] (optional) #public_url will use https for the AWS generated URL]
19
+ # [:fog_aws_accelerate] (optional) #public_url will use s3-accelerate subdomain
20
+ # instead of s3, defaults to false
21
21
  #
22
22
  #
23
23
  # AWS credentials contain the following keys:
@@ -25,12 +25,12 @@ module CarrierWave
25
25
  # [:aws_access_key_id]
26
26
  # [:aws_secret_access_key]
27
27
  # [:region] (optional) defaults to 'us-east-1'
28
- # :region should be one of ['eu-west-1', 'us-east-1', 'ap-southeast-1', 'us-west-1', 'ap-northeast-1']
28
+ # :region should be one of ['eu-west-1', 'us-east-1', 'ap-southeast-1', 'us-west-1', 'ap-northeast-1', 'eu-central-1']
29
29
  #
30
30
  #
31
31
  # Google credentials contain the following keys:
32
32
  # [:google_storage_access_key_id]
33
- # [:google_storage_secrete_access_key]
33
+ # [:google_storage_secret_access_key]
34
34
  #
35
35
  #
36
36
  # Local credentials contain the following keys:
@@ -60,6 +60,14 @@ module CarrierWave
60
60
  def connection_cache
61
61
  @connection_cache ||= {}
62
62
  end
63
+
64
+ def eager_load
65
+ # see #1198. This will hopefully no longer be necessary in future release of fog
66
+ fog_credentials = CarrierWave::Uploader::Base.fog_credentials
67
+ if fog_credentials.present?
68
+ CarrierWave::Storage::Fog.connection_cache[fog_credentials] ||= ::Fog::Storage.new(fog_credentials)
69
+ end
70
+ end
63
71
  end
64
72
 
65
73
  ##
@@ -94,6 +102,58 @@ module CarrierWave
94
102
  CarrierWave::Storage::Fog::File.new(uploader, self, uploader.store_path(identifier))
95
103
  end
96
104
 
105
+ ##
106
+ # Stores given file to cache directory.
107
+ #
108
+ # === Parameters
109
+ #
110
+ # [new_file (File, IOString, Tempfile)] any kind of file object
111
+ #
112
+ # === Returns
113
+ #
114
+ # [CarrierWave::SanitizedFile] a sanitized file
115
+ #
116
+ def cache!(new_file)
117
+ f = CarrierWave::Storage::Fog::File.new(uploader, self, uploader.cache_path)
118
+ f.store(new_file)
119
+ f
120
+ end
121
+
122
+ ##
123
+ # Retrieves the file with the given cache_name from the cache.
124
+ #
125
+ # === Parameters
126
+ #
127
+ # [cache_name (String)] uniquely identifies a cache file
128
+ #
129
+ # === Raises
130
+ #
131
+ # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
132
+ #
133
+ def retrieve_from_cache!(identifier)
134
+ CarrierWave::Storage::Fog::File.new(uploader, self, uploader.cache_path(identifier))
135
+ end
136
+
137
+ ##
138
+ # Deletes a cache dir
139
+ #
140
+ def delete_dir!(path)
141
+ # do nothing, because there's no such things as 'empty directory'
142
+ end
143
+
144
+ def clean_cache!(seconds)
145
+ connection.directories.new(
146
+ :key => uploader.fog_directory,
147
+ :public => uploader.fog_public
148
+ ).files.all(:prefix => uploader.cache_dir).each do |file|
149
+ # generate_cache_id returns key formatted TIMEINT-PID(-COUNTER)-RND
150
+ matched = file.key.match(/(\d+)-\d+-\d+(?:-\d+)?/)
151
+ next unless matched
152
+ time = Time.at(matched[1].to_i)
153
+ file.destroy if time < (Time.now.utc - seconds)
154
+ end
155
+ end
156
+
97
157
  def connection
98
158
  @connection ||= begin
99
159
  options = credentials = uploader.fog_credentials
@@ -102,7 +162,10 @@ module CarrierWave
102
162
  end
103
163
 
104
164
  class File
165
+ DEFAULT_S3_REGION = 'us-east-1'.freeze
166
+
105
167
  include CarrierWave::Utilities::Uri
168
+ include CarrierWave::Utilities::FileName
106
169
 
107
170
  ##
108
171
  # Current local path to file
@@ -126,7 +189,7 @@ module CarrierWave
126
189
 
127
190
  ##
128
191
  # Return a temporary authenticated url to a private file, if available
129
- # Only supported for AWS, Rackspace and Google providers
192
+ # Only supported for AWS, Rackspace, Google, AzureRM and Aliyun providers
130
193
  #
131
194
  # === Returns
132
195
  #
@@ -135,19 +198,28 @@ module CarrierWave
135
198
  # [NilClass] no authenticated url available
136
199
  #
137
200
  def authenticated_url(options = {})
138
- if ['AWS', 'Google', 'Rackspace', 'OpenStack'].include?(@uploader.fog_credentials[:provider])
201
+ if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM', 'Aliyun', 'backblaze'].include?(fog_provider)
139
202
  # avoid a get by using local references
140
203
  local_directory = connection.directories.new(:key => @uploader.fog_directory)
141
204
  local_file = local_directory.files.new(:key => path)
142
- if @uploader.fog_credentials[:provider] == "AWS"
143
- local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration, options)
144
- elsif ['Rackspace', 'OpenStack'].include?(@uploader.fog_credentials[:provider])
145
- connection.get_object_https_url(@uploader.fog_directory, path, ::Fog::Time.now + @uploader.fog_authenticated_url_expiration)
205
+ expire_at = options[:expire_at] || ::Fog::Time.now.since(@uploader.fog_authenticated_url_expiration.to_i)
206
+ case fog_provider
207
+ when 'AWS', 'Google'
208
+ # Older versions of fog-google do not support options as a parameter
209
+ if url_options_supported?(local_file)
210
+ local_file.url(expire_at, options)
211
+ else
212
+ warn "Options hash not supported in #{local_file.class}. You may need to upgrade your Fog provider."
213
+ local_file.url(expire_at)
214
+ end
215
+ when 'Rackspace', 'OpenStack'
216
+ connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options)
217
+ when 'Aliyun'
218
+ expire_at -= Time.now
219
+ local_file.url(expire_at)
146
220
  else
147
- local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration)
221
+ local_file.url(expire_at)
148
222
  end
149
- else
150
- nil
151
223
  end
152
224
  end
153
225
 
@@ -159,7 +231,7 @@ module CarrierWave
159
231
  # [String] value of content-type
160
232
  #
161
233
  def content_type
162
- @content_type || file.content_type
234
+ @content_type || file.try(:content_type)
163
235
  end
164
236
 
165
237
  ##
@@ -182,19 +254,9 @@ module CarrierWave
182
254
  #
183
255
  def delete
184
256
  # avoid a get by just using local reference
185
- directory.files.new(:key => path).destroy
186
- end
187
-
188
- ##
189
- # Return extension of file
190
- #
191
- # === Returns
192
- #
193
- # [String] extension of file or nil if the file has no extension
194
- #
195
- def extension
196
- path_elements = path.split('.')
197
- path_elements.last if path_elements.size > 1
257
+ directory.files.new(:key => path).destroy.tap do |result|
258
+ @file = nil if result
259
+ end
198
260
  end
199
261
 
200
262
  ##
@@ -213,7 +275,7 @@ module CarrierWave
213
275
  end
214
276
 
215
277
  def initialize(uploader, base, path)
216
- @uploader, @base, @path = uploader, base, path
278
+ @uploader, @base, @path, @content_type = uploader, base, path, nil
217
279
  end
218
280
 
219
281
  ##
@@ -223,6 +285,16 @@ module CarrierWave
223
285
  #
224
286
  # [String] contents of file
225
287
  def read
288
+ file_body = file&.body
289
+
290
+ return if file_body.nil?
291
+ return file_body unless file_body.is_a?(::File)
292
+
293
+ # Fog::Storage::XXX::File#body could return the source file which was uploaded to the remote server.
294
+ return read_source_file if ::File.exist?(file_body.path)
295
+
296
+ # If the source file doesn't exist, the remote content is read
297
+ @file = nil
226
298
  file.body
227
299
  end
228
300
 
@@ -234,7 +306,7 @@ module CarrierWave
234
306
  # [Integer] size of file body
235
307
  #
236
308
  def size
237
- file.content_length
309
+ file.nil? ? 0 : file.content_length
238
310
  end
239
311
 
240
312
  ##
@@ -244,7 +316,7 @@ module CarrierWave
244
316
  #
245
317
  # [Boolean] true if file exists or false
246
318
  def exists?
247
- !!directory.files.head(path)
319
+ !!file
248
320
  end
249
321
 
250
322
  ##
@@ -254,15 +326,19 @@ module CarrierWave
254
326
  #
255
327
  # [Boolean] true on success or raises error
256
328
  def store(new_file)
257
- fog_file = new_file.to_file
258
- @content_type ||= new_file.content_type
259
- @file = directory.files.create({
260
- :body => fog_file ? fog_file : new_file.read,
261
- :content_type => @content_type,
262
- :key => path,
263
- :public => @uploader.fog_public
264
- }.merge(@uploader.fog_attributes))
265
- fog_file.close if fog_file && !fog_file.closed?
329
+ if new_file.is_a?(self.class)
330
+ new_file.copy_to(path)
331
+ else
332
+ fog_file = new_file.to_file
333
+ @content_type ||= new_file.content_type
334
+ @file = directory.files.create({
335
+ :body => fog_file || new_file.read,
336
+ :content_type => @content_type,
337
+ :key => path,
338
+ :public => @uploader.fog_public
339
+ }.merge(@uploader.fog_attributes))
340
+ fog_file.close if fog_file && !fog_file.closed?
341
+ end
266
342
  true
267
343
  end
268
344
 
@@ -277,7 +353,7 @@ module CarrierWave
277
353
  #
278
354
  def public_url
279
355
  encoded_path = encode_path(path)
280
- if host = @uploader.asset_host
356
+ if (host = @uploader.asset_host)
281
357
  if host.respond_to? :call
282
358
  "#{host.call(self)}/#{encoded_path}"
283
359
  else
@@ -285,23 +361,36 @@ module CarrierWave
285
361
  end
286
362
  else
287
363
  # AWS/Google optimized for speed over correctness
288
- case @uploader.fog_credentials[:provider]
364
+ case fog_provider
289
365
  when 'AWS'
290
366
  # check if some endpoint is set in fog_credentials
291
367
  if @uploader.fog_credentials.has_key?(:endpoint)
292
368
  "#{@uploader.fog_credentials[:endpoint]}/#{@uploader.fog_directory}/#{encoded_path}"
293
369
  else
294
370
  protocol = @uploader.fog_use_ssl_for_aws ? "https" : "http"
295
- # if directory is a valid subdomain, use that style for access
296
- if @uploader.fog_directory.to_s =~ /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
297
- "#{protocol}://#{@uploader.fog_directory}.s3.amazonaws.com/#{encoded_path}"
298
- else
299
- # directory is not a valid subdomain, so use path style for access
300
- "#{protocol}://s3.amazonaws.com/#{@uploader.fog_directory}/#{encoded_path}"
371
+
372
+ subdomain_regex = /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
373
+ # To use the virtual-hosted style, the bucket name needs to be representable as a subdomain
374
+ use_virtual_hosted_style = @uploader.fog_directory.to_s =~ subdomain_regex && !(protocol == 'https' && @uploader.fog_directory =~ /\./)
375
+
376
+ region = @uploader.fog_credentials[:region].to_s
377
+ regional_host = case region
378
+ when DEFAULT_S3_REGION, ''
379
+ 's3.amazonaws.com'
380
+ else
381
+ "s3.#{region}.amazonaws.com"
382
+ end
383
+
384
+ if use_virtual_hosted_style
385
+ regional_host = 's3-accelerate.amazonaws.com' if @uploader.fog_aws_accelerate
386
+ "#{protocol}://#{@uploader.fog_directory}.#{regional_host}/#{encoded_path}"
387
+ else # directory is not a valid subdomain, so use path style for access
388
+ "#{protocol}://#{regional_host}/#{@uploader.fog_directory}/#{encoded_path}"
301
389
  end
302
390
  end
303
391
  when 'Google'
304
- "https://commondatastorage.googleapis.com/#{@uploader.fog_directory}/#{encoded_path}"
392
+ # https://cloud.google.com/storage/docs/access-public-data
393
+ "https://storage.googleapis.com/#{@uploader.fog_directory}/#{encoded_path}"
305
394
  else
306
395
  # avoid a get by just using local reference
307
396
  directory.files.new(:key => path).public_url
@@ -310,7 +399,7 @@ module CarrierWave
310
399
  end
311
400
 
312
401
  ##
313
- # Return url to file, if avaliable
402
+ # Return url to file, if available
314
403
  #
315
404
  # === Returns
316
405
  #
@@ -336,8 +425,42 @@ module CarrierWave
336
425
  # [NilClass] no file name available
337
426
  #
338
427
  def filename(options = {})
339
- if file_url = url(options)
340
- URI.decode(file_url).gsub(/.*\/(.*?$)/, '\1').split('?').first
428
+ return unless (file_url = url(options))
429
+ CGI.unescape(file_url.split('?').first).gsub(/.*\/(.*?$)/, '\1')
430
+ end
431
+
432
+ ##
433
+ # Creates a copy of this file and returns it.
434
+ #
435
+ # === Parameters
436
+ #
437
+ # [new_path (String)] The path where the file should be copied to.
438
+ #
439
+ # === Returns
440
+ #
441
+ # @return [CarrierWave::Storage::Fog::File] the location where the file will be stored.
442
+ #
443
+ def copy_to(new_path)
444
+ file.copy(@uploader.fog_directory, new_path, copy_options)
445
+ CarrierWave::Storage::Fog::File.new(@uploader, @base, new_path)
446
+ end
447
+
448
+ ##
449
+ # Return the local file
450
+ #
451
+ # === Returns
452
+ #
453
+ # [File] The local file as Ruby's File class
454
+ # or
455
+ # [NilClass] When there's no file, or the file is remotely stored
456
+ #
457
+ def to_file
458
+ return nil unless file.body.is_a? ::File
459
+
460
+ if file.body.closed?
461
+ ::File.open(file.body.path) # Reopen if it's already closed
462
+ else
463
+ file.body
341
464
  end
342
465
  end
343
466
 
@@ -362,12 +485,10 @@ module CarrierWave
362
485
  # [Fog::#{provider}::Directory] containing directory
363
486
  #
364
487
  def directory
365
- @directory ||= begin
366
- connection.directories.new(
367
- :key => @uploader.fog_directory,
368
- :public => @uploader.fog_public
369
- )
370
- end
488
+ @directory ||= connection.directories.new(
489
+ :key => @uploader.fog_directory,
490
+ :public => @uploader.fog_public
491
+ )
371
492
  end
372
493
 
373
494
  ##
@@ -381,6 +502,43 @@ module CarrierWave
381
502
  @file ||= directory.files.head(path)
382
503
  end
383
504
 
505
+ def copy_options
506
+ options = {}
507
+ options.merge!(acl_header) if acl_header.present?
508
+ options[fog_provider == "Google" ? :content_type : 'Content-Type'] ||= content_type if content_type
509
+ options.merge(@uploader.fog_attributes)
510
+ end
511
+
512
+ def acl_header
513
+ case fog_provider
514
+ when 'AWS'
515
+ { 'x-amz-acl' => @uploader.fog_public ? 'public-read' : 'private' }
516
+ when "Google"
517
+ @uploader.fog_public ? { destination_predefined_acl: "publicRead" } : {}
518
+ else
519
+ {}
520
+ end
521
+ end
522
+
523
+ def fog_provider
524
+ @uploader.fog_credentials[:provider].to_s
525
+ end
526
+
527
+ def read_source_file
528
+ source_file = to_file
529
+ return unless source_file
530
+
531
+ begin
532
+ source_file.read
533
+ ensure
534
+ source_file.close
535
+ end
536
+ end
537
+
538
+ def url_options_supported?(local_file)
539
+ parameters = local_file.method(:url).parameters
540
+ parameters.count == 2 && parameters[1].include?(:options)
541
+ end
384
542
  end
385
543
 
386
544
  end # Fog
@@ -1,11 +1,3 @@
1
1
  require "carrierwave/storage/abstract"
2
2
  require "carrierwave/storage/file"
3
-
4
- %w(aws google openstack rackspace).each do |fog_dependency|
5
- begin
6
- require "fog/#{fog_dependency}"
7
- rescue LoadError
8
- end
9
- end
10
-
11
- require "carrierwave/storage/fog" if defined?(Fog)
3
+ require "carrierwave/storage/fog"