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.
- checksums.yaml +7 -0
- data/README.md +508 -158
- data/lib/carrierwave/compatibility/paperclip.rb +31 -21
- data/lib/carrierwave/downloader/base.rb +101 -0
- data/lib/carrierwave/downloader/remote_file.rb +68 -0
- data/lib/carrierwave/error.rb +1 -0
- data/lib/carrierwave/locale/en.yml +11 -5
- data/lib/carrierwave/mount.rb +220 -187
- data/lib/carrierwave/mounter.rb +255 -0
- data/lib/carrierwave/orm/activerecord.rb +24 -34
- data/lib/carrierwave/processing/mini_magick.rb +142 -79
- data/lib/carrierwave/processing/rmagick.rb +76 -35
- data/lib/carrierwave/processing/vips.rb +284 -0
- data/lib/carrierwave/processing.rb +1 -1
- data/lib/carrierwave/sanitized_file.rb +89 -70
- data/lib/carrierwave/storage/abstract.rb +16 -3
- data/lib/carrierwave/storage/file.rb +71 -3
- data/lib/carrierwave/storage/fog.rb +215 -58
- data/lib/carrierwave/storage.rb +1 -7
- data/lib/carrierwave/test/matchers.rb +88 -19
- data/lib/carrierwave/uploader/cache.rb +88 -44
- data/lib/carrierwave/uploader/callbacks.rb +1 -3
- data/lib/carrierwave/uploader/configuration.rb +81 -9
- data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
- data/lib/carrierwave/uploader/content_type_denylist.rb +62 -0
- data/lib/carrierwave/uploader/default_url.rb +3 -5
- data/lib/carrierwave/uploader/dimension.rb +66 -0
- data/lib/carrierwave/uploader/download.rb +5 -69
- data/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
- data/lib/carrierwave/uploader/extension_denylist.rb +64 -0
- data/lib/carrierwave/uploader/file_size.rb +43 -0
- data/lib/carrierwave/uploader/mountable.rb +13 -8
- data/lib/carrierwave/uploader/processing.rb +54 -21
- data/lib/carrierwave/uploader/proxy.rb +30 -8
- data/lib/carrierwave/uploader/remove.rb +0 -2
- data/lib/carrierwave/uploader/serialization.rb +3 -5
- data/lib/carrierwave/uploader/store.rb +59 -28
- data/lib/carrierwave/uploader/url.rb +8 -7
- data/lib/carrierwave/uploader/versions.rb +173 -124
- data/lib/carrierwave/uploader.rb +12 -6
- data/lib/carrierwave/utilities/file_name.rb +47 -0
- data/lib/carrierwave/utilities/uri.rb +14 -12
- data/lib/carrierwave/utilities.rb +2 -3
- data/lib/carrierwave/validations/active_model.rb +7 -13
- data/lib/carrierwave/version.rb +1 -1
- data/lib/carrierwave.rb +41 -16
- data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -9
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +224 -100
- data/lib/carrierwave/locale/cs.yml +0 -11
- data/lib/carrierwave/locale/de.yml +0 -11
- data/lib/carrierwave/locale/nl.yml +0 -11
- data/lib/carrierwave/locale/sk.yml +0 -11
- data/lib/carrierwave/processing/mime_types.rb +0 -73
- data/lib/carrierwave/uploader/extension_blacklist.rb +0 -47
- 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
|
-
# [:
|
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
|
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?(
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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(
|
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
|
-
|
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
|
-
!!
|
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
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
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
|
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
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
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
|
-
|
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
|
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
|
-
|
341
|
-
|
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 ||=
|
367
|
-
|
368
|
-
|
369
|
-
|
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
|