carrierwave 1.3.2 → 3.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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 +53 -61
- data/lib/carrierwave/mounter.rb +167 -77
- data/lib/carrierwave/orm/activerecord.rb +23 -58
- 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 +157 -109
- 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} +3 -3
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +103 -36
- 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 =~ /\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
|
-
|
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
|