carrierwave 1.3.4 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +278 -86
- data/lib/carrierwave/compatibility/paperclip.rb +4 -2
- data/lib/carrierwave/downloader/base.rb +101 -0
- data/lib/carrierwave/downloader/remote_file.rb +68 -0
- data/lib/carrierwave/locale/en.yml +9 -6
- data/lib/carrierwave/mount.rb +53 -61
- data/lib/carrierwave/mounter.rb +169 -77
- data/lib/carrierwave/orm/activerecord.rb +23 -58
- data/lib/carrierwave/processing/mini_magick.rb +137 -123
- data/lib/carrierwave/processing/rmagick.rb +47 -20
- data/lib/carrierwave/processing/vips.rb +315 -0
- data/lib/carrierwave/processing.rb +1 -0
- data/lib/carrierwave/sanitized_file.rb +91 -70
- data/lib/carrierwave/storage/abstract.rb +5 -5
- data/lib/carrierwave/storage/file.rb +6 -5
- data/lib/carrierwave/storage/fog.rb +114 -62
- data/lib/carrierwave/storage.rb +1 -0
- data/lib/carrierwave/test/matchers.rb +11 -7
- data/lib/carrierwave/uploader/cache.rb +40 -24
- data/lib/carrierwave/uploader/callbacks.rb +1 -1
- data/lib/carrierwave/uploader/configuration.rb +43 -20
- data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
- data/lib/carrierwave/uploader/content_type_denylist.rb +65 -0
- data/lib/carrierwave/uploader/dimension.rb +66 -0
- data/lib/carrierwave/uploader/download.rb +2 -123
- data/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
- data/lib/carrierwave/uploader/extension_denylist.rb +64 -0
- data/lib/carrierwave/uploader/file_size.rb +2 -2
- data/lib/carrierwave/uploader/mountable.rb +6 -0
- data/lib/carrierwave/uploader/processing.rb +45 -7
- data/lib/carrierwave/uploader/proxy.rb +17 -4
- data/lib/carrierwave/uploader/serialization.rb +1 -1
- data/lib/carrierwave/uploader/store.rb +73 -7
- data/lib/carrierwave/uploader/url.rb +7 -4
- data/lib/carrierwave/uploader/versions.rb +161 -111
- data/lib/carrierwave/uploader.rb +10 -17
- data/lib/carrierwave/utilities/file_name.rb +47 -0
- data/lib/carrierwave/utilities/uri.rb +14 -11
- data/lib/carrierwave/utilities.rb +1 -0
- data/lib/carrierwave/validations/active_model.rb +7 -9
- data/lib/carrierwave/version.rb +1 -1
- data/lib/carrierwave.rb +22 -17
- data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +13 -4
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +115 -44
- 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,315 @@
|
|
|
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
|
+
|
|
82
|
+
def crop(left, top, width, height)
|
|
83
|
+
process :crop => [left, top, width, height]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# Changes the image encoding format to the given format
|
|
89
|
+
#
|
|
90
|
+
# See https://libvips.github.io/libvips/API/current/using-cli.html#using-command-line-conversion
|
|
91
|
+
#
|
|
92
|
+
# === Parameters
|
|
93
|
+
#
|
|
94
|
+
# [format (#to_s)] an abbreviation of the format
|
|
95
|
+
#
|
|
96
|
+
# === Yields
|
|
97
|
+
#
|
|
98
|
+
# [Vips::Image] additional manipulations to perform
|
|
99
|
+
#
|
|
100
|
+
# === Examples
|
|
101
|
+
#
|
|
102
|
+
# image.convert(:png)
|
|
103
|
+
#
|
|
104
|
+
def convert(format, page=nil)
|
|
105
|
+
vips! do |builder|
|
|
106
|
+
builder = builder.convert(format)
|
|
107
|
+
builder = builder.loader(page: page) if page
|
|
108
|
+
builder
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
##
|
|
113
|
+
# Resize the image to fit within the specified dimensions while retaining
|
|
114
|
+
# the original aspect ratio. Will only resize the image if it is larger than the
|
|
115
|
+
# specified dimensions. The resulting image may be shorter or narrower than specified
|
|
116
|
+
# in the smaller dimension but will not be larger than the specified values.
|
|
117
|
+
#
|
|
118
|
+
# === Parameters
|
|
119
|
+
#
|
|
120
|
+
# [width (Integer)] the width to scale the image to
|
|
121
|
+
# [height (Integer)] the height to scale the image to
|
|
122
|
+
# [combine_options (Hash)] additional Vips options to apply before resizing
|
|
123
|
+
#
|
|
124
|
+
# === Yields
|
|
125
|
+
#
|
|
126
|
+
# [Vips::Image] additional manipulations to perform
|
|
127
|
+
#
|
|
128
|
+
def resize_to_limit(width, height, combine_options: {})
|
|
129
|
+
width, height = resolve_dimensions(width, height)
|
|
130
|
+
|
|
131
|
+
vips! do |builder|
|
|
132
|
+
builder.resize_to_limit(width, height)
|
|
133
|
+
.apply(combine_options)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
##
|
|
138
|
+
# Resize the image to fit within the specified dimensions while retaining
|
|
139
|
+
# the original aspect ratio. The image may be shorter or narrower than
|
|
140
|
+
# specified in the smaller dimension but will not be larger than the specified values.
|
|
141
|
+
#
|
|
142
|
+
# === Parameters
|
|
143
|
+
#
|
|
144
|
+
# [width (Integer)] the width to scale the image to
|
|
145
|
+
# [height (Integer)] the height to scale the image to
|
|
146
|
+
# [combine_options (Hash)] additional Vips options to apply before resizing
|
|
147
|
+
#
|
|
148
|
+
# === Yields
|
|
149
|
+
#
|
|
150
|
+
# [Vips::Image] additional manipulations to perform
|
|
151
|
+
#
|
|
152
|
+
def resize_to_fit(width, height, combine_options: {})
|
|
153
|
+
width, height = resolve_dimensions(width, height)
|
|
154
|
+
|
|
155
|
+
vips! do |builder|
|
|
156
|
+
builder.resize_to_fit(width, height)
|
|
157
|
+
.apply(combine_options)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
##
|
|
162
|
+
# Resize the image to fit within the specified dimensions while retaining
|
|
163
|
+
# the aspect ratio of the original image. If necessary, crop the image in the
|
|
164
|
+
# larger dimension.
|
|
165
|
+
#
|
|
166
|
+
# === Parameters
|
|
167
|
+
#
|
|
168
|
+
# [width (Integer)] the width to scale the image to
|
|
169
|
+
# [height (Integer)] the height to scale the image to
|
|
170
|
+
# [combine_options (Hash)] additional vips options to apply before resizing
|
|
171
|
+
#
|
|
172
|
+
# === Yields
|
|
173
|
+
#
|
|
174
|
+
# [Vips::Image] additional manipulations to perform
|
|
175
|
+
#
|
|
176
|
+
def resize_to_fill(width, height, _gravity = nil, combine_options: {})
|
|
177
|
+
width, height = resolve_dimensions(width, height)
|
|
178
|
+
|
|
179
|
+
vips! do |builder|
|
|
180
|
+
builder.resize_to_fill(width, height).apply(combine_options)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
##
|
|
185
|
+
# Resize the image to fit within the specified dimensions while retaining
|
|
186
|
+
# the original aspect ratio. If necessary, will pad the remaining area
|
|
187
|
+
# with the given color, which defaults to transparent (for gif and png,
|
|
188
|
+
# white for jpeg).
|
|
189
|
+
#
|
|
190
|
+
# See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsCompassDirection
|
|
191
|
+
# for gravity options.
|
|
192
|
+
#
|
|
193
|
+
# === Parameters
|
|
194
|
+
#
|
|
195
|
+
# [width (Integer)] the width to scale the image to
|
|
196
|
+
# [height (Integer)] the height to scale the image to
|
|
197
|
+
# [background (List, nil)] the color of the background as a RGB, like [0, 255, 255], nil indicates transparent
|
|
198
|
+
# [gravity (String)] how to position the image
|
|
199
|
+
# [alpha (Boolean, nil)] pad the image with the alpha channel if supported
|
|
200
|
+
# [combine_options (Hash)] additional vips options to apply before resizing
|
|
201
|
+
#
|
|
202
|
+
# === Yields
|
|
203
|
+
#
|
|
204
|
+
# [Vips::Image] additional manipulations to perform
|
|
205
|
+
#
|
|
206
|
+
def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil, combine_options: {})
|
|
207
|
+
width, height = resolve_dimensions(width, height)
|
|
208
|
+
|
|
209
|
+
vips! do |builder|
|
|
210
|
+
builder.resize_and_pad(width, height, background: background, gravity: gravity, alpha: alpha)
|
|
211
|
+
.apply(combine_options)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
##
|
|
216
|
+
# Crop the image to the contents of a box positioned at [left] and [top], with the dimensions given
|
|
217
|
+
# by [width] and [height]. The original image bottom/right edge is preserved if the cropping box falls
|
|
218
|
+
# outside the image bounds.
|
|
219
|
+
#
|
|
220
|
+
# === Parameters
|
|
221
|
+
#
|
|
222
|
+
# [left (integer)] left edge of area to extract
|
|
223
|
+
# [top (integer)] top edge of area to extract
|
|
224
|
+
# [width (Integer)] width of area to extract
|
|
225
|
+
# [height (Integer)] height of area to extract
|
|
226
|
+
#
|
|
227
|
+
# === Yields
|
|
228
|
+
#
|
|
229
|
+
# [Vips::Image] additional manipulations to perform
|
|
230
|
+
#
|
|
231
|
+
def crop(left, top, width, height, combine_options: {})
|
|
232
|
+
width, height = resolve_dimensions(width, height)
|
|
233
|
+
width = vips_image.width - left if width + left > vips_image.width
|
|
234
|
+
height = vips_image.height - top if height + top > vips_image.height
|
|
235
|
+
|
|
236
|
+
vips! do |builder|
|
|
237
|
+
builder.crop(left, top, width, height)
|
|
238
|
+
.apply(combine_options)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
##
|
|
243
|
+
# Returns the width of the image in pixels.
|
|
244
|
+
#
|
|
245
|
+
# === Returns
|
|
246
|
+
#
|
|
247
|
+
# [Integer] the image's width in pixels
|
|
248
|
+
#
|
|
249
|
+
def width
|
|
250
|
+
vips_image.width
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
##
|
|
254
|
+
# Returns the height of the image in pixels.
|
|
255
|
+
#
|
|
256
|
+
# === Returns
|
|
257
|
+
#
|
|
258
|
+
# [Integer] the image's height in pixels
|
|
259
|
+
#
|
|
260
|
+
def height
|
|
261
|
+
vips_image.height
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Process the image with vip, using the ImageProcessing gem. This
|
|
265
|
+
# method will build a "convert" vips command and execute it on the
|
|
266
|
+
# current image.
|
|
267
|
+
#
|
|
268
|
+
# === Gotcha
|
|
269
|
+
#
|
|
270
|
+
# This method assumes that the object responds to +current_path+.
|
|
271
|
+
# Any class that this module is mixed into must have a +current_path+ method.
|
|
272
|
+
# CarrierWave::Uploader does, so you won't need to worry about this in
|
|
273
|
+
# most cases.
|
|
274
|
+
#
|
|
275
|
+
# === Yields
|
|
276
|
+
#
|
|
277
|
+
# [ImageProcessing::Builder] use it to define processing to be performed
|
|
278
|
+
#
|
|
279
|
+
# === Raises
|
|
280
|
+
#
|
|
281
|
+
# [CarrierWave::ProcessingError] if processing failed.
|
|
282
|
+
def vips!
|
|
283
|
+
builder = ImageProcessing::Vips.source(current_path)
|
|
284
|
+
builder = yield(builder)
|
|
285
|
+
|
|
286
|
+
result = builder.call
|
|
287
|
+
result.close
|
|
288
|
+
|
|
289
|
+
FileUtils.mv result.path, current_path
|
|
290
|
+
|
|
291
|
+
if File.extname(result.path) != File.extname(current_path)
|
|
292
|
+
move_to = current_path.chomp(File.extname(current_path)) + File.extname(result.path)
|
|
293
|
+
file.content_type = Marcel::Magic.by_path(move_to).try(:type)
|
|
294
|
+
file.move_to(move_to, permissions, directory_permissions)
|
|
295
|
+
end
|
|
296
|
+
rescue ::Vips::Error
|
|
297
|
+
message = I18n.translate(:"errors.messages.processing_error")
|
|
298
|
+
raise CarrierWave::ProcessingError, message
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
private
|
|
302
|
+
|
|
303
|
+
def resolve_dimensions(*dimensions)
|
|
304
|
+
dimensions.map do |value|
|
|
305
|
+
next value unless value.instance_of?(Proc)
|
|
306
|
+
value.arity >= 1 ? value.call(self) : value.call
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def vips_image
|
|
311
|
+
::Vips::Image.new_from_buffer(read, "")
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
end # Vips
|
|
315
|
+
end # CarrierWave
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
require 'pathname'
|
|
2
|
-
require '
|
|
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
|
|
2
|
+
require 'marcel'
|
|
10
3
|
|
|
11
4
|
module CarrierWave
|
|
12
5
|
|
|
@@ -19,6 +12,7 @@ module CarrierWave
|
|
|
19
12
|
# It's probably needlessly comprehensive and complex. Help is appreciated.
|
|
20
13
|
#
|
|
21
14
|
class SanitizedFile
|
|
15
|
+
include CarrierWave::Utilities::FileName
|
|
22
16
|
|
|
23
17
|
attr_reader :file
|
|
24
18
|
|
|
@@ -32,7 +26,7 @@ module CarrierWave
|
|
|
32
26
|
|
|
33
27
|
def initialize(file)
|
|
34
28
|
self.file = file
|
|
35
|
-
@content = nil
|
|
29
|
+
@content = @content_type = nil
|
|
36
30
|
end
|
|
37
31
|
|
|
38
32
|
##
|
|
@@ -44,7 +38,7 @@ module CarrierWave
|
|
|
44
38
|
#
|
|
45
39
|
def original_filename
|
|
46
40
|
return @original_filename if @original_filename
|
|
47
|
-
if @file
|
|
41
|
+
if @file && @file.respond_to?(:original_filename)
|
|
48
42
|
@file.original_filename
|
|
49
43
|
elsif path
|
|
50
44
|
File.basename(path)
|
|
@@ -64,29 +58,6 @@ module CarrierWave
|
|
|
64
58
|
|
|
65
59
|
alias_method :identifier, :filename
|
|
66
60
|
|
|
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
61
|
##
|
|
91
62
|
# Returns the file's size.
|
|
92
63
|
#
|
|
@@ -137,7 +108,7 @@ module CarrierWave
|
|
|
137
108
|
# [Boolean] whether the file is valid and has a non-zero size
|
|
138
109
|
#
|
|
139
110
|
def empty?
|
|
140
|
-
@file.nil? || self.size.nil? || (self.size.zero? && !
|
|
111
|
+
@file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
|
|
141
112
|
end
|
|
142
113
|
|
|
143
114
|
##
|
|
@@ -156,19 +127,36 @@ module CarrierWave
|
|
|
156
127
|
#
|
|
157
128
|
# [String] contents of the file
|
|
158
129
|
#
|
|
159
|
-
def read
|
|
160
|
-
if
|
|
161
|
-
@content
|
|
162
|
-
|
|
163
|
-
|
|
130
|
+
def read(*args)
|
|
131
|
+
if args.empty?
|
|
132
|
+
if @content
|
|
133
|
+
@content
|
|
134
|
+
elsif is_path?
|
|
135
|
+
File.open(@file, "rb") {|file| file.read }
|
|
136
|
+
else
|
|
137
|
+
@file.try(:rewind)
|
|
138
|
+
@content = @file.read
|
|
139
|
+
@file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
|
|
140
|
+
@content
|
|
141
|
+
end
|
|
164
142
|
else
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
143
|
+
if is_path?
|
|
144
|
+
@file = File.open(path, "rb")
|
|
145
|
+
elsif @file.is_a?(CarrierWave::Uploader::Base)
|
|
146
|
+
@file = StringIO.new(@file.read)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
@file.read(*args)
|
|
169
150
|
end
|
|
170
151
|
end
|
|
171
152
|
|
|
153
|
+
##
|
|
154
|
+
# Rewinds the underlying file.
|
|
155
|
+
#
|
|
156
|
+
def rewind
|
|
157
|
+
@file.rewind if @file.respond_to?(:rewind)
|
|
158
|
+
end
|
|
159
|
+
|
|
172
160
|
##
|
|
173
161
|
# Moves the file to the given path
|
|
174
162
|
#
|
|
@@ -185,13 +173,10 @@ module CarrierWave
|
|
|
185
173
|
mkdir!(new_path, directory_permissions)
|
|
186
174
|
move!(new_path)
|
|
187
175
|
chmod!(new_path, permissions)
|
|
188
|
-
|
|
189
|
-
self.file = {:tempfile => new_path, :filename => original_filename, :content_type => content_type}
|
|
190
|
-
else
|
|
191
|
-
self.file = {:tempfile => new_path, :content_type => content_type}
|
|
192
|
-
end
|
|
176
|
+
self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
|
|
193
177
|
self
|
|
194
178
|
end
|
|
179
|
+
|
|
195
180
|
##
|
|
196
181
|
# Helper to move file to new path.
|
|
197
182
|
#
|
|
@@ -223,7 +208,7 @@ module CarrierWave
|
|
|
223
208
|
mkdir!(new_path, directory_permissions)
|
|
224
209
|
copy!(new_path)
|
|
225
210
|
chmod!(new_path, permissions)
|
|
226
|
-
self.class.new({:
|
|
211
|
+
self.class.new({tempfile: new_path, content_type: declared_content_type})
|
|
227
212
|
end
|
|
228
213
|
|
|
229
214
|
##
|
|
@@ -264,12 +249,11 @@ module CarrierWave
|
|
|
264
249
|
# [String] the content type of the file
|
|
265
250
|
#
|
|
266
251
|
def content_type
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
end
|
|
252
|
+
@content_type ||=
|
|
253
|
+
identified_content_type ||
|
|
254
|
+
declared_content_type ||
|
|
255
|
+
guessed_safe_content_type ||
|
|
256
|
+
Marcel::MimeType::BINARY
|
|
273
257
|
end
|
|
274
258
|
|
|
275
259
|
##
|
|
@@ -300,11 +284,11 @@ module CarrierWave
|
|
|
300
284
|
if file.is_a?(Hash)
|
|
301
285
|
@file = file["tempfile"] || file[:tempfile]
|
|
302
286
|
@original_filename = file["filename"] || file[:filename]
|
|
303
|
-
@
|
|
287
|
+
@declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
|
|
304
288
|
else
|
|
305
289
|
@file = file
|
|
306
290
|
@original_filename = nil
|
|
307
|
-
@
|
|
291
|
+
@declared_content_type = nil
|
|
308
292
|
end
|
|
309
293
|
end
|
|
310
294
|
|
|
@@ -321,28 +305,65 @@ module CarrierWave
|
|
|
321
305
|
|
|
322
306
|
# Sanitize the filename, to prevent hacking
|
|
323
307
|
def sanitize(name)
|
|
308
|
+
name = name.scrub
|
|
324
309
|
name = name.tr("\\", "/") # work-around for IE
|
|
325
310
|
name = File.basename(name)
|
|
326
|
-
name = name.gsub(sanitize_regexp,"_")
|
|
311
|
+
name = name.gsub(sanitize_regexp, "_")
|
|
327
312
|
name = "_#{name}" if name =~ /\A\.+\z/
|
|
328
|
-
name = "unnamed" if name.size
|
|
329
|
-
|
|
313
|
+
name = "unnamed" if name.size.zero?
|
|
314
|
+
name.to_s
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def declared_content_type
|
|
318
|
+
@declared_content_type ||
|
|
319
|
+
if @file.respond_to?(:content_type) && @file.content_type
|
|
320
|
+
Marcel::MimeType.for(declared_type: first_declared_content_type(@file.content_type))
|
|
321
|
+
end
|
|
330
322
|
end
|
|
331
323
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
]
|
|
324
|
+
# Clients occasionally send more than one MIME type in a single header
|
|
325
|
+
# (e.g. "image/png; text/html" or "image/png, text/html"). Marcel 2 validates
|
|
326
|
+
# the declared type against the RFC grammar and rejects such values, so pick
|
|
327
|
+
# the first media type ourselves before handing it over.
|
|
328
|
+
def first_declared_content_type(content_type)
|
|
329
|
+
content_type.to_s.strip.split(/[;,\s]/, 2).first
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Guess content type from its file extension. Limit what to be returned to prevent spoofing.
|
|
333
|
+
def guessed_safe_content_type
|
|
334
|
+
return unless path
|
|
335
|
+
|
|
336
|
+
type = Marcel::Magic.by_path(original_filename).to_s
|
|
337
|
+
type if type.start_with?('text/') || type.start_with?('application/json')
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def identified_content_type
|
|
341
|
+
with_io do |io|
|
|
342
|
+
mimetype_by_magic = Marcel::Magic.by_magic(io)
|
|
343
|
+
mimetype_by_path = Marcel::Magic.by_path(path)
|
|
338
344
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
345
|
+
return nil if mimetype_by_magic.nil?
|
|
346
|
+
|
|
347
|
+
if mimetype_by_path&.child_of?(mimetype_by_magic.type)
|
|
348
|
+
mimetype_by_path.type
|
|
349
|
+
else
|
|
350
|
+
mimetype_by_magic.type
|
|
342
351
|
end
|
|
343
352
|
end
|
|
344
|
-
|
|
353
|
+
rescue Errno::ENOENT
|
|
354
|
+
nil
|
|
345
355
|
end
|
|
346
356
|
|
|
357
|
+
def with_io(&block)
|
|
358
|
+
if file.is_a?(IO)
|
|
359
|
+
begin
|
|
360
|
+
yield file
|
|
361
|
+
ensure
|
|
362
|
+
file.try(:rewind)
|
|
363
|
+
end
|
|
364
|
+
elsif path
|
|
365
|
+
File.open(path, &block)
|
|
366
|
+
end
|
|
367
|
+
end
|
|
347
368
|
end # SanitizedFile
|
|
348
369
|
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
|