carrierwave 1.3.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.
- 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 +48 -61
- data/lib/carrierwave/mounter.rb +167 -77
- data/lib/carrierwave/orm/activerecord.rb +15 -55
- 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 +153 -105
- 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} +2 -2
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +100 -33
- 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
|
+
mime_type = Marcel::TYPES[content_type]
|
37
|
+
unless File.extname(filename).present? || mime_type.blank?
|
38
|
+
extension = mime_type[0].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
|
@@ -443,9 +430,9 @@ module CarrierWave
|
|
443
430
|
|
444
431
|
def _mounter(column)
|
445
432
|
# We cannot memoize in frozen objects :(
|
446
|
-
return Mounter.
|
433
|
+
return Mounter.build(self, column) if frozen?
|
447
434
|
@_mounters ||= {}
|
448
|
-
@_mounters[column] ||= Mounter.
|
435
|
+
@_mounters[column] ||= Mounter.build(self, column)
|
449
436
|
end
|
450
437
|
|
451
438
|
end # Extension
|