carrierwave 0.11.2 → 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
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 +217 -182
  9. data/lib/carrierwave/mounter.rb +255 -0
  10. data/lib/carrierwave/orm/activerecord.rb +29 -35
  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 +171 -123
  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} +6 -10
  48. data/lib/generators/uploader_generator.rb +3 -3
  49. metadata +135 -83
  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"