carrierwave 1.3.4 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +278 -86
- 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 +53 -61
- data/lib/carrierwave/mounter.rb +169 -77
- data/lib/carrierwave/orm/activerecord.rb +23 -58
- data/lib/carrierwave/processing/mini_magick.rb +137 -123
- data/lib/carrierwave/processing/rmagick.rb +47 -20
- data/lib/carrierwave/processing/vips.rb +315 -0
- data/lib/carrierwave/processing.rb +1 -0
- data/lib/carrierwave/sanitized_file.rb +83 -70
- data/lib/carrierwave/storage/abstract.rb +5 -5
- data/lib/carrierwave/storage/file.rb +6 -5
- data/lib/carrierwave/storage/fog.rb +114 -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 +42 -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 +45 -7
- data/lib/carrierwave/uploader/proxy.rb +17 -4
- data/lib/carrierwave/uploader/serialization.rb +1 -1
- data/lib/carrierwave/uploader/store.rb +73 -7
- data/lib/carrierwave/uploader/url.rb +7 -4
- data/lib/carrierwave/uploader/versions.rb +161 -111
- 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 +22 -17
- data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -4
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +94 -47
- 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
|
@@ -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
|
-
|
|
30
|
+
add_deprecated_config :fog_provider
|
|
27
31
|
add_config :fog_attributes
|
|
28
32
|
add_config :fog_credentials
|
|
29
33
|
add_config :fog_directory
|
|
@@ -31,6 +35,7 @@ module CarrierWave
|
|
|
31
35
|
add_config :fog_authenticated_url_expiration
|
|
32
36
|
add_config :fog_use_ssl_for_aws
|
|
33
37
|
add_config :fog_aws_accelerate
|
|
38
|
+
add_config :fog_aws_fips
|
|
34
39
|
|
|
35
40
|
# Mounting
|
|
36
41
|
add_config :ignore_integrity_errors
|
|
@@ -41,6 +46,9 @@ module CarrierWave
|
|
|
41
46
|
add_config :validate_download
|
|
42
47
|
add_config :mount_on
|
|
43
48
|
add_config :cache_only
|
|
49
|
+
add_config :download_retry_count
|
|
50
|
+
add_config :download_retry_wait_time
|
|
51
|
+
add_config :skip_ssrf_protection
|
|
44
52
|
|
|
45
53
|
# set default values
|
|
46
54
|
reset_config
|
|
@@ -74,7 +82,7 @@ module CarrierWave
|
|
|
74
82
|
def storage(storage = nil)
|
|
75
83
|
case storage
|
|
76
84
|
when Symbol
|
|
77
|
-
if storage_engine = storage_engines[storage]
|
|
85
|
+
if (storage_engine = storage_engines[storage])
|
|
78
86
|
self._storage = eval storage_engine
|
|
79
87
|
else
|
|
80
88
|
raise CarrierWave::UnknownStorageError, "Unknown storage: #{storage}"
|
|
@@ -107,8 +115,8 @@ module CarrierWave
|
|
|
107
115
|
# cache_storage CarrierWave::Storage::File
|
|
108
116
|
# cache_storage MyCustomStorageEngine
|
|
109
117
|
#
|
|
110
|
-
def cache_storage(storage =
|
|
111
|
-
|
|
118
|
+
def cache_storage(storage = false)
|
|
119
|
+
unless storage == false
|
|
112
120
|
self._cache_storage = storage.is_a?(Symbol) ? eval(storage_engines[storage]) : storage
|
|
113
121
|
end
|
|
114
122
|
_cache_storage
|
|
@@ -119,16 +127,8 @@ module CarrierWave
|
|
|
119
127
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
120
128
|
@#{name} = nil
|
|
121
129
|
|
|
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
130
|
def self.#{name}(value=nil)
|
|
130
|
-
@#{name} = value
|
|
131
|
-
eager_load_fog(value) if value && '#{name}' == 'fog_credentials'
|
|
131
|
+
@#{name} = value unless value.nil?
|
|
132
132
|
return @#{name} if self.object_id == #{self.object_id} || defined?(@#{name})
|
|
133
133
|
name = superclass.#{name}
|
|
134
134
|
return nil if name.nil? && !instance_variable_defined?(:@#{name})
|
|
@@ -136,12 +136,10 @@ module CarrierWave
|
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
def self.#{name}=(value)
|
|
139
|
-
eager_load_fog(value) if '#{name}' == 'fog_credentials' && value.present?
|
|
140
139
|
@#{name} = value
|
|
141
140
|
end
|
|
142
141
|
|
|
143
142
|
def #{name}=(value)
|
|
144
|
-
self.class.eager_load_fog(value) if '#{name}' == 'fog_credentials' && value.present?
|
|
145
143
|
@#{name} = value
|
|
146
144
|
end
|
|
147
145
|
|
|
@@ -157,6 +155,26 @@ module CarrierWave
|
|
|
157
155
|
RUBY
|
|
158
156
|
end
|
|
159
157
|
|
|
158
|
+
def add_deprecated_config(name)
|
|
159
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
160
|
+
def self.#{name}(value=nil)
|
|
161
|
+
CarrierWave.deprecator.warn "##{name} is deprecated and has no effect"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def self.#{name}=(value)
|
|
165
|
+
CarrierWave.deprecator.warn "##{name} is deprecated and has no effect"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def #{name}=(value)
|
|
169
|
+
CarrierWave.deprecator.warn "##{name} is deprecated and has no effect"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def #{name}
|
|
173
|
+
CarrierWave.deprecator.warn "##{name} is deprecated and has no effect"
|
|
174
|
+
end
|
|
175
|
+
RUBY
|
|
176
|
+
end
|
|
177
|
+
|
|
160
178
|
def configure
|
|
161
179
|
yield self
|
|
162
180
|
end
|
|
@@ -166,27 +184,29 @@ module CarrierWave
|
|
|
166
184
|
#
|
|
167
185
|
def reset_config
|
|
168
186
|
configure do |config|
|
|
169
|
-
config.permissions =
|
|
170
|
-
config.directory_permissions =
|
|
187
|
+
config.permissions = 0o644
|
|
188
|
+
config.directory_permissions = 0o755
|
|
171
189
|
config.storage_engines = {
|
|
172
190
|
:file => "CarrierWave::Storage::File",
|
|
173
191
|
:fog => "CarrierWave::Storage::Fog"
|
|
174
192
|
}
|
|
175
193
|
config.storage = :file
|
|
176
|
-
config.cache_storage =
|
|
177
|
-
config.fog_provider = 'fog'
|
|
194
|
+
config.cache_storage = nil
|
|
178
195
|
config.fog_attributes = {}
|
|
179
196
|
config.fog_credentials = {}
|
|
180
197
|
config.fog_public = true
|
|
181
198
|
config.fog_authenticated_url_expiration = 600
|
|
182
199
|
config.fog_use_ssl_for_aws = true
|
|
183
200
|
config.fog_aws_accelerate = false
|
|
201
|
+
config.fog_aws_fips = false
|
|
184
202
|
config.store_dir = 'uploads'
|
|
185
203
|
config.cache_dir = 'uploads/tmp'
|
|
186
204
|
config.delete_tmp_file_after_storage = true
|
|
187
205
|
config.move_to_cache = false
|
|
188
206
|
config.move_to_store = false
|
|
189
207
|
config.remove_previously_stored_files_after_update = true
|
|
208
|
+
config.downloader = CarrierWave::Downloader::Base
|
|
209
|
+
config.force_extension = false
|
|
190
210
|
config.ignore_integrity_errors = true
|
|
191
211
|
config.ignore_processing_errors = true
|
|
192
212
|
config.ignore_download_errors = true
|
|
@@ -197,6 +217,9 @@ module CarrierWave
|
|
|
197
217
|
config.base_path = CarrierWave.base_path
|
|
198
218
|
config.enable_processing = true
|
|
199
219
|
config.ensure_multipart_form = true
|
|
220
|
+
config.download_retry_count = 0
|
|
221
|
+
config.download_retry_wait_time = 5
|
|
222
|
+
config.skip_ssrf_protection = false
|
|
200
223
|
end
|
|
201
224
|
end
|
|
202
225
|
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
|
+
CarrierWave.deprecator.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
|
+
CarrierWave.deprecator.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
|
+
CarrierWave.deprecator.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
|
-
|
|
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
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module CarrierWave
|
|
2
|
+
module Uploader
|
|
3
|
+
module ExtensionAllowlist
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
before :cache, :check_extension_allowlist!
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# Override this method in your uploader to provide an allowlist of extensions which
|
|
12
|
+
# are allowed to be uploaded. Compares the file's extension case insensitive.
|
|
13
|
+
# Furthermore, not only strings but Regexp are allowed as well.
|
|
14
|
+
#
|
|
15
|
+
# When using a Regexp in the allowlist, `\A` and `\z` are automatically added to
|
|
16
|
+
# the Regexp expression, also case insensitive.
|
|
17
|
+
#
|
|
18
|
+
# === Returns
|
|
19
|
+
#
|
|
20
|
+
# [NilClass, String, Regexp, Array[String, Regexp]] an allowlist of extensions which are allowed to be uploaded
|
|
21
|
+
#
|
|
22
|
+
# === Examples
|
|
23
|
+
#
|
|
24
|
+
# def extension_allowlist
|
|
25
|
+
# %w(jpg jpeg gif png)
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# Basically the same, but using a Regexp:
|
|
29
|
+
#
|
|
30
|
+
# def extension_allowlist
|
|
31
|
+
# [/jpe?g/, 'gif', 'png']
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
def extension_allowlist
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def check_extension_allowlist!(new_file)
|
|
40
|
+
allowlist = extension_allowlist
|
|
41
|
+
if !allowlist && respond_to?(:extension_whitelist) && extension_whitelist
|
|
42
|
+
CarrierWave.deprecator.warn "#extension_whitelist is deprecated, use #extension_allowlist instead." unless instance_variable_defined?(:@extension_whitelist_warned)
|
|
43
|
+
@extension_whitelist_warned = true
|
|
44
|
+
allowlist = extension_whitelist
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return unless allowlist
|
|
48
|
+
|
|
49
|
+
extension = new_file.extension.to_s
|
|
50
|
+
if !allowlisted_extension?(allowlist, extension)
|
|
51
|
+
# Look for whitelist first, then fallback to allowlist
|
|
52
|
+
raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_allowlist_error", extension: new_file.extension.inspect,
|
|
53
|
+
allowed_types: Array(allowlist).join(", "), default: :"errors.messages.extension_whitelist_error")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def allowlisted_extension?(allowlist, extension)
|
|
58
|
+
downcase_extension = extension.downcase
|
|
59
|
+
Array(allowlist).any? { |item| downcase_extension =~ /\A#{item}\z/i }
|
|
60
|
+
end
|
|
61
|
+
end # ExtensionAllowlist
|
|
62
|
+
end # Uploader
|
|
63
|
+
end # CarrierWave
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module CarrierWave
|
|
2
|
+
module Uploader
|
|
3
|
+
module ExtensionDenylist
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
before :cache, :check_extension_denylist!
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# Override this method in your uploader to provide a denylist of extensions which
|
|
12
|
+
# are prohibited to be uploaded. Compares the file's extension case insensitive.
|
|
13
|
+
# Furthermore, not only strings but Regexp are allowed as well.
|
|
14
|
+
#
|
|
15
|
+
# When using a Regexp in the denylist, `\A` and `\z` are automatically added to
|
|
16
|
+
# the Regexp expression, also case insensitive.
|
|
17
|
+
#
|
|
18
|
+
# === Returns
|
|
19
|
+
|
|
20
|
+
# [NilClass, String, Regexp, Array[String, Regexp]] a deny list of extensions which are prohibited to be uploaded
|
|
21
|
+
#
|
|
22
|
+
# === Examples
|
|
23
|
+
#
|
|
24
|
+
# def extension_denylist
|
|
25
|
+
# %w(swf tiff)
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# Basically the same, but using a Regexp:
|
|
29
|
+
#
|
|
30
|
+
# def extension_denylist
|
|
31
|
+
# [/swf/, 'tiff']
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
def extension_denylist
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def check_extension_denylist!(new_file)
|
|
40
|
+
denylist = extension_denylist
|
|
41
|
+
if !denylist && respond_to?(:extension_blacklist) && extension_blacklist
|
|
42
|
+
CarrierWave.deprecator.warn "#extension_blacklist is deprecated, use #extension_denylist instead." unless instance_variable_defined?(:@extension_blacklist_warned)
|
|
43
|
+
@extension_blacklist_warned = true
|
|
44
|
+
denylist = extension_blacklist
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return unless denylist
|
|
48
|
+
|
|
49
|
+
CarrierWave.deprecator.warn "Use of #extension_denylist is deprecated for the security reason, use #extension_allowlist instead to explicitly state what are safe to accept" unless instance_variable_defined?(:@extension_denylist_warned)
|
|
50
|
+
@extension_denylist_warned = true
|
|
51
|
+
|
|
52
|
+
extension = new_file.extension.to_s
|
|
53
|
+
if denylisted_extension?(denylist, extension)
|
|
54
|
+
raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_denylist_error", extension: new_file.extension.inspect,
|
|
55
|
+
prohibited_types: Array(extension_denylist).join(", "), default: :"errors.messages.extension_blacklist_error")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def denylisted_extension?(denylist, extension)
|
|
60
|
+
Array(denylist).any? { |item| extension =~ /\A#{item}\z/i }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -14,7 +14,7 @@ module CarrierWave
|
|
|
14
14
|
# are allowed to be uploaded.
|
|
15
15
|
# === Returns
|
|
16
16
|
#
|
|
17
|
-
# [NilClass, Range] a size range which are permitted to be uploaded
|
|
17
|
+
# [NilClass, Range] a size range (in bytes) which are permitted to be uploaded
|
|
18
18
|
#
|
|
19
19
|
# === Examples
|
|
20
20
|
#
|
|
@@ -24,7 +24,7 @@ module CarrierWave
|
|
|
24
24
|
#
|
|
25
25
|
def size_range; end
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
private
|
|
28
28
|
|
|
29
29
|
def check_size!(new_file)
|
|
30
30
|
size = new_file.size
|
|
@@ -33,6 +33,12 @@ module CarrierWave
|
|
|
33
33
|
@mounted_as = mounted_as
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
##
|
|
37
|
+
# Returns array index of given uploader within currently mounted uploaders
|
|
38
|
+
#
|
|
39
|
+
def index
|
|
40
|
+
model.__send__(:_mounter, mounted_as).uploaders.index(self)
|
|
41
|
+
end
|
|
36
42
|
end # Mountable
|
|
37
43
|
end # Uploader
|
|
38
44
|
end # CarrierWave
|