carrierwave 1.3.2 → 3.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +235 -91
- data/lib/carrierwave/compatibility/paperclip.rb +4 -2
- data/lib/carrierwave/downloader/base.rb +101 -0
- data/lib/carrierwave/downloader/remote_file.rb +68 -0
- data/lib/carrierwave/locale/en.yml +9 -6
- data/lib/carrierwave/mount.rb +53 -61
- data/lib/carrierwave/mounter.rb +167 -77
- data/lib/carrierwave/orm/activerecord.rb +23 -58
- data/lib/carrierwave/processing/mini_magick.rb +108 -123
- data/lib/carrierwave/processing/rmagick.rb +11 -15
- data/lib/carrierwave/processing/vips.rb +284 -0
- data/lib/carrierwave/processing.rb +1 -0
- data/lib/carrierwave/sanitized_file.rb +60 -66
- data/lib/carrierwave/storage/abstract.rb +5 -5
- data/lib/carrierwave/storage/file.rb +6 -5
- data/lib/carrierwave/storage/fog.rb +101 -62
- data/lib/carrierwave/storage.rb +1 -0
- data/lib/carrierwave/test/matchers.rb +11 -7
- data/lib/carrierwave/uploader/cache.rb +40 -24
- data/lib/carrierwave/uploader/callbacks.rb +1 -1
- data/lib/carrierwave/uploader/configuration.rb +38 -19
- data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
- data/lib/carrierwave/uploader/content_type_denylist.rb +62 -0
- data/lib/carrierwave/uploader/dimension.rb +66 -0
- data/lib/carrierwave/uploader/download.rb +2 -123
- 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 +2 -2
- data/lib/carrierwave/uploader/mountable.rb +6 -0
- data/lib/carrierwave/uploader/processing.rb +42 -7
- data/lib/carrierwave/uploader/proxy.rb +17 -4
- data/lib/carrierwave/uploader/serialization.rb +1 -1
- data/lib/carrierwave/uploader/store.rb +47 -7
- data/lib/carrierwave/uploader/url.rb +7 -4
- data/lib/carrierwave/uploader/versions.rb +157 -109
- data/lib/carrierwave/uploader.rb +10 -17
- data/lib/carrierwave/utilities/file_name.rb +47 -0
- data/lib/carrierwave/utilities/uri.rb +14 -11
- data/lib/carrierwave/utilities.rb +1 -0
- data/lib/carrierwave/validations/active_model.rb +7 -9
- data/lib/carrierwave/version.rb +1 -1
- data/lib/carrierwave.rb +13 -17
- data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +3 -3
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +103 -36
- data/lib/carrierwave/uploader/content_type_blacklist.rb +0 -48
- data/lib/carrierwave/uploader/content_type_whitelist.rb +0 -48
- data/lib/carrierwave/uploader/extension_blacklist.rb +0 -51
- data/lib/carrierwave/uploader/extension_whitelist.rb +0 -52
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'ssrf_filter'
|
3
|
+
require 'addressable'
|
4
|
+
require 'carrierwave/downloader/remote_file'
|
5
|
+
|
6
|
+
module CarrierWave
|
7
|
+
module Downloader
|
8
|
+
class Base
|
9
|
+
include CarrierWave::Utilities::Uri
|
10
|
+
|
11
|
+
attr_reader :uploader
|
12
|
+
|
13
|
+
def initialize(uploader)
|
14
|
+
@uploader = uploader
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Downloads a file from given URL and returns a RemoteFile.
|
19
|
+
#
|
20
|
+
# === Parameters
|
21
|
+
#
|
22
|
+
# [url (String)] The URL where the remote file is stored
|
23
|
+
# [remote_headers (Hash)] Request headers
|
24
|
+
#
|
25
|
+
def download(url, remote_headers = {})
|
26
|
+
@current_download_retry_count = 0
|
27
|
+
headers = remote_headers.
|
28
|
+
reverse_merge('User-Agent' => "CarrierWave/#{CarrierWave::VERSION}")
|
29
|
+
uri = process_uri(url.to_s)
|
30
|
+
begin
|
31
|
+
if skip_ssrf_protection?(uri)
|
32
|
+
response = OpenURI.open_uri(process_uri(url.to_s), headers)
|
33
|
+
else
|
34
|
+
request = nil
|
35
|
+
if ::SsrfFilter::VERSION.to_f < 1.1
|
36
|
+
response = SsrfFilter.get(uri, headers: headers) do |req|
|
37
|
+
request = req
|
38
|
+
end
|
39
|
+
else
|
40
|
+
response = SsrfFilter.get(uri, headers: headers, request_proc: ->(req) { request = req }) do |res|
|
41
|
+
res.body # ensure to read body
|
42
|
+
end
|
43
|
+
end
|
44
|
+
response.uri = request.uri
|
45
|
+
response.value
|
46
|
+
end
|
47
|
+
rescue StandardError => e
|
48
|
+
if @current_download_retry_count < @uploader.download_retry_count
|
49
|
+
@current_download_retry_count += 1
|
50
|
+
sleep @uploader.download_retry_wait_time
|
51
|
+
retry
|
52
|
+
else
|
53
|
+
raise CarrierWave::DownloadError, "could not download file: #{e.message}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
CarrierWave::Downloader::RemoteFile.new(response)
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Processes the given URL by parsing it, and escaping if necessary. Public to allow overriding.
|
61
|
+
#
|
62
|
+
# === Parameters
|
63
|
+
#
|
64
|
+
# [url (String)] The URL where the remote file is stored
|
65
|
+
#
|
66
|
+
def process_uri(source)
|
67
|
+
uri = Addressable::URI.parse(source)
|
68
|
+
uri.host = uri.normalized_host
|
69
|
+
# Perform decode first, as the path is likely to be already encoded
|
70
|
+
uri.path = encode_path(decode_uri(uri.path)) if uri.path =~ CarrierWave::Utilities::Uri::PATH_UNSAFE
|
71
|
+
uri.query = encode_non_ascii(uri.query) if uri.query
|
72
|
+
uri.fragment = encode_non_ascii(uri.fragment) if uri.fragment
|
73
|
+
URI.parse(uri.to_s)
|
74
|
+
rescue URI::InvalidURIError, Addressable::URI::InvalidURIError
|
75
|
+
raise CarrierWave::DownloadError, "couldn't parse URL: #{source}"
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# If this returns true, SSRF protection will be bypassed.
|
80
|
+
# You can override this if you want to allow accessing specific local URIs that are not SSRF exploitable.
|
81
|
+
#
|
82
|
+
# === Parameters
|
83
|
+
#
|
84
|
+
# [uri (URI)] The URI where the remote file is stored
|
85
|
+
#
|
86
|
+
# === Examples
|
87
|
+
#
|
88
|
+
# class CarrierWave::Downloader::CustomDownloader < CarrierWave::Downloader::Base
|
89
|
+
# def skip_ssrf_protection?(uri)
|
90
|
+
# uri.hostname == 'localhost' && uri.port == 80
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# my_uploader.downloader = CarrierWave::Downloader::CustomDownloader
|
95
|
+
#
|
96
|
+
def skip_ssrf_protection?(uri)
|
97
|
+
false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module CarrierWave
|
2
|
+
module Downloader
|
3
|
+
class RemoteFile
|
4
|
+
attr_reader :file, :uri
|
5
|
+
|
6
|
+
def initialize(file)
|
7
|
+
case file
|
8
|
+
when String
|
9
|
+
@file = StringIO.new(file)
|
10
|
+
when Net::HTTPResponse
|
11
|
+
body = file.body
|
12
|
+
raise CarrierWave::DownloadError, 'could not download file: No Content' if body.nil?
|
13
|
+
|
14
|
+
@file = StringIO.new(body)
|
15
|
+
@content_type = file.content_type
|
16
|
+
@headers = file
|
17
|
+
@uri = file.uri
|
18
|
+
else
|
19
|
+
@file = file
|
20
|
+
@content_type = file.content_type
|
21
|
+
@headers = file.meta
|
22
|
+
@uri = file.base_uri
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def content_type
|
27
|
+
@content_type || 'application/octet-stream'
|
28
|
+
end
|
29
|
+
|
30
|
+
def headers
|
31
|
+
@headers || {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def original_filename
|
35
|
+
filename = filename_from_header || filename_from_uri
|
36
|
+
extensions = Marcel::Magic.new(content_type).extensions
|
37
|
+
unless File.extname(filename).present? || extensions.blank?
|
38
|
+
extension = extensions.first
|
39
|
+
filename = "#{filename}.#{extension}"
|
40
|
+
end
|
41
|
+
filename
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def filename_from_header
|
47
|
+
return nil unless headers['content-disposition']
|
48
|
+
|
49
|
+
match = headers['content-disposition'].match(/filename=(?:"([^"]+)"|([^";]+))/)
|
50
|
+
return nil unless match
|
51
|
+
|
52
|
+
match[1].presence || match[2].presence
|
53
|
+
end
|
54
|
+
|
55
|
+
def filename_from_uri
|
56
|
+
CGI.unescape(File.basename(uri.path))
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing(*args, &block)
|
60
|
+
file.send(*args, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def respond_to_missing?(*args)
|
64
|
+
super || file.respond_to?(*args)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -4,11 +4,14 @@ en:
|
|
4
4
|
carrierwave_processing_error: failed to be processed
|
5
5
|
carrierwave_integrity_error: is not of an allowed file type
|
6
6
|
carrierwave_download_error: could not be downloaded
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}"
|
7
|
+
extension_allowlist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
|
8
|
+
extension_denylist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
|
9
|
+
content_type_allowlist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
|
10
|
+
content_type_denylist_error: "You are not allowed to upload %{content_type} files"
|
11
|
+
processing_error: "Failed to manipulate, maybe it is not an image?"
|
13
12
|
min_size_error: "File size should be greater than %{min_size}"
|
14
13
|
max_size_error: "File size should be less than %{max_size}"
|
14
|
+
min_width_error: "Image width should be greater than %{min_width}px"
|
15
|
+
max_width_error: "Image width should be less than %{max_width}px"
|
16
|
+
min_height_error: "Image height should be greater than %{min_height}px"
|
17
|
+
max_height_error: "Image height should be less than %{max_height}px"
|
data/lib/carrierwave/mount.rb
CHANGED
@@ -132,7 +132,7 @@ module CarrierWave
|
|
132
132
|
# end
|
133
133
|
#
|
134
134
|
def mount_uploader(column, uploader=nil, options={}, &block)
|
135
|
-
mount_base(column, uploader, options, &block)
|
135
|
+
mount_base(column, uploader, options.merge(multiple: false), &block)
|
136
136
|
|
137
137
|
mod = Module.new
|
138
138
|
include mod
|
@@ -163,36 +163,27 @@ module CarrierWave
|
|
163
163
|
end
|
164
164
|
|
165
165
|
def remote_#{column}_url=(url)
|
166
|
-
_mounter(:#{column}).remote_urls =
|
166
|
+
_mounter(:#{column}).remote_urls = url
|
167
167
|
end
|
168
168
|
|
169
169
|
def remote_#{column}_request_header=(header)
|
170
170
|
_mounter(:#{column}).remote_request_headers = [header]
|
171
171
|
end
|
172
172
|
|
173
|
-
def write_#{column}_identifier
|
174
|
-
return if frozen?
|
175
|
-
mounter = _mounter(:#{column})
|
176
|
-
|
177
|
-
if mounter.remove?
|
178
|
-
write_uploader(mounter.serialization_column, nil)
|
179
|
-
elsif mounter.identifiers.first
|
180
|
-
write_uploader(mounter.serialization_column, mounter.identifiers.first)
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
173
|
def #{column}_identifier
|
185
174
|
_mounter(:#{column}).read_identifiers[0]
|
186
175
|
end
|
187
176
|
|
188
|
-
def
|
189
|
-
|
190
|
-
@_previous_changes_for_#{column} = attribute_changes[_mounter(:#{column}).serialization_column]
|
177
|
+
def #{column}_integrity_error
|
178
|
+
#{column}_integrity_errors.last
|
191
179
|
end
|
192
180
|
|
193
|
-
def
|
194
|
-
|
195
|
-
|
181
|
+
def #{column}_processing_error
|
182
|
+
#{column}_processing_errors.last
|
183
|
+
end
|
184
|
+
|
185
|
+
def #{column}_download_error
|
186
|
+
#{column}_download_errors.last
|
196
187
|
end
|
197
188
|
RUBY
|
198
189
|
end
|
@@ -240,9 +231,9 @@ module CarrierWave
|
|
240
231
|
# [store_images!] Stores all files that have been assigned with +images=+
|
241
232
|
# [remove_images!] Removes the uploaded file from the filesystem.
|
242
233
|
#
|
243
|
-
# [
|
244
|
-
# [
|
245
|
-
# [
|
234
|
+
# [image_integrity_errors] Returns error objects of files which failed to pass integrity check
|
235
|
+
# [image_processing_errors] Returns error objects of files which failed to be processed
|
236
|
+
# [image_download_errors] Returns error objects of files which failed to be downloaded
|
246
237
|
#
|
247
238
|
# [image_identifiers] Reads out the identifiers of the files
|
248
239
|
#
|
@@ -286,7 +277,7 @@ module CarrierWave
|
|
286
277
|
# end
|
287
278
|
#
|
288
279
|
def mount_uploaders(column, uploader=nil, options={}, &block)
|
289
|
-
mount_base(column, uploader, options, &block)
|
280
|
+
mount_base(column, uploader, options.merge(multiple: true), &block)
|
290
281
|
|
291
282
|
mod = Module.new
|
292
283
|
include mod
|
@@ -325,38 +316,18 @@ module CarrierWave
|
|
325
316
|
_mounter(:#{column}).remote_request_headers = headers
|
326
317
|
end
|
327
318
|
|
328
|
-
def write_#{column}_identifier
|
329
|
-
return if frozen?
|
330
|
-
mounter = _mounter(:#{column})
|
331
|
-
|
332
|
-
if mounter.remove?
|
333
|
-
write_uploader(mounter.serialization_column, nil)
|
334
|
-
elsif mounter.identifiers.any?
|
335
|
-
write_uploader(mounter.serialization_column, mounter.identifiers)
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
319
|
def #{column}_identifiers
|
340
320
|
_mounter(:#{column}).read_identifiers
|
341
321
|
end
|
342
|
-
|
343
|
-
def store_previous_changes_for_#{column}
|
344
|
-
attribute_changes = ::ActiveRecord.version.to_s.to_f >= 5.1 ? saved_changes : changes
|
345
|
-
@_previous_changes_for_#{column} = attribute_changes[_mounter(:#{column}).serialization_column]
|
346
|
-
end
|
347
|
-
|
348
|
-
def remove_previously_stored_#{column}
|
349
|
-
_mounter(:#{column}).remove_previous(*@_previous_changes_for_#{column})
|
350
|
-
end
|
351
322
|
RUBY
|
352
323
|
end
|
353
324
|
|
354
|
-
|
325
|
+
private
|
355
326
|
|
356
327
|
def mount_base(column, uploader=nil, options={}, &block)
|
357
328
|
include CarrierWave::Mount::Extension
|
358
329
|
|
359
|
-
uploader = build_uploader(uploader, &block)
|
330
|
+
uploader = build_uploader(uploader, column, &block)
|
360
331
|
uploaders[column.to_sym] = uploader
|
361
332
|
uploader_options[column.to_sym] = options
|
362
333
|
|
@@ -381,6 +352,8 @@ module CarrierWave
|
|
381
352
|
|
382
353
|
def remove_#{column}!
|
383
354
|
_mounter(:#{column}).remove!
|
355
|
+
self.remove_#{column} = true
|
356
|
+
write_#{column}_identifier
|
384
357
|
end
|
385
358
|
|
386
359
|
def remove_#{column}=(value)
|
@@ -395,34 +368,48 @@ module CarrierWave
|
|
395
368
|
_mounter(:#{column}).store!
|
396
369
|
end
|
397
370
|
|
398
|
-
def #{column}
|
399
|
-
_mounter(:#{column}).
|
371
|
+
def #{column}_integrity_errors
|
372
|
+
_mounter(:#{column}).integrity_errors
|
400
373
|
end
|
401
374
|
|
402
|
-
def #{column}
|
403
|
-
_mounter(:#{column}).
|
375
|
+
def #{column}_processing_errors
|
376
|
+
_mounter(:#{column}).processing_errors
|
404
377
|
end
|
405
378
|
|
406
|
-
def #{column}
|
407
|
-
_mounter(:#{column}).
|
379
|
+
def #{column}_download_errors
|
380
|
+
_mounter(:#{column}).download_errors
|
381
|
+
end
|
382
|
+
|
383
|
+
def write_#{column}_identifier
|
384
|
+
_mounter(:#{column}).write_identifier
|
408
385
|
end
|
409
386
|
|
410
387
|
def mark_remove_#{column}_false
|
411
388
|
_mounter(:#{column}).remove = false
|
412
389
|
end
|
390
|
+
|
391
|
+
def reset_previous_changes_for_#{column}
|
392
|
+
_mounter(:#{column}).reset_changes!
|
393
|
+
end
|
394
|
+
|
395
|
+
def remove_previously_stored_#{column}
|
396
|
+
_mounter(:#{column}).remove_previous
|
397
|
+
end
|
398
|
+
|
399
|
+
def remove_rolled_back_#{column}
|
400
|
+
_mounter(:#{column}).remove_added
|
401
|
+
end
|
413
402
|
RUBY
|
414
403
|
end
|
415
404
|
|
416
|
-
def build_uploader(uploader, &block)
|
417
|
-
|
405
|
+
def build_uploader(uploader, column, &block)
|
406
|
+
uploader ||= CarrierWave::Uploader::Base
|
407
|
+
return uploader unless block_given?
|
418
408
|
|
419
|
-
uploader = Class.new(uploader
|
420
|
-
const_set("
|
409
|
+
uploader = Class.new(uploader)
|
410
|
+
const_set("CarrierWave#{column.to_s.camelize}Uploader", uploader)
|
421
411
|
|
422
|
-
|
423
|
-
uploader.class_eval(&block)
|
424
|
-
uploader.recursively_apply_block_to_versions(&block)
|
425
|
-
end
|
412
|
+
uploader.class_eval(&block)
|
426
413
|
|
427
414
|
uploader
|
428
415
|
end
|
@@ -441,11 +428,16 @@ module CarrierWave
|
|
441
428
|
|
442
429
|
private
|
443
430
|
|
431
|
+
def initialize_dup(other)
|
432
|
+
@_mounters = @_mounters.dup
|
433
|
+
super
|
434
|
+
end
|
435
|
+
|
444
436
|
def _mounter(column)
|
445
437
|
# We cannot memoize in frozen objects :(
|
446
|
-
return Mounter.
|
438
|
+
return Mounter.build(self, column) if frozen?
|
447
439
|
@_mounters ||= {}
|
448
|
-
@_mounters[column] ||= Mounter.
|
440
|
+
@_mounters[column] ||= Mounter.build(self, column)
|
449
441
|
end
|
450
442
|
|
451
443
|
end # Extension
|