carrierwave 1.3.2 → 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
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 +53 -61
  8. data/lib/carrierwave/mounter.rb +167 -77
  9. data/lib/carrierwave/orm/activerecord.rb +23 -58
  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 +157 -109
  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} +3 -3
  45. data/lib/generators/uploader_generator.rb +3 -3
  46. metadata +103 -36
  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
+ 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
- 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
@@ -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.new(self, column) if frozen?
438
+ return Mounter.build(self, column) if frozen?
447
439
  @_mounters ||= {}
448
- @_mounters[column] ||= Mounter.new(self, column)
440
+ @_mounters[column] ||= Mounter.build(self, column)
449
441
  end
450
442
 
451
443
  end # Extension