carrierwave 0.9.0 → 3.0.2

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 (56) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +508 -158
  3. data/lib/carrierwave/compatibility/paperclip.rb +31 -21
  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 +220 -187
  9. data/lib/carrierwave/mounter.rb +255 -0
  10. data/lib/carrierwave/orm/activerecord.rb +24 -34
  11. data/lib/carrierwave/processing/mini_magick.rb +142 -79
  12. data/lib/carrierwave/processing/rmagick.rb +76 -35
  13. data/lib/carrierwave/processing/vips.rb +284 -0
  14. data/lib/carrierwave/processing.rb +1 -1
  15. data/lib/carrierwave/sanitized_file.rb +89 -70
  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 -58
  19. data/lib/carrierwave/storage.rb +1 -7
  20. data/lib/carrierwave/test/matchers.rb +88 -19
  21. data/lib/carrierwave/uploader/cache.rb +88 -44
  22. data/lib/carrierwave/uploader/callbacks.rb +1 -3
  23. data/lib/carrierwave/uploader/configuration.rb +81 -9
  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 +5 -69
  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 +54 -21
  34. data/lib/carrierwave/uploader/proxy.rb +30 -8
  35. data/lib/carrierwave/uploader/remove.rb +0 -2
  36. data/lib/carrierwave/uploader/serialization.rb +3 -5
  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 +173 -124
  40. data/lib/carrierwave/uploader.rb +12 -6
  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 +2 -3
  44. data/lib/carrierwave/validations/active_model.rb +7 -13
  45. data/lib/carrierwave/version.rb +1 -1
  46. data/lib/carrierwave.rb +41 -16
  47. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -9
  48. data/lib/generators/uploader_generator.rb +3 -3
  49. metadata +224 -100
  50. data/lib/carrierwave/locale/cs.yml +0 -11
  51. data/lib/carrierwave/locale/de.yml +0 -11
  52. data/lib/carrierwave/locale/nl.yml +0 -11
  53. data/lib/carrierwave/locale/sk.yml +0 -11
  54. data/lib/carrierwave/processing/mime_types.rb +0 -73
  55. data/lib/carrierwave/uploader/extension_blacklist.rb +0 -47
  56. data/lib/carrierwave/uploader/extension_whitelist.rb +0 -49
@@ -1,7 +1,3 @@
1
- # encoding: utf-8
2
-
3
- require "fog"
4
-
5
1
  module CarrierWave
6
2
  module Storage
7
3
 
@@ -20,6 +16,8 @@ module CarrierWave
20
16
  # [:fog_authenticated_url_expiration] (optional) time (in seconds) that authenticated urls
21
17
  # will be valid, when fog_public is false and provider is AWS or Google, defaults to 600
22
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
23
21
  #
24
22
  #
25
23
  # AWS credentials contain the following keys:
@@ -27,12 +25,12 @@ module CarrierWave
27
25
  # [:aws_access_key_id]
28
26
  # [:aws_secret_access_key]
29
27
  # [:region] (optional) defaults to 'us-east-1'
30
- # :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']
31
29
  #
32
30
  #
33
31
  # Google credentials contain the following keys:
34
32
  # [:google_storage_access_key_id]
35
- # [:google_storage_secrete_access_key]
33
+ # [:google_storage_secret_access_key]
36
34
  #
37
35
  #
38
36
  # Local credentials contain the following keys:
@@ -62,6 +60,14 @@ module CarrierWave
62
60
  def connection_cache
63
61
  @connection_cache ||= {}
64
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
65
71
  end
66
72
 
67
73
  ##
@@ -96,6 +102,58 @@ module CarrierWave
96
102
  CarrierWave::Storage::Fog::File.new(uploader, self, uploader.store_path(identifier))
97
103
  end
98
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
+
99
157
  def connection
100
158
  @connection ||= begin
101
159
  options = credentials = uploader.fog_credentials
@@ -104,7 +162,10 @@ module CarrierWave
104
162
  end
105
163
 
106
164
  class File
165
+ DEFAULT_S3_REGION = 'us-east-1'.freeze
166
+
107
167
  include CarrierWave::Utilities::Uri
168
+ include CarrierWave::Utilities::FileName
108
169
 
109
170
  ##
110
171
  # Current local path to file
@@ -128,7 +189,7 @@ module CarrierWave
128
189
 
129
190
  ##
130
191
  # Return a temporary authenticated url to a private file, if available
131
- # Only supported for AWS, Rackspace and Google providers
192
+ # Only supported for AWS, Rackspace, Google, AzureRM and Aliyun providers
132
193
  #
133
194
  # === Returns
134
195
  #
@@ -137,19 +198,28 @@ module CarrierWave
137
198
  # [NilClass] no authenticated url available
138
199
  #
139
200
  def authenticated_url(options = {})
140
- if ['AWS', 'Google', 'Rackspace'].include?(@uploader.fog_credentials[:provider])
201
+ if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM', 'Aliyun', 'backblaze'].include?(fog_provider)
141
202
  # avoid a get by using local references
142
203
  local_directory = connection.directories.new(:key => @uploader.fog_directory)
143
204
  local_file = local_directory.files.new(:key => path)
144
- if @uploader.fog_credentials[:provider] == "AWS"
145
- local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration, options)
146
- elsif @uploader.fog_credentials[:provider] == "Rackspace"
147
- 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)
148
220
  else
149
- local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration)
221
+ local_file.url(expire_at)
150
222
  end
151
- else
152
- nil
153
223
  end
154
224
  end
155
225
 
@@ -161,7 +231,7 @@ module CarrierWave
161
231
  # [String] value of content-type
162
232
  #
163
233
  def content_type
164
- @content_type || file.content_type
234
+ @content_type || file.try(:content_type)
165
235
  end
166
236
 
167
237
  ##
@@ -184,18 +254,9 @@ module CarrierWave
184
254
  #
185
255
  def delete
186
256
  # avoid a get by just using local reference
187
- directory.files.new(:key => path).destroy
188
- end
189
-
190
- ##
191
- # Return extension of file
192
- #
193
- # === Returns
194
- #
195
- # [String] extension of file
196
- #
197
- def extension
198
- path.split('.').last
257
+ directory.files.new(:key => path).destroy.tap do |result|
258
+ @file = nil if result
259
+ end
199
260
  end
200
261
 
201
262
  ##
@@ -214,7 +275,7 @@ module CarrierWave
214
275
  end
215
276
 
216
277
  def initialize(uploader, base, path)
217
- @uploader, @base, @path = uploader, base, path
278
+ @uploader, @base, @path, @content_type = uploader, base, path, nil
218
279
  end
219
280
 
220
281
  ##
@@ -224,6 +285,16 @@ module CarrierWave
224
285
  #
225
286
  # [String] contents of file
226
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
227
298
  file.body
228
299
  end
229
300
 
@@ -235,7 +306,7 @@ module CarrierWave
235
306
  # [Integer] size of file body
236
307
  #
237
308
  def size
238
- file.content_length
309
+ file.nil? ? 0 : file.content_length
239
310
  end
240
311
 
241
312
  ##
@@ -245,7 +316,7 @@ module CarrierWave
245
316
  #
246
317
  # [Boolean] true if file exists or false
247
318
  def exists?
248
- !!directory.files.head(path)
319
+ !!file
249
320
  end
250
321
 
251
322
  ##
@@ -255,15 +326,19 @@ module CarrierWave
255
326
  #
256
327
  # [Boolean] true on success or raises error
257
328
  def store(new_file)
258
- fog_file = new_file.to_file
259
- @content_type ||= new_file.content_type
260
- @file = directory.files.create({
261
- :body => fog_file ? fog_file : new_file.read,
262
- :content_type => @content_type,
263
- :key => path,
264
- :public => @uploader.fog_public
265
- }.merge(@uploader.fog_attributes))
266
- 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
267
342
  true
268
343
  end
269
344
 
@@ -278,7 +353,7 @@ module CarrierWave
278
353
  #
279
354
  def public_url
280
355
  encoded_path = encode_path(path)
281
- if host = @uploader.asset_host
356
+ if (host = @uploader.asset_host)
282
357
  if host.respond_to? :call
283
358
  "#{host.call(self)}/#{encoded_path}"
284
359
  else
@@ -286,23 +361,36 @@ module CarrierWave
286
361
  end
287
362
  else
288
363
  # AWS/Google optimized for speed over correctness
289
- case @uploader.fog_credentials[:provider]
364
+ case fog_provider
290
365
  when 'AWS'
291
366
  # check if some endpoint is set in fog_credentials
292
367
  if @uploader.fog_credentials.has_key?(:endpoint)
293
368
  "#{@uploader.fog_credentials[:endpoint]}/#{@uploader.fog_directory}/#{encoded_path}"
294
369
  else
295
370
  protocol = @uploader.fog_use_ssl_for_aws ? "https" : "http"
296
- # if directory is a valid subdomain, use that style for access
297
- if @uploader.fog_directory.to_s =~ /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
298
- "#{protocol}://#{@uploader.fog_directory}.s3.amazonaws.com/#{encoded_path}"
299
- else
300
- # directory is not a valid subdomain, so use path style for access
301
- "#{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}"
302
389
  end
303
390
  end
304
391
  when 'Google'
305
- "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}"
306
394
  else
307
395
  # avoid a get by just using local reference
308
396
  directory.files.new(:key => path).public_url
@@ -311,7 +399,7 @@ module CarrierWave
311
399
  end
312
400
 
313
401
  ##
314
- # Return url to file, if avaliable
402
+ # Return url to file, if available
315
403
  #
316
404
  # === Returns
317
405
  #
@@ -337,8 +425,42 @@ module CarrierWave
337
425
  # [NilClass] no file name available
338
426
  #
339
427
  def filename(options = {})
340
- if file_url = url(options)
341
- URI.decode(file_url).gsub(/.*\/(.*?$)/, '\1')
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
342
464
  end
343
465
  end
344
466
 
@@ -363,12 +485,10 @@ module CarrierWave
363
485
  # [Fog::#{provider}::Directory] containing directory
364
486
  #
365
487
  def directory
366
- @directory ||= begin
367
- connection.directories.new(
368
- :key => @uploader.fog_directory,
369
- :public => @uploader.fog_public
370
- )
371
- end
488
+ @directory ||= connection.directories.new(
489
+ :key => @uploader.fog_directory,
490
+ :public => @uploader.fog_public
491
+ )
372
492
  end
373
493
 
374
494
  ##
@@ -382,6 +502,43 @@ module CarrierWave
382
502
  @file ||= directory.files.head(path)
383
503
  end
384
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
385
542
  end
386
543
 
387
544
  end # Fog
@@ -1,9 +1,3 @@
1
1
  require "carrierwave/storage/abstract"
2
2
  require "carrierwave/storage/file"
3
-
4
- begin
5
- require "fog"
6
- rescue LoadError
7
- end
8
-
9
- require "carrierwave/storage/fog" if defined?(Fog)
3
+ require "carrierwave/storage/fog"