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
@@ -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 =~ /\A#{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