carrierwave 1.3.1 → 3.0.5
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 +238 -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 +15 -55
- data/lib/carrierwave/processing/mini_magick.rb +108 -123
- data/lib/carrierwave/processing/rmagick.rb +20 -18
- data/lib/carrierwave/processing/vips.rb +284 -0
- data/lib/carrierwave/processing.rb +1 -0
- data/lib/carrierwave/sanitized_file.rb +66 -73
- data/lib/carrierwave/storage/abstract.rb +5 -5
- data/lib/carrierwave/storage/file.rb +6 -5
- data/lib/carrierwave/storage/fog.rb +101 -64
- 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 -80
- 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} +3 -3
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +104 -38
- 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
@@ -0,0 +1,284 @@
|
|
1
|
+
module CarrierWave
|
2
|
+
|
3
|
+
##
|
4
|
+
# This module simplifies manipulation with vips by providing a set
|
5
|
+
# of convenient helper methods. If you want to use them, you'll need to
|
6
|
+
# require this file:
|
7
|
+
#
|
8
|
+
# require 'carrierwave/processing/vips'
|
9
|
+
#
|
10
|
+
# And then include it in your uploader:
|
11
|
+
#
|
12
|
+
# class MyUploader < CarrierWave::Uploader::Base
|
13
|
+
# include CarrierWave::Vips
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# You can now use the provided helpers:
|
17
|
+
#
|
18
|
+
# class MyUploader < CarrierWave::Uploader::Base
|
19
|
+
# include CarrierWave::Vips
|
20
|
+
#
|
21
|
+
# process :resize_to_fit => [200, 200]
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Or create your own helpers with the powerful vips! method, which
|
25
|
+
# yields an ImageProcessing::Builder object. Check out the ImageProcessing
|
26
|
+
# docs at http://github.com/janko-m/image_processing and the list of all
|
27
|
+
# available Vips options at
|
28
|
+
# https://libvips.github.io/libvips/API/current/using-cli.html for more info.
|
29
|
+
#
|
30
|
+
# class MyUploader < CarrierWave::Uploader::Base
|
31
|
+
# include CarrierWave::Vips
|
32
|
+
#
|
33
|
+
# process :radial_blur => 10
|
34
|
+
#
|
35
|
+
# def radial_blur(amount)
|
36
|
+
# vips! do |builder|
|
37
|
+
# builder.radial_blur(amount)
|
38
|
+
# builder = yield(builder) if block_given?
|
39
|
+
# builder
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# === Note
|
45
|
+
#
|
46
|
+
# The ImageProcessing gem uses ruby-vips, a binding for the vips image
|
47
|
+
# library. You can find more information here:
|
48
|
+
#
|
49
|
+
# https://github.com/libvips/ruby-vips
|
50
|
+
#
|
51
|
+
#
|
52
|
+
module Vips
|
53
|
+
extend ActiveSupport::Concern
|
54
|
+
|
55
|
+
included do
|
56
|
+
require "image_processing/vips"
|
57
|
+
# We need to disable caching since we're editing images in place.
|
58
|
+
::Vips.cache_set_max(0)
|
59
|
+
end
|
60
|
+
|
61
|
+
module ClassMethods
|
62
|
+
def convert(format)
|
63
|
+
process :convert => format
|
64
|
+
end
|
65
|
+
|
66
|
+
def resize_to_limit(width, height)
|
67
|
+
process :resize_to_limit => [width, height]
|
68
|
+
end
|
69
|
+
|
70
|
+
def resize_to_fit(width, height)
|
71
|
+
process :resize_to_fit => [width, height]
|
72
|
+
end
|
73
|
+
|
74
|
+
def resize_to_fill(width, height, gravity='centre')
|
75
|
+
process :resize_to_fill => [width, height, gravity]
|
76
|
+
end
|
77
|
+
|
78
|
+
def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil)
|
79
|
+
process :resize_and_pad => [width, height, background, gravity, alpha]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Changes the image encoding format to the given format
|
85
|
+
#
|
86
|
+
# See https://libvips.github.io/libvips/API/current/using-cli.html#using-command-line-conversion
|
87
|
+
#
|
88
|
+
# === Parameters
|
89
|
+
#
|
90
|
+
# [format (#to_s)] an abbreviation of the format
|
91
|
+
#
|
92
|
+
# === Yields
|
93
|
+
#
|
94
|
+
# [Vips::Image] additional manipulations to perform
|
95
|
+
#
|
96
|
+
# === Examples
|
97
|
+
#
|
98
|
+
# image.convert(:png)
|
99
|
+
#
|
100
|
+
def convert(format, page=nil)
|
101
|
+
vips! do |builder|
|
102
|
+
builder = builder.convert(format)
|
103
|
+
builder = builder.loader(page: page) if page
|
104
|
+
builder
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Resize the image to fit within the specified dimensions while retaining
|
110
|
+
# the original aspect ratio. Will only resize the image if it is larger than the
|
111
|
+
# specified dimensions. The resulting image may be shorter or narrower than specified
|
112
|
+
# in the smaller dimension but will not be larger than the specified values.
|
113
|
+
#
|
114
|
+
# === Parameters
|
115
|
+
#
|
116
|
+
# [width (Integer)] the width to scale the image to
|
117
|
+
# [height (Integer)] the height to scale the image to
|
118
|
+
# [combine_options (Hash)] additional Vips options to apply before resizing
|
119
|
+
#
|
120
|
+
# === Yields
|
121
|
+
#
|
122
|
+
# [Vips::Image] additional manipulations to perform
|
123
|
+
#
|
124
|
+
def resize_to_limit(width, height, combine_options: {})
|
125
|
+
width, height = resolve_dimensions(width, height)
|
126
|
+
|
127
|
+
vips! do |builder|
|
128
|
+
builder.resize_to_limit(width, height)
|
129
|
+
.apply(combine_options)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Resize the image to fit within the specified dimensions while retaining
|
135
|
+
# the original aspect ratio. The image may be shorter or narrower than
|
136
|
+
# specified in the smaller dimension but will not be larger than the specified values.
|
137
|
+
#
|
138
|
+
# === Parameters
|
139
|
+
#
|
140
|
+
# [width (Integer)] the width to scale the image to
|
141
|
+
# [height (Integer)] the height to scale the image to
|
142
|
+
# [combine_options (Hash)] additional Vips options to apply before resizing
|
143
|
+
#
|
144
|
+
# === Yields
|
145
|
+
#
|
146
|
+
# [Vips::Image] additional manipulations to perform
|
147
|
+
#
|
148
|
+
def resize_to_fit(width, height, combine_options: {})
|
149
|
+
width, height = resolve_dimensions(width, height)
|
150
|
+
|
151
|
+
vips! do |builder|
|
152
|
+
builder.resize_to_fit(width, height)
|
153
|
+
.apply(combine_options)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# Resize the image to fit within the specified dimensions while retaining
|
159
|
+
# the aspect ratio of the original image. If necessary, crop the image in the
|
160
|
+
# larger dimension.
|
161
|
+
#
|
162
|
+
# === Parameters
|
163
|
+
#
|
164
|
+
# [width (Integer)] the width to scale the image to
|
165
|
+
# [height (Integer)] the height to scale the image to
|
166
|
+
# [combine_options (Hash)] additional vips options to apply before resizing
|
167
|
+
#
|
168
|
+
# === Yields
|
169
|
+
#
|
170
|
+
# [Vips::Image] additional manipulations to perform
|
171
|
+
#
|
172
|
+
def resize_to_fill(width, height, _gravity = nil, combine_options: {})
|
173
|
+
width, height = resolve_dimensions(width, height)
|
174
|
+
|
175
|
+
vips! do |builder|
|
176
|
+
builder.resize_to_fill(width, height).apply(combine_options)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Resize the image to fit within the specified dimensions while retaining
|
182
|
+
# the original aspect ratio. If necessary, will pad the remaining area
|
183
|
+
# with the given color, which defaults to transparent (for gif and png,
|
184
|
+
# white for jpeg).
|
185
|
+
#
|
186
|
+
# See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsCompassDirection
|
187
|
+
# for gravity options.
|
188
|
+
#
|
189
|
+
# === Parameters
|
190
|
+
#
|
191
|
+
# [width (Integer)] the width to scale the image to
|
192
|
+
# [height (Integer)] the height to scale the image to
|
193
|
+
# [background (List, nil)] the color of the background as a RGB, like [0, 255, 255], nil indicates transparent
|
194
|
+
# [gravity (String)] how to position the image
|
195
|
+
# [alpha (Boolean, nil)] pad the image with the alpha channel if supported
|
196
|
+
# [combine_options (Hash)] additional vips options to apply before resizing
|
197
|
+
#
|
198
|
+
# === Yields
|
199
|
+
#
|
200
|
+
# [Vips::Image] additional manipulations to perform
|
201
|
+
#
|
202
|
+
def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil, combine_options: {})
|
203
|
+
width, height = resolve_dimensions(width, height)
|
204
|
+
|
205
|
+
vips! do |builder|
|
206
|
+
builder.resize_and_pad(width, height, background: background, gravity: gravity, alpha: alpha)
|
207
|
+
.apply(combine_options)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
##
|
212
|
+
# Returns the width of the image in pixels.
|
213
|
+
#
|
214
|
+
# === Returns
|
215
|
+
#
|
216
|
+
# [Integer] the image's width in pixels
|
217
|
+
#
|
218
|
+
def width
|
219
|
+
vips_image.width
|
220
|
+
end
|
221
|
+
|
222
|
+
##
|
223
|
+
# Returns the height of the image in pixels.
|
224
|
+
#
|
225
|
+
# === Returns
|
226
|
+
#
|
227
|
+
# [Integer] the image's height in pixels
|
228
|
+
#
|
229
|
+
def height
|
230
|
+
vips_image.height
|
231
|
+
end
|
232
|
+
|
233
|
+
# Process the image with vip, using the ImageProcessing gem. This
|
234
|
+
# method will build a "convert" vips command and execute it on the
|
235
|
+
# current image.
|
236
|
+
#
|
237
|
+
# === Gotcha
|
238
|
+
#
|
239
|
+
# This method assumes that the object responds to +current_path+.
|
240
|
+
# Any class that this module is mixed into must have a +current_path+ method.
|
241
|
+
# CarrierWave::Uploader does, so you won't need to worry about this in
|
242
|
+
# most cases.
|
243
|
+
#
|
244
|
+
# === Yields
|
245
|
+
#
|
246
|
+
# [ImageProcessing::Builder] use it to define processing to be performed
|
247
|
+
#
|
248
|
+
# === Raises
|
249
|
+
#
|
250
|
+
# [CarrierWave::ProcessingError] if processing failed.
|
251
|
+
def vips!
|
252
|
+
builder = ImageProcessing::Vips.source(current_path)
|
253
|
+
builder = yield(builder)
|
254
|
+
|
255
|
+
result = builder.call
|
256
|
+
result.close
|
257
|
+
|
258
|
+
FileUtils.mv result.path, current_path
|
259
|
+
|
260
|
+
if File.extname(result.path) != File.extname(current_path)
|
261
|
+
move_to = current_path.chomp(File.extname(current_path)) + File.extname(result.path)
|
262
|
+
file.content_type = Marcel::Magic.by_path(move_to).try(:type)
|
263
|
+
file.move_to(move_to, permissions, directory_permissions)
|
264
|
+
end
|
265
|
+
rescue ::Vips::Error
|
266
|
+
message = I18n.translate(:"errors.messages.processing_error")
|
267
|
+
raise CarrierWave::ProcessingError, message
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
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
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def vips_image
|
280
|
+
::Vips::Image.new_from_buffer(read, "")
|
281
|
+
end
|
282
|
+
|
283
|
+
end # Vips
|
284
|
+
end # CarrierWave
|
@@ -1,12 +1,6 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require 'active_support/core_ext/string/multibyte'
|
3
|
-
|
4
|
-
begin
|
5
|
-
# Use mime/types/columnar if available, for reduced memory usage
|
6
|
-
require 'mime/types/columnar'
|
7
|
-
rescue LoadError
|
8
|
-
require 'mime/types'
|
9
|
-
end
|
3
|
+
require 'marcel'
|
10
4
|
|
11
5
|
module CarrierWave
|
12
6
|
|
@@ -19,6 +13,7 @@ module CarrierWave
|
|
19
13
|
# It's probably needlessly comprehensive and complex. Help is appreciated.
|
20
14
|
#
|
21
15
|
class SanitizedFile
|
16
|
+
include CarrierWave::Utilities::FileName
|
22
17
|
|
23
18
|
attr_reader :file
|
24
19
|
|
@@ -32,7 +27,7 @@ module CarrierWave
|
|
32
27
|
|
33
28
|
def initialize(file)
|
34
29
|
self.file = file
|
35
|
-
@content = nil
|
30
|
+
@content = @content_type = nil
|
36
31
|
end
|
37
32
|
|
38
33
|
##
|
@@ -44,7 +39,7 @@ module CarrierWave
|
|
44
39
|
#
|
45
40
|
def original_filename
|
46
41
|
return @original_filename if @original_filename
|
47
|
-
if @file
|
42
|
+
if @file && @file.respond_to?(:original_filename)
|
48
43
|
@file.original_filename
|
49
44
|
elsif path
|
50
45
|
File.basename(path)
|
@@ -64,29 +59,6 @@ module CarrierWave
|
|
64
59
|
|
65
60
|
alias_method :identifier, :filename
|
66
61
|
|
67
|
-
##
|
68
|
-
# Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
|
69
|
-
# this would return 'test'
|
70
|
-
#
|
71
|
-
# === Returns
|
72
|
-
#
|
73
|
-
# [String] the first part of the filename
|
74
|
-
#
|
75
|
-
def basename
|
76
|
-
split_extension(filename)[0] if filename
|
77
|
-
end
|
78
|
-
|
79
|
-
##
|
80
|
-
# Returns the file extension
|
81
|
-
#
|
82
|
-
# === Returns
|
83
|
-
#
|
84
|
-
# [String] the extension
|
85
|
-
#
|
86
|
-
def extension
|
87
|
-
split_extension(filename)[1] if filename
|
88
|
-
end
|
89
|
-
|
90
62
|
##
|
91
63
|
# Returns the file's size.
|
92
64
|
#
|
@@ -114,12 +86,11 @@ module CarrierWave
|
|
114
86
|
# [String, nil] the path where the file is located.
|
115
87
|
#
|
116
88
|
def path
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
end
|
89
|
+
return if @file.blank?
|
90
|
+
if is_path?
|
91
|
+
File.expand_path(@file)
|
92
|
+
elsif @file.respond_to?(:path) && !@file.path.blank?
|
93
|
+
File.expand_path(@file.path)
|
123
94
|
end
|
124
95
|
end
|
125
96
|
|
@@ -138,7 +109,7 @@ module CarrierWave
|
|
138
109
|
# [Boolean] whether the file is valid and has a non-zero size
|
139
110
|
#
|
140
111
|
def empty?
|
141
|
-
@file.nil? || self.size.nil? || (self.size.zero? && !
|
112
|
+
@file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
|
142
113
|
end
|
143
114
|
|
144
115
|
##
|
@@ -157,15 +128,21 @@ module CarrierWave
|
|
157
128
|
#
|
158
129
|
# [String] contents of the file
|
159
130
|
#
|
160
|
-
def read
|
131
|
+
def read(*args)
|
161
132
|
if @content
|
162
|
-
|
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
|
163
140
|
elsif is_path?
|
164
|
-
File.open(@file, "rb") {|file| file.read}
|
141
|
+
File.open(@file, "rb") {|file| file.read(*args)}
|
165
142
|
else
|
166
143
|
@file.try(:rewind)
|
167
|
-
@content = @file.read
|
168
|
-
@file.try(:close) unless @file.try(:closed?)
|
144
|
+
@content = @file.read(*args)
|
145
|
+
@file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
|
169
146
|
@content
|
170
147
|
end
|
171
148
|
end
|
@@ -186,13 +163,10 @@ module CarrierWave
|
|
186
163
|
mkdir!(new_path, directory_permissions)
|
187
164
|
move!(new_path)
|
188
165
|
chmod!(new_path, permissions)
|
189
|
-
|
190
|
-
self.file = {:tempfile => new_path, :filename => original_filename, :content_type => content_type}
|
191
|
-
else
|
192
|
-
self.file = {:tempfile => new_path, :content_type => content_type}
|
193
|
-
end
|
166
|
+
self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
|
194
167
|
self
|
195
168
|
end
|
169
|
+
|
196
170
|
##
|
197
171
|
# Helper to move file to new path.
|
198
172
|
#
|
@@ -224,7 +198,7 @@ module CarrierWave
|
|
224
198
|
mkdir!(new_path, directory_permissions)
|
225
199
|
copy!(new_path)
|
226
200
|
chmod!(new_path, permissions)
|
227
|
-
self.class.new({:
|
201
|
+
self.class.new({tempfile: new_path, content_type: declared_content_type})
|
228
202
|
end
|
229
203
|
|
230
204
|
##
|
@@ -265,12 +239,11 @@ module CarrierWave
|
|
265
239
|
# [String] the content type of the file
|
266
240
|
#
|
267
241
|
def content_type
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
end
|
242
|
+
@content_type ||=
|
243
|
+
identified_content_type ||
|
244
|
+
declared_content_type ||
|
245
|
+
guessed_safe_content_type ||
|
246
|
+
Marcel::MimeType::BINARY
|
274
247
|
end
|
275
248
|
|
276
249
|
##
|
@@ -301,11 +274,11 @@ module CarrierWave
|
|
301
274
|
if file.is_a?(Hash)
|
302
275
|
@file = file["tempfile"] || file[:tempfile]
|
303
276
|
@original_filename = file["filename"] || file[:filename]
|
304
|
-
@
|
277
|
+
@declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
|
305
278
|
else
|
306
279
|
@file = file
|
307
280
|
@original_filename = nil
|
308
|
-
@
|
281
|
+
@declared_content_type = nil
|
309
282
|
end
|
310
283
|
end
|
311
284
|
|
@@ -313,7 +286,7 @@ module CarrierWave
|
|
313
286
|
def mkdir!(path, directory_permissions)
|
314
287
|
options = {}
|
315
288
|
options[:mode] = directory_permissions if directory_permissions
|
316
|
-
FileUtils.mkdir_p(File.dirname(path), options) unless File.exist?(File.dirname(path))
|
289
|
+
FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
|
317
290
|
end
|
318
291
|
|
319
292
|
def chmod!(path, permissions)
|
@@ -322,28 +295,48 @@ module CarrierWave
|
|
322
295
|
|
323
296
|
# Sanitize the filename, to prevent hacking
|
324
297
|
def sanitize(name)
|
298
|
+
name = name.scrub
|
325
299
|
name = name.tr("\\", "/") # work-around for IE
|
326
300
|
name = File.basename(name)
|
327
|
-
name = name.gsub(sanitize_regexp,"_")
|
301
|
+
name = name.gsub(sanitize_regexp, "_")
|
328
302
|
name = "_#{name}" if name =~ /\A\.+\z/
|
329
|
-
name = "unnamed" if name.size
|
330
|
-
|
303
|
+
name = "unnamed" if name.size.zero?
|
304
|
+
name.mb_chars.to_s
|
331
305
|
end
|
332
306
|
|
333
|
-
def
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
/\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
|
338
|
-
]
|
339
|
-
|
340
|
-
extension_matchers.each do |regexp|
|
341
|
-
if filename =~ regexp
|
342
|
-
return $1, $2
|
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
|
343
311
|
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Guess content type from its file extension. Limit what to be returned to prevent spoofing.
|
315
|
+
def guessed_safe_content_type
|
316
|
+
return unless path
|
317
|
+
|
318
|
+
type = Marcel::Magic.by_path(original_filename).to_s
|
319
|
+
type if type.start_with?('text/') || type.start_with?('application/json')
|
320
|
+
end
|
321
|
+
|
322
|
+
def identified_content_type
|
323
|
+
with_io do |io|
|
324
|
+
Marcel::Magic.by_magic(io).try(:type)
|
344
325
|
end
|
345
|
-
|
326
|
+
rescue Errno::ENOENT
|
327
|
+
nil
|
346
328
|
end
|
347
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
|
340
|
+
end
|
348
341
|
end # SanitizedFile
|
349
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
|
@@ -17,7 +17,7 @@ module CarrierWave
|
|
17
17
|
#
|
18
18
|
# By default, store!() uses copy_to(), which operates by copying the file
|
19
19
|
# from the cache to the store, then deleting the file from the cache.
|
20
|
-
# If move_to_store() is
|
20
|
+
# If move_to_store() is overridden to return true, then store!() uses move_to(),
|
21
21
|
# which simply moves the file from cache to store. Useful for large files.
|
22
22
|
#
|
23
23
|
# === Parameters
|
@@ -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, '*'),
|
113
|
-
# generate_cache_id returns key
|
114
|
-
|
115
|
-
|
112
|
+
Dir.glob(::File.expand_path(::File.join(uploader.cache_dir, '*'), uploader.root)).each do |dir|
|
113
|
+
# generate_cache_id returns key formatted TIMEINT-PID(-COUNTER)-RND
|
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
|