carrierwave 3.0.0.beta → 3.0.0.rc
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 +104 -64
- data/lib/carrierwave/compatibility/paperclip.rb +4 -2
- data/lib/carrierwave/downloader/base.rb +12 -11
- data/lib/carrierwave/downloader/remote_file.rb +5 -6
- data/lib/carrierwave/locale/en.yml +4 -0
- data/lib/carrierwave/mount.rb +31 -82
- data/lib/carrierwave/mounter.rb +115 -50
- data/lib/carrierwave/orm/activerecord.rb +8 -59
- data/lib/carrierwave/processing/mini_magick.rb +13 -11
- data/lib/carrierwave/processing/rmagick.rb +7 -11
- data/lib/carrierwave/processing/vips.rb +9 -9
- data/lib/carrierwave/sanitized_file.rb +47 -37
- data/lib/carrierwave/storage/abstract.rb +5 -5
- data/lib/carrierwave/storage/file.rb +4 -3
- data/lib/carrierwave/storage/fog.rb +69 -50
- data/lib/carrierwave/test/matchers.rb +11 -7
- data/lib/carrierwave/uploader/cache.rb +17 -9
- data/lib/carrierwave/uploader/callbacks.rb +1 -1
- data/lib/carrierwave/uploader/configuration.rb +8 -4
- data/lib/carrierwave/uploader/dimension.rb +66 -0
- data/lib/carrierwave/uploader/file_size.rb +2 -2
- data/lib/carrierwave/uploader/processing.rb +21 -7
- data/lib/carrierwave/uploader/proxy.rb +16 -3
- data/lib/carrierwave/uploader/store.rb +43 -6
- data/lib/carrierwave/uploader/url.rb +1 -1
- data/lib/carrierwave/uploader/versions.rb +126 -134
- data/lib/carrierwave/uploader.rb +2 -0
- data/lib/carrierwave/utilities/file_name.rb +2 -2
- data/lib/carrierwave/utilities/uri.rb +14 -11
- data/lib/carrierwave/validations/active_model.rb +4 -6
- data/lib/carrierwave/version.rb +1 -1
- data/lib/carrierwave.rb +8 -7
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +18 -3
- /data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +0 -0
data/lib/carrierwave/mounter.rb
CHANGED
@@ -2,18 +2,49 @@ module CarrierWave
|
|
2
2
|
|
3
3
|
# this is an internal class, used by CarrierWave::Mount so that
|
4
4
|
# we don't pollute the model with a lot of methods.
|
5
|
-
class Mounter
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
class Mounter # :nodoc:
|
6
|
+
class Single < Mounter # :nodoc
|
7
|
+
def identifier
|
8
|
+
uploaders.first&.identifier
|
9
|
+
end
|
10
|
+
|
11
|
+
def temporary_identifier
|
12
|
+
temporary_identifiers.first
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Multiple < Mounter # :nodoc
|
17
|
+
def identifier
|
18
|
+
uploaders.map(&:identifier).presence
|
19
|
+
end
|
20
|
+
|
21
|
+
def temporary_identifier
|
22
|
+
temporary_identifiers.presence
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.build(record, column)
|
27
|
+
if record.class.uploader_options[column][:multiple]
|
28
|
+
Multiple.new(record, column)
|
29
|
+
else
|
30
|
+
Single.new(record, column)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :column, :record, :remote_urls, :remove,
|
35
|
+
:integrity_errors, :processing_errors, :download_errors
|
36
|
+
attr_accessor :remote_request_headers, :uploader_options
|
9
37
|
|
10
|
-
def initialize(record, column
|
38
|
+
def initialize(record, column)
|
11
39
|
@record = record
|
12
40
|
@column = column
|
13
41
|
@options = record.class.uploader_options[column]
|
14
42
|
@download_errors = []
|
15
43
|
@processing_errors = []
|
16
44
|
@integrity_errors = []
|
45
|
+
|
46
|
+
@removed_uploaders = []
|
47
|
+
@added_uploaders = []
|
17
48
|
end
|
18
49
|
|
19
50
|
def uploader_class
|
@@ -35,7 +66,7 @@ module CarrierWave
|
|
35
66
|
def uploaders
|
36
67
|
@uploaders ||= read_identifiers.map do |identifier|
|
37
68
|
uploader = blank_uploader
|
38
|
-
uploader.retrieve_from_store!(identifier)
|
69
|
+
uploader.retrieve_from_store!(identifier)
|
39
70
|
uploader
|
40
71
|
end
|
41
72
|
end
|
@@ -46,7 +77,7 @@ module CarrierWave
|
|
46
77
|
@uploaders = new_files.map do |new_file|
|
47
78
|
handle_error do
|
48
79
|
if new_file.is_a?(String)
|
49
|
-
if (uploader = old_uploaders.detect { |
|
80
|
+
if (uploader = old_uploaders.detect { |old_uploader| old_uploader.identifier == new_file })
|
50
81
|
uploader.staged = true
|
51
82
|
uploader
|
52
83
|
else
|
@@ -64,7 +95,9 @@ module CarrierWave
|
|
64
95
|
uploader
|
65
96
|
end
|
66
97
|
end
|
67
|
-
end.
|
98
|
+
end.reject(&:blank?)
|
99
|
+
@removed_uploaders += (old_uploaders - @uploaders)
|
100
|
+
write_temporary_identifier
|
68
101
|
end
|
69
102
|
|
70
103
|
def cache_names
|
@@ -76,33 +109,51 @@ module CarrierWave
|
|
76
109
|
return if cache_names.blank?
|
77
110
|
clear_unstaged
|
78
111
|
cache_names.each do |cache_name|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
# ignore
|
85
|
-
end
|
112
|
+
uploader = blank_uploader
|
113
|
+
uploader.retrieve_from_cache!(cache_name)
|
114
|
+
@uploaders << uploader
|
115
|
+
rescue CarrierWave::InvalidParameter
|
116
|
+
# ignore
|
86
117
|
end
|
118
|
+
write_temporary_identifier
|
87
119
|
end
|
88
120
|
|
89
121
|
def remote_urls=(urls)
|
90
|
-
|
91
|
-
|
122
|
+
if urls.nil?
|
123
|
+
urls = []
|
124
|
+
else
|
125
|
+
urls = Array.wrap(urls).reject(&:blank?)
|
126
|
+
return if urls.blank?
|
127
|
+
end
|
92
128
|
@remote_urls = urls
|
93
129
|
|
94
130
|
clear_unstaged
|
95
|
-
|
131
|
+
@remote_urls.zip(remote_request_headers || []) do |url, header|
|
96
132
|
handle_error do
|
97
133
|
uploader = blank_uploader
|
98
134
|
uploader.download!(url, header || {})
|
99
135
|
@uploaders << uploader
|
100
136
|
end
|
101
137
|
end
|
138
|
+
write_temporary_identifier
|
102
139
|
end
|
103
140
|
|
104
141
|
def store!
|
105
|
-
uploaders.
|
142
|
+
additions, remains = uploaders.partition(&:cached?)
|
143
|
+
existing_paths = (@removed_uploaders + remains).map(&:store_path)
|
144
|
+
additions.each do |uploader|
|
145
|
+
uploader.deduplicate(existing_paths)
|
146
|
+
uploader.store!
|
147
|
+
existing_paths << uploader.store_path
|
148
|
+
end
|
149
|
+
@added_uploaders += additions
|
150
|
+
end
|
151
|
+
|
152
|
+
def write_identifier
|
153
|
+
return if record.frozen?
|
154
|
+
|
155
|
+
clear! if remove?
|
156
|
+
record.write_uploader(serialization_column, identifier)
|
106
157
|
end
|
107
158
|
|
108
159
|
def urls(*args)
|
@@ -113,52 +164,50 @@ module CarrierWave
|
|
113
164
|
uploaders.none?(&:present?)
|
114
165
|
end
|
115
166
|
|
167
|
+
def remove=(value)
|
168
|
+
@remove = value
|
169
|
+
write_temporary_identifier
|
170
|
+
end
|
171
|
+
|
116
172
|
def remove?
|
117
|
-
remove.present? && (remove
|
173
|
+
remove.present? && (remove.to_s !~ /\A0|false$\z/)
|
118
174
|
end
|
119
175
|
|
120
176
|
def remove!
|
121
|
-
uploaders.
|
122
|
-
|
177
|
+
uploaders.each(&:remove!)
|
178
|
+
clear!
|
123
179
|
end
|
124
180
|
|
125
181
|
def clear!
|
182
|
+
@removed_uploaders += uploaders
|
183
|
+
@remove = nil
|
126
184
|
@uploaders = []
|
127
185
|
end
|
128
186
|
|
187
|
+
def reset_changes!
|
188
|
+
@removed_uploaders = []
|
189
|
+
@added_uploaders = []
|
190
|
+
end
|
191
|
+
|
129
192
|
def serialization_column
|
130
193
|
option(:mount_on) || column
|
131
194
|
end
|
132
195
|
|
133
|
-
def remove_previous
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
if value.is_a?(String)
|
140
|
-
uploader = blank_uploader
|
141
|
-
uploader.retrieve_from_store!(value)
|
142
|
-
uploader
|
143
|
-
else
|
144
|
-
value
|
145
|
-
end
|
146
|
-
end
|
147
|
-
after_paths = after.reject(&:blank?).map do |value|
|
148
|
-
if value.is_a?(String)
|
149
|
-
uploader = blank_uploader
|
150
|
-
uploader.retrieve_from_store!(value)
|
151
|
-
uploader
|
152
|
-
else
|
153
|
-
value
|
154
|
-
end.path
|
155
|
-
end
|
156
|
-
before.each do |uploader|
|
157
|
-
uploader.remove! if uploader.remove_previously_stored_files_after_update && !after_paths.include?(uploader.path)
|
158
|
-
end
|
196
|
+
def remove_previous
|
197
|
+
current_paths = uploaders.map(&:path)
|
198
|
+
@removed_uploaders
|
199
|
+
.reject {|uploader| current_paths.include?(uploader.path) }
|
200
|
+
.each { |uploader| uploader.remove! if uploader.remove_previously_stored_files_after_update }
|
201
|
+
reset_changes!
|
159
202
|
end
|
160
203
|
|
161
|
-
|
204
|
+
def remove_added
|
205
|
+
current_paths = (@removed_uploaders + @uploaders.select(&:staged)).map(&:path)
|
206
|
+
@added_uploaders
|
207
|
+
.reject {|uploader| current_paths.include?(uploader.path) }
|
208
|
+
.each { |uploader| uploader.remove! }
|
209
|
+
reset_changes!
|
210
|
+
end
|
162
211
|
|
163
212
|
private
|
164
213
|
|
@@ -169,7 +218,9 @@ module CarrierWave
|
|
169
218
|
|
170
219
|
def clear_unstaged
|
171
220
|
@uploaders ||= []
|
172
|
-
@uploaders.
|
221
|
+
staged, unstaged = @uploaders.partition(&:staged)
|
222
|
+
@uploaders = staged
|
223
|
+
@removed_uploaders += unstaged
|
173
224
|
end
|
174
225
|
|
175
226
|
def handle_error
|
@@ -184,5 +235,19 @@ module CarrierWave
|
|
184
235
|
@integrity_errors << e
|
185
236
|
raise e unless option(:ignore_integrity_errors)
|
186
237
|
end
|
238
|
+
|
239
|
+
def write_temporary_identifier
|
240
|
+
return if record.frozen?
|
241
|
+
|
242
|
+
record.write_uploader(serialization_column, temporary_identifier)
|
243
|
+
end
|
244
|
+
|
245
|
+
def temporary_identifiers
|
246
|
+
if remove?
|
247
|
+
[]
|
248
|
+
else
|
249
|
+
uploaders.map { |uploader| uploader.temporary_identifier }
|
250
|
+
end
|
251
|
+
end
|
187
252
|
end # Mounter
|
188
253
|
end # CarrierWave
|
@@ -6,40 +6,6 @@ module CarrierWave
|
|
6
6
|
|
7
7
|
include CarrierWave::Mount
|
8
8
|
|
9
|
-
##
|
10
|
-
# See +CarrierWave::Mount#mount_uploader+ for documentation
|
11
|
-
#
|
12
|
-
def mount_uploader(column, uploader=nil, options={}, &block)
|
13
|
-
super
|
14
|
-
|
15
|
-
mod = Module.new
|
16
|
-
prepend mod
|
17
|
-
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
|
18
|
-
def remote_#{column}_url=(url)
|
19
|
-
column = _mounter(:#{column}).serialization_column
|
20
|
-
__send__(:"\#{column}_will_change!")
|
21
|
-
super
|
22
|
-
end
|
23
|
-
RUBY
|
24
|
-
end
|
25
|
-
|
26
|
-
##
|
27
|
-
# See +CarrierWave::Mount#mount_uploaders+ for documentation
|
28
|
-
#
|
29
|
-
def mount_uploaders(column, uploader=nil, options={}, &block)
|
30
|
-
super
|
31
|
-
|
32
|
-
mod = Module.new
|
33
|
-
prepend mod
|
34
|
-
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
|
35
|
-
def remote_#{column}_urls=(url)
|
36
|
-
column = _mounter(:#{column}).serialization_column
|
37
|
-
__send__(:"\#{column}_will_change!")
|
38
|
-
super
|
39
|
-
end
|
40
|
-
RUBY
|
41
|
-
end
|
42
|
-
|
43
9
|
private
|
44
10
|
|
45
11
|
def mount_base(column, uploader=nil, options={}, &block)
|
@@ -61,7 +27,6 @@ module CarrierWave
|
|
61
27
|
after_commit :"remove_#{column}!", :on => :destroy
|
62
28
|
after_commit :"mark_remove_#{column}_false", :on => :update
|
63
29
|
|
64
|
-
after_save :"store_previous_changes_for_#{column}"
|
65
30
|
after_commit :"reset_previous_changes_for_#{column}"
|
66
31
|
after_commit :"remove_previously_stored_#{column}", :on => :update
|
67
32
|
after_rollback :"remove_rolled_back_#{column}"
|
@@ -69,29 +34,6 @@ module CarrierWave
|
|
69
34
|
mod = Module.new
|
70
35
|
prepend mod
|
71
36
|
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
|
72
|
-
def #{column}=(new_file)
|
73
|
-
column = _mounter(:#{column}).serialization_column
|
74
|
-
if !(new_file.blank? && __send__(:#{column}).blank?)
|
75
|
-
__send__(:"\#{column}_will_change!")
|
76
|
-
end
|
77
|
-
|
78
|
-
super
|
79
|
-
end
|
80
|
-
|
81
|
-
def remove_#{column}=(value)
|
82
|
-
column = _mounter(:#{column}).serialization_column
|
83
|
-
result = super
|
84
|
-
__send__(:"\#{column}_will_change!") if _mounter(:#{column}).remove?
|
85
|
-
result
|
86
|
-
end
|
87
|
-
|
88
|
-
def remove_#{column}!
|
89
|
-
self.remove_#{column} = true
|
90
|
-
write_#{column}_identifier
|
91
|
-
self.remove_#{column} = false
|
92
|
-
super
|
93
|
-
end
|
94
|
-
|
95
37
|
# Reset cached mounter on record reload
|
96
38
|
def reload(*)
|
97
39
|
@_mounters = nil
|
@@ -100,7 +42,14 @@ module CarrierWave
|
|
100
42
|
|
101
43
|
# Reset cached mounter on record dup
|
102
44
|
def initialize_dup(other)
|
103
|
-
|
45
|
+
old_uploaders = _mounter(:"#{column}").uploaders
|
46
|
+
@_mounters[:"#{column}"] = nil
|
47
|
+
super
|
48
|
+
_mounter(:"#{column}").cache(old_uploaders)
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_#{column}_identifier
|
52
|
+
return unless has_attribute?(_mounter(:#{column}).serialization_column)
|
104
53
|
super
|
105
54
|
end
|
106
55
|
RUBY
|
@@ -263,7 +263,8 @@ module CarrierWave
|
|
263
263
|
FileUtils.mv image.path, current_path
|
264
264
|
|
265
265
|
image.run_command("identify", current_path)
|
266
|
-
rescue ::MiniMagick::Error, ::MiniMagick::Invalid
|
266
|
+
rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e
|
267
|
+
raise e if e.message =~ /(You must have .+ installed|is not installed|executable not found)/
|
267
268
|
message = I18n.translate(:"errors.messages.processing_error")
|
268
269
|
raise CarrierWave::ProcessingError, message
|
269
270
|
ensure
|
@@ -309,23 +310,24 @@ module CarrierWave
|
|
309
310
|
file.content_type = Marcel::Magic.by_path(move_to).try(:type)
|
310
311
|
file.move_to(move_to, permissions, directory_permissions)
|
311
312
|
end
|
312
|
-
rescue ::MiniMagick::Error, ::MiniMagick::Invalid
|
313
|
+
rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e
|
314
|
+
raise e if e.message =~ /(You must have .+ installed|is not installed|executable not found)/
|
313
315
|
message = I18n.translate(:"errors.messages.processing_error")
|
314
316
|
raise CarrierWave::ProcessingError, message
|
315
317
|
end
|
316
318
|
|
317
|
-
|
319
|
+
private
|
318
320
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
end
|
321
|
+
def resolve_dimensions(*dimensions)
|
322
|
+
dimensions.map do |value|
|
323
|
+
next value unless value.instance_of?(Proc)
|
324
|
+
value.arity >= 1 ? value.call(self) : value.call
|
324
325
|
end
|
326
|
+
end
|
325
327
|
|
326
|
-
|
327
|
-
|
328
|
-
|
328
|
+
def mini_magick_image
|
329
|
+
::MiniMagick::Image.read(read)
|
330
|
+
end
|
329
331
|
|
330
332
|
end # MiniMagick
|
331
333
|
end # CarrierWave
|
@@ -62,10 +62,12 @@ module CarrierWave
|
|
62
62
|
begin
|
63
63
|
require "rmagick"
|
64
64
|
rescue LoadError
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
65
|
+
begin
|
66
|
+
require "RMagick"
|
67
|
+
rescue LoadError => e
|
68
|
+
e.message << " (You may need to install the rmagick gem)"
|
69
|
+
raise e
|
70
|
+
end
|
69
71
|
end
|
70
72
|
|
71
73
|
prepend Module.new {
|
@@ -228,13 +230,7 @@ module CarrierWave
|
|
228
230
|
height = dimension_from height
|
229
231
|
manipulate! do |img|
|
230
232
|
img.resize_to_fit!(width, height)
|
231
|
-
|
232
|
-
if background == :transparent
|
233
|
-
filled = new_img.matte_floodfill(1, 1)
|
234
|
-
else
|
235
|
-
filled = new_img.color_floodfill(1, 1, ::Magick::Pixel.from_color(background))
|
236
|
-
end
|
237
|
-
destroy_image(new_img)
|
233
|
+
filled = ::Magick::Image.new(width, height) { |image| image.background_color = background == :transparent ? 'rgba(255,255,255,0)' : background.to_s }
|
238
234
|
filled.composite!(img, gravity, ::Magick::OverCompositeOp)
|
239
235
|
destroy_image(img)
|
240
236
|
filled = yield(filled) if block_given?
|
@@ -267,18 +267,18 @@ module CarrierWave
|
|
267
267
|
raise CarrierWave::ProcessingError, message
|
268
268
|
end
|
269
269
|
|
270
|
-
|
270
|
+
private
|
271
271
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
end
|
272
|
+
def resolve_dimensions(*dimensions)
|
273
|
+
dimensions.map do |value|
|
274
|
+
next value unless value.instance_of?(Proc)
|
275
|
+
value.arity >= 1 ? value.call(self) : value.call
|
277
276
|
end
|
277
|
+
end
|
278
278
|
|
279
|
-
|
280
|
-
|
281
|
-
|
279
|
+
def vips_image
|
280
|
+
::Vips::Image.new_from_buffer(read, "")
|
281
|
+
end
|
282
282
|
|
283
283
|
end # Vips
|
284
284
|
end # CarrierWave
|
@@ -27,7 +27,7 @@ module CarrierWave
|
|
27
27
|
|
28
28
|
def initialize(file)
|
29
29
|
self.file = file
|
30
|
-
@content = nil
|
30
|
+
@content = @content_type = nil
|
31
31
|
end
|
32
32
|
|
33
33
|
##
|
@@ -39,7 +39,7 @@ module CarrierWave
|
|
39
39
|
#
|
40
40
|
def original_filename
|
41
41
|
return @original_filename if @original_filename
|
42
|
-
if @file
|
42
|
+
if @file && @file.respond_to?(:original_filename)
|
43
43
|
@file.original_filename
|
44
44
|
elsif path
|
45
45
|
File.basename(path)
|
@@ -109,7 +109,7 @@ module CarrierWave
|
|
109
109
|
# [Boolean] whether the file is valid and has a non-zero size
|
110
110
|
#
|
111
111
|
def empty?
|
112
|
-
@file.nil? || self.size.nil? || (self.size.zero? && !
|
112
|
+
@file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
|
113
113
|
end
|
114
114
|
|
115
115
|
##
|
@@ -128,14 +128,20 @@ module CarrierWave
|
|
128
128
|
#
|
129
129
|
# [String] contents of the file
|
130
130
|
#
|
131
|
-
def read
|
131
|
+
def read(*args)
|
132
132
|
if @content
|
133
|
-
|
133
|
+
if args.empty?
|
134
|
+
@content
|
135
|
+
else
|
136
|
+
length, outbuf = args
|
137
|
+
raise ArgumentError, "outbuf argument not supported since the content is already loaded" if outbuf
|
138
|
+
@content[0, length]
|
139
|
+
end
|
134
140
|
elsif is_path?
|
135
|
-
File.open(@file, "rb") {|file| file.read}
|
141
|
+
File.open(@file, "rb") {|file| file.read(*args)}
|
136
142
|
else
|
137
143
|
@file.try(:rewind)
|
138
|
-
@content = @file.read
|
144
|
+
@content = @file.read(*args)
|
139
145
|
@file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
|
140
146
|
@content
|
141
147
|
end
|
@@ -157,13 +163,10 @@ module CarrierWave
|
|
157
163
|
mkdir!(new_path, directory_permissions)
|
158
164
|
move!(new_path)
|
159
165
|
chmod!(new_path, permissions)
|
160
|
-
|
161
|
-
self.file = {:tempfile => new_path, :filename => original_filename, :content_type => @content_type}
|
162
|
-
else
|
163
|
-
self.file = {:tempfile => new_path, :content_type => @content_type}
|
164
|
-
end
|
166
|
+
self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
|
165
167
|
self
|
166
168
|
end
|
169
|
+
|
167
170
|
##
|
168
171
|
# Helper to move file to new path.
|
169
172
|
#
|
@@ -195,7 +198,7 @@ module CarrierWave
|
|
195
198
|
mkdir!(new_path, directory_permissions)
|
196
199
|
copy!(new_path)
|
197
200
|
chmod!(new_path, permissions)
|
198
|
-
self.class.new({:
|
201
|
+
self.class.new({tempfile: new_path, content_type: declared_content_type})
|
199
202
|
end
|
200
203
|
|
201
204
|
##
|
@@ -237,9 +240,10 @@ module CarrierWave
|
|
237
240
|
#
|
238
241
|
def content_type
|
239
242
|
@content_type ||=
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
+
identified_content_type ||
|
244
|
+
declared_content_type ||
|
245
|
+
guessed_safe_content_type ||
|
246
|
+
Marcel::MimeType::BINARY
|
243
247
|
end
|
244
248
|
|
245
249
|
##
|
@@ -270,11 +274,11 @@ module CarrierWave
|
|
270
274
|
if file.is_a?(Hash)
|
271
275
|
@file = file["tempfile"] || file[:tempfile]
|
272
276
|
@original_filename = file["filename"] || file[:filename]
|
273
|
-
@
|
277
|
+
@declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
|
274
278
|
else
|
275
279
|
@file = file
|
276
280
|
@original_filename = nil
|
277
|
-
@
|
281
|
+
@declared_content_type = nil
|
278
282
|
end
|
279
283
|
end
|
280
284
|
|
@@ -294,39 +298,45 @@ module CarrierWave
|
|
294
298
|
name = name.scrub
|
295
299
|
name = name.tr("\\", "/") # work-around for IE
|
296
300
|
name = File.basename(name)
|
297
|
-
name = name.gsub(sanitize_regexp,"_")
|
301
|
+
name = name.gsub(sanitize_regexp, "_")
|
298
302
|
name = "_#{name}" if name =~ /\A\.+\z/
|
299
303
|
name = "unnamed" if name.size.zero?
|
300
|
-
|
304
|
+
name.mb_chars.to_s
|
301
305
|
end
|
302
306
|
|
303
|
-
def
|
304
|
-
|
305
|
-
@file.content_type.
|
306
|
-
|
307
|
+
def declared_content_type
|
308
|
+
@declared_content_type ||
|
309
|
+
if @file.respond_to?(:content_type) && @file.content_type
|
310
|
+
@file.content_type.to_s.chomp
|
311
|
+
end
|
307
312
|
end
|
308
313
|
|
309
|
-
|
314
|
+
# Guess content type from its file extension. Limit what to be returned to prevent spoofing.
|
315
|
+
def guessed_safe_content_type
|
310
316
|
return unless path
|
311
317
|
|
312
|
-
type =
|
313
|
-
|
314
|
-
|
318
|
+
type = Marcel::Magic.by_path(original_filename).to_s
|
319
|
+
type if type.start_with?('text/') || type.start_with?('application/json')
|
320
|
+
end
|
315
321
|
|
316
|
-
|
317
|
-
|
318
|
-
|
322
|
+
def identified_content_type
|
323
|
+
with_io do |io|
|
324
|
+
Marcel::Magic.by_magic(io).try(:type)
|
319
325
|
end
|
320
|
-
|
321
|
-
type
|
322
326
|
rescue Errno::ENOENT
|
323
327
|
nil
|
324
328
|
end
|
325
329
|
|
326
|
-
def
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
+
def with_io(&block)
|
331
|
+
if file.is_a?(IO)
|
332
|
+
begin
|
333
|
+
yield file
|
334
|
+
ensure
|
335
|
+
file.try(:rewind)
|
336
|
+
end
|
337
|
+
elsif path
|
338
|
+
File.open(path, &block)
|
339
|
+
end
|
330
340
|
end
|
331
341
|
end # SanitizedFile
|
332
342
|
end # CarrierWave
|
@@ -14,7 +14,7 @@ module CarrierWave
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def identifier
|
17
|
-
uploader.
|
17
|
+
uploader.deduplicated_filename
|
18
18
|
end
|
19
19
|
|
20
20
|
def store!(file)
|
@@ -24,19 +24,19 @@ module CarrierWave
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def cache!(new_file)
|
27
|
-
raise NotImplementedError
|
27
|
+
raise NotImplementedError, "Need to implement #cache! if you want to use #{self.class.name} as a cache storage."
|
28
28
|
end
|
29
29
|
|
30
30
|
def retrieve_from_cache!(identifier)
|
31
|
-
raise NotImplementedError
|
31
|
+
raise NotImplementedError, "Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage."
|
32
32
|
end
|
33
33
|
|
34
34
|
def delete_dir!(path)
|
35
|
-
raise NotImplementedError
|
35
|
+
raise NotImplementedError, "Need to implement #delete_dir! if you want to use #{self.class.name} as a cache storage."
|
36
36
|
end
|
37
37
|
|
38
38
|
def clean_cache!(seconds)
|
39
|
-
raise NotImplementedError
|
39
|
+
raise NotImplementedError, "Need to implement #clean_cache! if you want to use #{self.class.name} as a cache storage."
|
40
40
|
end
|
41
41
|
end # Abstract
|
42
42
|
end # Storage
|
@@ -109,10 +109,11 @@ module CarrierWave
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def clean_cache!(seconds)
|
112
|
-
Dir.glob(::File.expand_path(::File.join(uploader.cache_dir, '*'),
|
112
|
+
Dir.glob(::File.expand_path(::File.join(uploader.cache_dir, '*'), uploader.root)).each do |dir|
|
113
113
|
# generate_cache_id returns key formatted TIMEINT-PID(-COUNTER)-RND
|
114
|
-
|
115
|
-
|
114
|
+
matched = dir.scan(/(\d+)-\d+-\d+(?:-\d+)?/).first
|
115
|
+
next unless matched
|
116
|
+
time = Time.at(matched[0].to_i)
|
116
117
|
if time < (Time.now.utc - seconds)
|
117
118
|
FileUtils.rm_rf(dir)
|
118
119
|
end
|