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.
- checksums.yaml +4 -4
- data/README.md +235 -91
- 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 +48 -61
- data/lib/carrierwave/mounter.rb +167 -77
- data/lib/carrierwave/orm/activerecord.rb +15 -55
- data/lib/carrierwave/processing/mini_magick.rb +108 -123
- data/lib/carrierwave/processing/rmagick.rb +11 -15
- data/lib/carrierwave/processing/vips.rb +284 -0
- data/lib/carrierwave/processing.rb +1 -0
- data/lib/carrierwave/sanitized_file.rb +60 -66
- data/lib/carrierwave/storage/abstract.rb +5 -5
- data/lib/carrierwave/storage/file.rb +6 -5
- data/lib/carrierwave/storage/fog.rb +101 -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 +38 -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 +42 -7
- data/lib/carrierwave/uploader/proxy.rb +17 -4
- data/lib/carrierwave/uploader/serialization.rb +1 -1
- data/lib/carrierwave/uploader/store.rb +47 -7
- data/lib/carrierwave/uploader/url.rb +7 -4
- data/lib/carrierwave/uploader/versions.rb +153 -105
- 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 +13 -17
- data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +2 -2
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +100 -33
- 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 '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
|
-
[
|
26
|
-
|
27
|
-
|
28
|
-
'%04d' %
|
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(
|
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
|
-
|
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
|
-
|
82
|
-
|
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,
|
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
|
-
#
|
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 =
|
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!(
|
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=
|
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 :
|
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
|
@@ -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
|
@@ -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 =
|
111
|
-
|
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
|
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 =
|
170
|
-
config.directory_permissions =
|
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 =
|
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
|
-
|
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
|