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
@@ -1,3 +1,5 @@
1
+ require 'securerandom'
2
+
1
3
  module CarrierWave
2
4
 
3
5
  class FormNotMultipart < UploadError
@@ -22,10 +24,11 @@ module CarrierWave
22
24
  # [String] a cache id in the format TIMEINT-PID-COUNTER-RND
23
25
  #
24
26
  def self.generate_cache_id
25
- [Time.now.utc.to_i,
26
- Process.pid,
27
- '%04d' % (CarrierWave::CacheCounter.increment % 1000),
28
- '%04d' % rand(9999)
27
+ [
28
+ Time.now.utc.to_i,
29
+ SecureRandom.random_number(1_000_000_000_000_000),
30
+ '%04d' % (CarrierWave::CacheCounter.increment % 10_000),
31
+ '%04d' % SecureRandom.random_number(10_000)
29
32
  ].map(&:to_s).join('-')
30
33
  end
31
34
 
@@ -36,6 +39,16 @@ module CarrierWave
36
39
  include CarrierWave::Uploader::Callbacks
37
40
  include CarrierWave::Uploader::Configuration
38
41
 
42
+ included do
43
+ prepend Module.new {
44
+ def initialize(*)
45
+ super
46
+ @staged = false
47
+ end
48
+ }
49
+ attr_accessor :staged
50
+ end
51
+
39
52
  module ClassMethods
40
53
 
41
54
  ##
@@ -52,7 +65,7 @@ module CarrierWave
52
65
  # It's recommended that you keep cache files in one place only.
53
66
  #
54
67
  def clean_cached_files!(seconds=60*60*24)
55
- cache_storage.new(CarrierWave::Uploader::Base.new).clean_cache!(seconds)
68
+ (cache_storage || storage).new(new).clean_cache!(seconds)
56
69
  end
57
70
  end
58
71
 
@@ -64,7 +77,7 @@ module CarrierWave
64
77
  # [Bool] whether the current file is cached
65
78
  #
66
79
  def cached?
67
- @cache_id
80
+ !!@cache_id
68
81
  end
69
82
 
70
83
  ##
@@ -78,14 +91,8 @@ module CarrierWave
78
91
  end
79
92
 
80
93
  def sanitized_file
81
- _content = file.read
82
- if _content.is_a?(File) # could be if storage is Fog
83
- sanitized = CarrierWave::Storage::Fog.new(self).retrieve!(File.basename(_content.path))
84
- else
85
- sanitized = SanitizedFile.new :tempfile => StringIO.new(_content),
86
- :filename => File.basename(path), :content_type => file.content_type
87
- end
88
- sanitized
94
+ ActiveSupport::Deprecation.warn('#sanitized_file is deprecated, use #file instead.')
95
+ file
89
96
  end
90
97
 
91
98
  ##
@@ -96,7 +103,7 @@ module CarrierWave
96
103
  # [String] a cache name, in the format TIMEINT-PID-COUNTER-RND/filename.txt
97
104
  #
98
105
  def cache_name
99
- File.join(cache_id, full_original_filename) if cache_id and original_filename
106
+ File.join(cache_id, original_filename) if cache_id && original_filename
100
107
  end
101
108
 
102
109
  ##
@@ -104,7 +111,7 @@ module CarrierWave
104
111
  #
105
112
  # By default, cache!() uses copy_to(), which operates by copying the file
106
113
  # to the cache, then deleting the original file. If move_to_cache() is
107
- # overriden to return true, then cache!() uses move_to(), which simply
114
+ # overridden to return true, then cache!() uses move_to(), which simply
108
115
  # moves the file to the cache. Useful for large files.
109
116
  #
110
117
  # === Parameters
@@ -115,7 +122,7 @@ module CarrierWave
115
122
  #
116
123
  # [CarrierWave::FormNotMultipart] if the assigned parameter is a string
117
124
  #
118
- def cache!(new_file = sanitized_file)
125
+ def cache!(new_file = file)
119
126
  new_file = CarrierWave::SanitizedFile.new(new_file)
120
127
  return if new_file.empty?
121
128
 
@@ -123,6 +130,8 @@ module CarrierWave
123
130
 
124
131
  self.cache_id = CarrierWave.generate_cache_id unless cache_id
125
132
 
133
+ @identifier = nil
134
+ @staged = true
126
135
  @filename = new_file.filename
127
136
  self.original_filename = new_file.filename
128
137
 
@@ -156,8 +165,9 @@ module CarrierWave
156
165
  def retrieve_from_cache!(cache_name)
157
166
  with_callbacks(:retrieve_from_cache, cache_name) do
158
167
  self.cache_id, self.original_filename = cache_name.to_s.split('/', 2)
168
+ @staged = true
159
169
  @filename = original_filename
160
- @file = cache_storage.retrieve_from_cache!(full_filename(original_filename))
170
+ @file = cache_storage.retrieve_from_cache!(full_original_filename)
161
171
  end
162
172
  end
163
173
 
@@ -172,20 +182,21 @@ module CarrierWave
172
182
  #
173
183
  # [String] the cache path
174
184
  #
175
- def cache_path(for_file=full_filename(original_filename))
185
+ def cache_path(for_file=full_original_filename)
176
186
  File.join(*[cache_dir, @cache_id, for_file].compact)
177
187
  end
178
188
 
189
+ protected
190
+
191
+ attr_reader :cache_id
192
+
179
193
  private
180
194
 
181
195
  def workfile_path(for_file=original_filename)
182
196
  File.join(CarrierWave.tmp_path, @cache_id, version_name.to_s, for_file)
183
197
  end
184
198
 
185
- attr_reader :cache_id, :original_filename
186
-
187
- # We can override the full_original_filename method in other modules
188
- alias_method :full_original_filename, :original_filename
199
+ attr_reader :original_filename
189
200
 
190
201
  def cache_id=(cache_id)
191
202
  # Earlier version used 3 part cache_id. Thus we should allow for
@@ -200,7 +211,12 @@ module CarrierWave
200
211
  end
201
212
 
202
213
  def cache_storage
203
- @cache_storage ||= self.class.cache_storage.new(self)
214
+ @cache_storage ||= (self.class.cache_storage || self.class.storage).new(self)
215
+ end
216
+
217
+ # We can override the full_original_filename method in other modules
218
+ def full_original_filename
219
+ forcing_extension(original_filename)
204
220
  end
205
221
  end # Cache
206
222
  end # Uploader
@@ -5,7 +5,7 @@ module CarrierWave
5
5
 
6
6
  included do
7
7
  class_attribute :_before_callbacks, :_after_callbacks,
8
- :instance_writer => false
8
+ :instance_writer => false
9
9
  self._before_callbacks = Hash.new []
10
10
  self._after_callbacks = Hash.new []
11
11
  end
@@ -1,3 +1,5 @@
1
+ require 'carrierwave/downloader/base'
2
+
1
3
  module CarrierWave
2
4
 
3
5
  module Uploader
@@ -21,9 +23,11 @@ module CarrierWave
21
23
  add_config :move_to_cache
22
24
  add_config :move_to_store
23
25
  add_config :remove_previously_stored_files_after_update
26
+ add_config :downloader
27
+ add_config :force_extension
24
28
 
25
29
  # fog
26
- add_config :fog_provider
30
+ add_deprecated_config :fog_provider
27
31
  add_config :fog_attributes
28
32
  add_config :fog_credentials
29
33
  add_config :fog_directory
@@ -41,6 +45,8 @@ module CarrierWave
41
45
  add_config :validate_download
42
46
  add_config :mount_on
43
47
  add_config :cache_only
48
+ add_config :download_retry_count
49
+ add_config :download_retry_wait_time
44
50
 
45
51
  # set default values
46
52
  reset_config
@@ -74,7 +80,7 @@ module CarrierWave
74
80
  def storage(storage = nil)
75
81
  case storage
76
82
  when Symbol
77
- if storage_engine = storage_engines[storage]
83
+ if (storage_engine = storage_engines[storage])
78
84
  self._storage = eval storage_engine
79
85
  else
80
86
  raise CarrierWave::UnknownStorageError, "Unknown storage: #{storage}"
@@ -107,8 +113,8 @@ module CarrierWave
107
113
  # cache_storage CarrierWave::Storage::File
108
114
  # cache_storage MyCustomStorageEngine
109
115
  #
110
- def cache_storage(storage = nil)
111
- if storage
116
+ def cache_storage(storage = false)
117
+ unless storage == false
112
118
  self._cache_storage = storage.is_a?(Symbol) ? eval(storage_engines[storage]) : storage
113
119
  end
114
120
  _cache_storage
@@ -119,16 +125,8 @@ module CarrierWave
119
125
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
120
126
  @#{name} = nil
121
127
 
122
- def self.eager_load_fog(fog_credentials)
123
- # see #1198. This will hopefully no longer be necessary after fog 2.0
124
- require self.fog_provider
125
- require 'carrierwave/storage/fog'
126
- Fog::Storage.new(fog_credentials) if fog_credentials.present?
127
- end unless defined? eager_load_fog
128
-
129
128
  def self.#{name}(value=nil)
130
- @#{name} = value if value
131
- eager_load_fog(value) if value && '#{name}' == 'fog_credentials'
129
+ @#{name} = value unless value.nil?
132
130
  return @#{name} if self.object_id == #{self.object_id} || defined?(@#{name})
133
131
  name = superclass.#{name}
134
132
  return nil if name.nil? && !instance_variable_defined?(:@#{name})
@@ -136,12 +134,10 @@ module CarrierWave
136
134
  end
137
135
 
138
136
  def self.#{name}=(value)
139
- eager_load_fog(value) if '#{name}' == 'fog_credentials' && value.present?
140
137
  @#{name} = value
141
138
  end
142
139
 
143
140
  def #{name}=(value)
144
- self.class.eager_load_fog(value) if '#{name}' == 'fog_credentials' && value.present?
145
141
  @#{name} = value
146
142
  end
147
143
 
@@ -157,6 +153,26 @@ module CarrierWave
157
153
  RUBY
158
154
  end
159
155
 
156
+ def add_deprecated_config(name)
157
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
158
+ def self.#{name}(value=nil)
159
+ ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
160
+ end
161
+
162
+ def self.#{name}=(value)
163
+ ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
164
+ end
165
+
166
+ def #{name}=(value)
167
+ ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
168
+ end
169
+
170
+ def #{name}
171
+ ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
172
+ end
173
+ RUBY
174
+ end
175
+
160
176
  def configure
161
177
  yield self
162
178
  end
@@ -166,15 +182,14 @@ module CarrierWave
166
182
  #
167
183
  def reset_config
168
184
  configure do |config|
169
- config.permissions = 0644
170
- config.directory_permissions = 0755
185
+ config.permissions = 0o644
186
+ config.directory_permissions = 0o755
171
187
  config.storage_engines = {
172
188
  :file => "CarrierWave::Storage::File",
173
189
  :fog => "CarrierWave::Storage::Fog"
174
190
  }
175
191
  config.storage = :file
176
- config.cache_storage = :file
177
- config.fog_provider = 'fog'
192
+ config.cache_storage = nil
178
193
  config.fog_attributes = {}
179
194
  config.fog_credentials = {}
180
195
  config.fog_public = true
@@ -187,6 +202,8 @@ module CarrierWave
187
202
  config.move_to_cache = false
188
203
  config.move_to_store = false
189
204
  config.remove_previously_stored_files_after_update = true
205
+ config.downloader = CarrierWave::Downloader::Base
206
+ config.force_extension = false
190
207
  config.ignore_integrity_errors = true
191
208
  config.ignore_processing_errors = true
192
209
  config.ignore_download_errors = true
@@ -197,6 +214,8 @@ module CarrierWave
197
214
  config.base_path = CarrierWave.base_path
198
215
  config.enable_processing = true
199
216
  config.ensure_multipart_form = true
217
+ config.download_retry_count = 0
218
+ config.download_retry_wait_time = 5
200
219
  end
201
220
  end
202
221
  end
@@ -0,0 +1,62 @@
1
+ module CarrierWave
2
+ module Uploader
3
+ module ContentTypeAllowlist
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before :cache, :check_content_type_allowlist!
8
+ end
9
+
10
+ ##
11
+ # Override this method in your uploader to provide an allowlist of files content types
12
+ # which are allowed to be uploaded.
13
+ # Not only strings but Regexp are allowed as well.
14
+ #
15
+ # === Returns
16
+ #
17
+ # [NilClass, String, Regexp, Array[String, Regexp]] an allowlist of content types which are allowed to be uploaded
18
+ #
19
+ # === Examples
20
+ #
21
+ # def content_type_allowlist
22
+ # %w(text/json application/json)
23
+ # end
24
+ #
25
+ # Basically the same, but using a Regexp:
26
+ #
27
+ # def content_type_allowlist
28
+ # [/(text|application)\/json/]
29
+ # end
30
+ #
31
+ def content_type_allowlist
32
+ end
33
+
34
+ private
35
+
36
+ def check_content_type_allowlist!(new_file)
37
+ allowlist = content_type_allowlist
38
+ if !allowlist && respond_to?(:content_type_whitelist) && content_type_whitelist
39
+ ActiveSupport::Deprecation.warn "#content_type_whitelist is deprecated, use #content_type_allowlist instead." unless instance_variable_defined?(:@content_type_whitelist_warned)
40
+ @content_type_whitelist_warned = true
41
+ allowlist = content_type_whitelist
42
+ end
43
+
44
+ return unless allowlist
45
+
46
+ content_type = new_file.content_type
47
+ if !allowlisted_content_type?(allowlist, content_type)
48
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.content_type_allowlist_error", content_type: content_type,
49
+ allowed_types: Array(allowlist).join(", "), default: :"errors.messages.content_type_whitelist_error")
50
+ end
51
+ end
52
+
53
+ def allowlisted_content_type?(allowlist, content_type)
54
+ Array(allowlist).any? do |item|
55
+ item = Regexp.quote(item) if item.class != Regexp
56
+ content_type =~ /#{item}/
57
+ end
58
+ end
59
+
60
+ end # ContentTypeAllowlist
61
+ end # Uploader
62
+ end # CarrierWave
@@ -0,0 +1,62 @@
1
+ module CarrierWave
2
+ module Uploader
3
+ module ContentTypeDenylist
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before :cache, :check_content_type_denylist!
8
+ end
9
+
10
+ ##
11
+ # Override this method in your uploader to provide a denylist of files content types
12
+ # which are not allowed to be uploaded.
13
+ # Not only strings but Regexp are allowed as well.
14
+ #
15
+ # === Returns
16
+ #
17
+ # [NilClass, String, Regexp, Array[String, Regexp]] a denylist of content types which are not allowed to be uploaded
18
+ #
19
+ # === Examples
20
+ #
21
+ # def content_type_denylist
22
+ # %w(text/json application/json)
23
+ # end
24
+ #
25
+ # Basically the same, but using a Regexp:
26
+ #
27
+ # def content_type_denylist
28
+ # [/(text|application)\/json/]
29
+ # end
30
+ #
31
+ def content_type_denylist
32
+ end
33
+
34
+ private
35
+
36
+ def check_content_type_denylist!(new_file)
37
+ denylist = content_type_denylist
38
+ if !denylist && respond_to?(:content_type_blacklist) && content_type_blacklist
39
+ ActiveSupport::Deprecation.warn "#content_type_blacklist is deprecated, use #content_type_denylist instead." unless instance_variable_defined?(:@content_type_blacklist_warned)
40
+ @content_type_blacklist_warned = true
41
+ denylist = content_type_blacklist
42
+ end
43
+
44
+ return unless denylist
45
+
46
+ ActiveSupport::Deprecation.warn "Use of #content_type_denylist is deprecated for the security reason, use #content_type_allowlist instead to explicitly state what are safe to accept" unless instance_variable_defined?(:@content_type_denylist_warned)
47
+ @content_type_denylist_warned = true
48
+
49
+ content_type = new_file.content_type
50
+ if denylisted_content_type?(denylist, content_type)
51
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.content_type_denylist_error",
52
+ content_type: content_type, default: :"errors.messages.content_type_blacklist_error")
53
+ end
54
+ end
55
+
56
+ def denylisted_content_type?(denylist, content_type)
57
+ Array(denylist).any? { |item| content_type =~ /#{item}/ }
58
+ end
59
+
60
+ end # ContentTypeDenylist
61
+ end # Uploader
62
+ end # CarrierWave
@@ -0,0 +1,66 @@
1
+ require 'active_support'
2
+
3
+ module CarrierWave
4
+ module Uploader
5
+ module Dimension
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ before :cache, :check_dimensions!
10
+ end
11
+
12
+ ##
13
+ # Override this method in your uploader to provide a Range of width which
14
+ # are allowed to be uploaded.
15
+ # === Returns
16
+ #
17
+ # [NilClass, Range] a width range which are permitted to be uploaded
18
+ #
19
+ # === Examples
20
+ #
21
+ # def width_range
22
+ # 1000..2000
23
+ # end
24
+ #
25
+ def width_range; end
26
+
27
+ ##
28
+ # Override this method in your uploader to provide a Range of height which
29
+ # are allowed to be uploaded.
30
+ # === Returns
31
+ #
32
+ # [NilClass, Range] a height range which are permitted to be uploaded
33
+ #
34
+ # === Examples
35
+ #
36
+ # def height_range
37
+ # 1000..
38
+ # end
39
+ #
40
+ def height_range; end
41
+
42
+ private
43
+
44
+ def check_dimensions!(new_file)
45
+ # NOTE: Skip the check for resized images
46
+ return if version_name.present?
47
+ return unless width_range || height_range
48
+
49
+ unless respond_to?(:width) || respond_to?(:height)
50
+ raise 'You need to include one of CarrierWave::MiniMagick, CarrierWave::RMagick, or CarrierWave::Vips to perform image dimension validation'
51
+ end
52
+
53
+ if width_range&.begin && width < width_range.begin
54
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.min_width_error", :min_width => ActiveSupport::NumberHelper.number_to_delimited(width_range.begin))
55
+ elsif width_range&.end && width > width_range.end
56
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.max_width_error", :max_width => ActiveSupport::NumberHelper.number_to_delimited(width_range.end))
57
+ elsif height_range&.begin && height < height_range.begin
58
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.min_height_error", :min_height => ActiveSupport::NumberHelper.number_to_delimited(height_range.begin))
59
+ elsif height_range&.end && height > height_range.end
60
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.max_height_error", :max_height => ActiveSupport::NumberHelper.number_to_delimited(height_range.end))
61
+ end
62
+ end
63
+
64
+ end # Dimension
65
+ end # Uploader
66
+ end # CarrierWave
@@ -1,6 +1,3 @@
1
- require 'open-uri'
2
- require 'ssrf_filter'
3
-
4
1
  module CarrierWave
5
2
  module Uploader
6
3
  module Download
@@ -10,87 +7,8 @@ module CarrierWave
10
7
  include CarrierWave::Uploader::Configuration
11
8
  include CarrierWave::Uploader::Cache
12
9
 
13
- class RemoteFile
14
- attr_reader :uri
15
-
16
- def initialize(uri, remote_headers = {}, skip_ssrf_protection: false)
17
- @uri = uri
18
- @remote_headers = remote_headers.reverse_merge('User-Agent' => "CarrierWave/#{CarrierWave::VERSION}")
19
- @file, @content_type, @headers = nil
20
- @skip_ssrf_protection = skip_ssrf_protection
21
- end
22
-
23
- def original_filename
24
- filename = filename_from_header || filename_from_uri
25
- mime_type = MIME::Types[content_type].first
26
- unless File.extname(filename).present? || mime_type.blank?
27
- filename = "#{filename}.#{mime_type.extensions.first}"
28
- end
29
- filename
30
- end
31
-
32
- def respond_to?(*args)
33
- super or file.respond_to?(*args)
34
- end
35
-
36
- def http?
37
- @uri.scheme =~ /^https?$/
38
- end
39
-
40
- def content_type
41
- @content_type || 'application/octet-stream'
42
- end
43
-
44
- def headers
45
- @headers || {}
46
- end
47
-
48
- private
49
-
50
- def file
51
- if @file.blank?
52
- if @skip_ssrf_protection
53
- @file = (URI.respond_to?(:open) ? URI : Kernel).open(@uri.to_s, @remote_headers)
54
- @file = @file.is_a?(String) ? StringIO.new(@file) : @file
55
- @content_type = @file.content_type
56
- @headers = @file.meta
57
- @uri = @file.base_uri
58
- else
59
- request = nil
60
- response = SsrfFilter.get(@uri, headers: @remote_headers) do |req|
61
- request = req
62
- end
63
- response.value
64
- @file = StringIO.new(response.body)
65
- @content_type = response.content_type
66
- @headers = response
67
- @uri = request.uri
68
- end
69
- end
70
- @file
71
-
72
- rescue StandardError => e
73
- raise CarrierWave::DownloadError, "could not download file: #{e.message}"
74
- end
75
-
76
- def filename_from_header
77
- if headers['content-disposition']
78
- match = headers['content-disposition'].match(/filename="?([^"]+)/)
79
- return match[1] unless match.nil? || match[1].empty?
80
- end
81
- end
82
-
83
- def filename_from_uri
84
- URI::DEFAULT_PARSER.unescape(File.basename(@uri.path))
85
- end
86
-
87
- def method_missing(*args, &block)
88
- file.send(*args, &block)
89
- end
90
- end
91
-
92
10
  ##
93
- # Caches the file by downloading it from the given URL.
11
+ # Caches the file by downloading it from the given URL, using downloader.
94
12
  #
95
13
  # === Parameters
96
14
  #
@@ -98,48 +16,9 @@ module CarrierWave
98
16
  # [remote_headers (Hash)] Request headers
99
17
  #
100
18
  def download!(uri, remote_headers = {})
101
- processed_uri = process_uri(uri)
102
- file = RemoteFile.new(processed_uri, remote_headers, skip_ssrf_protection: skip_ssrf_protection?(processed_uri))
103
- raise CarrierWave::DownloadError, "trying to download a file which is not served over HTTP" unless file.http?
19
+ file = downloader.new(self).download(uri, remote_headers)
104
20
  cache!(file)
105
21
  end
106
-
107
- ##
108
- # Processes the given URL by parsing and escaping it. Public to allow overriding.
109
- #
110
- # === Parameters
111
- #
112
- # [url (String)] The URL where the remote file is stored
113
- #
114
- def process_uri(uri)
115
- URI.parse(uri)
116
- rescue URI::InvalidURIError
117
- uri_parts = uri.split('?')
118
- # regexp from Ruby's URI::Parser#regexp[:UNSAFE], with [] specifically removed
119
- encoded_uri = URI::DEFAULT_PARSER.escape(uri_parts.shift, /[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,]/)
120
- encoded_uri << '?' << URI::DEFAULT_PARSER.escape(uri_parts.join('?')) if uri_parts.any?
121
- URI.parse(encoded_uri) rescue raise CarrierWave::DownloadError, "couldn't parse URL"
122
- end
123
-
124
- ##
125
- # If this returns true, SSRF protection will be bypassed.
126
- # You can override this if you want to allow accessing specific local URIs that are not SSRF exploitable.
127
- #
128
- # === Parameters
129
- #
130
- # [uri (URI)] The URI where the remote file is stored
131
- #
132
- # === Examples
133
- #
134
- # class MyUploader < CarrierWave::Uploader::Base
135
- # def skip_ssrf_protection?(uri)
136
- # uri.hostname == 'localhost' && uri.port == 80
137
- # end
138
- # end
139
- #
140
- def skip_ssrf_protection?(uri)
141
- false
142
- end
143
22
  end # Download
144
23
  end # Uploader
145
24
  end # CarrierWave