carrierwave 1.3.2 → 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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +235 -91
  3. data/lib/carrierwave/compatibility/paperclip.rb +4 -2
  4. data/lib/carrierwave/downloader/base.rb +101 -0
  5. data/lib/carrierwave/downloader/remote_file.rb +68 -0
  6. data/lib/carrierwave/locale/en.yml +9 -6
  7. data/lib/carrierwave/mount.rb +48 -61
  8. data/lib/carrierwave/mounter.rb +167 -77
  9. data/lib/carrierwave/orm/activerecord.rb +15 -55
  10. data/lib/carrierwave/processing/mini_magick.rb +108 -123
  11. data/lib/carrierwave/processing/rmagick.rb +11 -15
  12. data/lib/carrierwave/processing/vips.rb +284 -0
  13. data/lib/carrierwave/processing.rb +1 -0
  14. data/lib/carrierwave/sanitized_file.rb +60 -66
  15. data/lib/carrierwave/storage/abstract.rb +5 -5
  16. data/lib/carrierwave/storage/file.rb +6 -5
  17. data/lib/carrierwave/storage/fog.rb +101 -62
  18. data/lib/carrierwave/storage.rb +1 -0
  19. data/lib/carrierwave/test/matchers.rb +11 -7
  20. data/lib/carrierwave/uploader/cache.rb +40 -24
  21. data/lib/carrierwave/uploader/callbacks.rb +1 -1
  22. data/lib/carrierwave/uploader/configuration.rb +38 -19
  23. data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
  24. data/lib/carrierwave/uploader/content_type_denylist.rb +62 -0
  25. data/lib/carrierwave/uploader/dimension.rb +66 -0
  26. data/lib/carrierwave/uploader/download.rb +2 -123
  27. data/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
  28. data/lib/carrierwave/uploader/extension_denylist.rb +64 -0
  29. data/lib/carrierwave/uploader/file_size.rb +2 -2
  30. data/lib/carrierwave/uploader/mountable.rb +6 -0
  31. data/lib/carrierwave/uploader/processing.rb +42 -7
  32. data/lib/carrierwave/uploader/proxy.rb +17 -4
  33. data/lib/carrierwave/uploader/serialization.rb +1 -1
  34. data/lib/carrierwave/uploader/store.rb +47 -7
  35. data/lib/carrierwave/uploader/url.rb +7 -4
  36. data/lib/carrierwave/uploader/versions.rb +153 -105
  37. data/lib/carrierwave/uploader.rb +10 -17
  38. data/lib/carrierwave/utilities/file_name.rb +47 -0
  39. data/lib/carrierwave/utilities/uri.rb +14 -11
  40. data/lib/carrierwave/utilities.rb +1 -0
  41. data/lib/carrierwave/validations/active_model.rb +7 -9
  42. data/lib/carrierwave/version.rb +1 -1
  43. data/lib/carrierwave.rb +13 -17
  44. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +2 -2
  45. data/lib/generators/uploader_generator.rb +3 -3
  46. metadata +100 -33
  47. data/lib/carrierwave/uploader/content_type_blacklist.rb +0 -48
  48. data/lib/carrierwave/uploader/content_type_whitelist.rb +0 -48
  49. data/lib/carrierwave/uploader/extension_blacklist.rb +0 -51
  50. 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
- extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
8
- extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
9
- content_type_whitelist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
10
- content_type_blacklist_error: "You are not allowed to upload %{content_type} files"
11
- rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
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"
@@ -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 = [url]
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 store_previous_changes_for_#{column}
189
- attribute_changes = ::ActiveRecord.version.to_s.to_f >= 5.1 ? saved_changes : changes
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 remove_previously_stored_#{column}
194
- before, after = @_previous_changes_for_#{column}
195
- _mounter(:#{column}).remove_previous([before], [after])
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
- # [images_integrity_error] Returns an error object if the last files to be assigned caused an integrity error
244
- # [images_processing_error] Returns an error object if the last files to be assigned caused a processing error
245
- # [images_download_error] Returns an error object if the last files to be remotely assigned caused a download error
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
- private
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}_integrity_error
399
- _mounter(:#{column}).integrity_error
371
+ def #{column}_integrity_errors
372
+ _mounter(:#{column}).integrity_errors
400
373
  end
401
374
 
402
- def #{column}_processing_error
403
- _mounter(:#{column}).processing_error
375
+ def #{column}_processing_errors
376
+ _mounter(:#{column}).processing_errors
404
377
  end
405
378
 
406
- def #{column}_download_error
407
- _mounter(:#{column}).download_error
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
- return uploader if uploader && !block_given?
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 || CarrierWave::Uploader::Base)
420
- const_set("Uploader#{uploader.object_id}".tr('-', '_'), uploader)
409
+ uploader = Class.new(uploader)
410
+ const_set("CarrierWave#{column.to_s.camelize}Uploader", uploader)
421
411
 
422
- if block_given?
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.new(self, column) if frozen?
433
+ return Mounter.build(self, column) if frozen?
447
434
  @_mounters ||= {}
448
- @_mounters[column] ||= Mounter.new(self, column)
435
+ @_mounters[column] ||= Mounter.build(self, column)
449
436
  end
450
437
 
451
438
  end # Extension