carrierwave 0.11.2 → 3.1.2
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 +5 -5
- data/README.md +495 -173
- data/lib/carrierwave/compatibility/paperclip.rb +4 -4
- data/lib/carrierwave/downloader/base.rb +101 -0
- data/lib/carrierwave/downloader/remote_file.rb +68 -0
- data/lib/carrierwave/error.rb +1 -0
- data/lib/carrierwave/locale/en.yml +11 -5
- data/lib/carrierwave/mount.rb +217 -182
- data/lib/carrierwave/mounter.rb +257 -0
- data/lib/carrierwave/orm/activerecord.rb +29 -35
- data/lib/carrierwave/processing/mini_magick.rb +169 -84
- data/lib/carrierwave/processing/rmagick.rb +107 -25
- data/lib/carrierwave/processing/vips.rb +315 -0
- data/lib/carrierwave/processing.rb +1 -1
- data/lib/carrierwave/sanitized_file.rb +105 -87
- data/lib/carrierwave/storage/abstract.rb +16 -3
- data/lib/carrierwave/storage/file.rb +71 -3
- data/lib/carrierwave/storage/fog.rb +228 -57
- data/lib/carrierwave/storage.rb +1 -9
- data/lib/carrierwave/test/matchers.rb +88 -19
- data/lib/carrierwave/uploader/cache.rb +75 -45
- data/lib/carrierwave/uploader/callbacks.rb +1 -3
- data/lib/carrierwave/uploader/configuration.rb +84 -16
- data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
- data/lib/carrierwave/uploader/content_type_denylist.rb +62 -0
- data/lib/carrierwave/uploader/default_url.rb +3 -5
- data/lib/carrierwave/uploader/dimension.rb +66 -0
- data/lib/carrierwave/uploader/download.rb +4 -74
- 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 +43 -0
- data/lib/carrierwave/uploader/mountable.rb +13 -8
- data/lib/carrierwave/uploader/processing.rb +51 -13
- data/lib/carrierwave/uploader/proxy.rb +20 -9
- data/lib/carrierwave/uploader/remove.rb +0 -2
- data/lib/carrierwave/uploader/serialization.rb +2 -4
- data/lib/carrierwave/uploader/store.rb +85 -28
- data/lib/carrierwave/uploader/url.rb +8 -7
- data/lib/carrierwave/uploader/versions.rb +175 -125
- data/lib/carrierwave/uploader.rb +12 -10
- data/lib/carrierwave/utilities/file_name.rb +47 -0
- data/lib/carrierwave/utilities/uri.rb +14 -12
- data/lib/carrierwave/utilities.rb +1 -3
- data/lib/carrierwave/validations/active_model.rb +7 -11
- data/lib/carrierwave/version.rb +1 -1
- data/lib/carrierwave.rb +48 -21
- data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +8 -11
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +124 -86
- data/lib/carrierwave/locale/cs.yml +0 -11
- data/lib/carrierwave/locale/de.yml +0 -11
- data/lib/carrierwave/locale/el.yml +0 -11
- data/lib/carrierwave/locale/es.yml +0 -11
- data/lib/carrierwave/locale/fr.yml +0 -11
- data/lib/carrierwave/locale/ja.yml +0 -11
- data/lib/carrierwave/locale/nb.yml +0 -11
- data/lib/carrierwave/locale/nl.yml +0 -11
- data/lib/carrierwave/locale/pl.yml +0 -11
- data/lib/carrierwave/locale/pt-BR.yml +0 -11
- data/lib/carrierwave/locale/pt-PT.yml +0 -11
- data/lib/carrierwave/locale/ru.yml +0 -11
- data/lib/carrierwave/locale/sk.yml +0 -11
- data/lib/carrierwave/locale/tr.yml +0 -11
- data/lib/carrierwave/processing/mime_types.rb +0 -74
- 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 -47
- data/lib/carrierwave/uploader/extension_whitelist.rb +0 -49
- data/lib/carrierwave/utilities/deprecation.rb +0 -18
@@ -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,9 +1,5 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
require 'pathname'
|
4
|
-
require '
|
5
|
-
require 'mime/types'
|
6
|
-
require 'mimemagic'
|
2
|
+
require 'marcel'
|
7
3
|
|
8
4
|
module CarrierWave
|
9
5
|
|
@@ -16,19 +12,21 @@ module CarrierWave
|
|
16
12
|
# It's probably needlessly comprehensive and complex. Help is appreciated.
|
17
13
|
#
|
18
14
|
class SanitizedFile
|
15
|
+
include CarrierWave::Utilities::FileName
|
19
16
|
|
20
|
-
|
17
|
+
attr_reader :file
|
21
18
|
|
22
19
|
class << self
|
23
20
|
attr_writer :sanitize_regexp
|
24
21
|
|
25
22
|
def sanitize_regexp
|
26
|
-
@sanitize_regexp ||= /[^
|
23
|
+
@sanitize_regexp ||= /[^[:word:]\.\-\+]/
|
27
24
|
end
|
28
25
|
end
|
29
26
|
|
30
27
|
def initialize(file)
|
31
28
|
self.file = file
|
29
|
+
@content = @content_type = nil
|
32
30
|
end
|
33
31
|
|
34
32
|
##
|
@@ -40,7 +38,7 @@ module CarrierWave
|
|
40
38
|
#
|
41
39
|
def original_filename
|
42
40
|
return @original_filename if @original_filename
|
43
|
-
if @file
|
41
|
+
if @file && @file.respond_to?(:original_filename)
|
44
42
|
@file.original_filename
|
45
43
|
elsif path
|
46
44
|
File.basename(path)
|
@@ -60,29 +58,6 @@ module CarrierWave
|
|
60
58
|
|
61
59
|
alias_method :identifier, :filename
|
62
60
|
|
63
|
-
##
|
64
|
-
# Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
|
65
|
-
# this would return 'test'
|
66
|
-
#
|
67
|
-
# === Returns
|
68
|
-
#
|
69
|
-
# [String] the first part of the filename
|
70
|
-
#
|
71
|
-
def basename
|
72
|
-
split_extension(filename)[0] if filename
|
73
|
-
end
|
74
|
-
|
75
|
-
##
|
76
|
-
# Returns the file extension
|
77
|
-
#
|
78
|
-
# === Returns
|
79
|
-
#
|
80
|
-
# [String] the extension
|
81
|
-
#
|
82
|
-
def extension
|
83
|
-
split_extension(filename)[1] if filename
|
84
|
-
end
|
85
|
-
|
86
61
|
##
|
87
62
|
# Returns the file's size.
|
88
63
|
#
|
@@ -110,12 +85,11 @@ module CarrierWave
|
|
110
85
|
# [String, nil] the path where the file is located.
|
111
86
|
#
|
112
87
|
def path
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
end
|
88
|
+
return if @file.blank?
|
89
|
+
if is_path?
|
90
|
+
File.expand_path(@file)
|
91
|
+
elsif @file.respond_to?(:path) && !@file.path.blank?
|
92
|
+
File.expand_path(@file.path)
|
119
93
|
end
|
120
94
|
end
|
121
95
|
|
@@ -134,7 +108,7 @@ module CarrierWave
|
|
134
108
|
# [Boolean] whether the file is valid and has a non-zero size
|
135
109
|
#
|
136
110
|
def empty?
|
137
|
-
@file.nil? || self.size.nil? || (self.size.zero? && !
|
111
|
+
@file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
|
138
112
|
end
|
139
113
|
|
140
114
|
##
|
@@ -143,8 +117,7 @@ module CarrierWave
|
|
143
117
|
# [Boolean] Whether the file exists
|
144
118
|
#
|
145
119
|
def exists?
|
146
|
-
|
147
|
-
return false
|
120
|
+
self.path.present? && File.exist?(self.path)
|
148
121
|
end
|
149
122
|
|
150
123
|
##
|
@@ -154,19 +127,36 @@ module CarrierWave
|
|
154
127
|
#
|
155
128
|
# [String] contents of the file
|
156
129
|
#
|
157
|
-
def read
|
158
|
-
if
|
159
|
-
@content
|
160
|
-
|
161
|
-
|
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
|
162
142
|
else
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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)
|
167
150
|
end
|
168
151
|
end
|
169
152
|
|
153
|
+
##
|
154
|
+
# Rewinds the underlying file.
|
155
|
+
#
|
156
|
+
def rewind
|
157
|
+
@file.rewind if @file.respond_to?(:rewind)
|
158
|
+
end
|
159
|
+
|
170
160
|
##
|
171
161
|
# Moves the file to the given path
|
172
162
|
#
|
@@ -176,19 +166,26 @@ module CarrierWave
|
|
176
166
|
# [permissions (Integer)] permissions to set on the file in its new location.
|
177
167
|
# [directory_permissions (Integer)] permissions to set on created directories.
|
178
168
|
#
|
179
|
-
def move_to(new_path, permissions=nil, directory_permissions=nil)
|
169
|
+
def move_to(new_path, permissions=nil, directory_permissions=nil, keep_filename=false)
|
180
170
|
return if self.empty?
|
181
171
|
new_path = File.expand_path(new_path)
|
182
172
|
|
183
173
|
mkdir!(new_path, directory_permissions)
|
174
|
+
move!(new_path)
|
175
|
+
chmod!(new_path, permissions)
|
176
|
+
self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
|
177
|
+
self
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Helper to move file to new path.
|
182
|
+
#
|
183
|
+
def move!(new_path)
|
184
184
|
if exists?
|
185
|
-
FileUtils.mv(path, new_path) unless new_path
|
185
|
+
FileUtils.mv(path, new_path) unless File.identical?(new_path, path)
|
186
186
|
else
|
187
187
|
File.open(new_path, "wb") { |f| f.write(read) }
|
188
188
|
end
|
189
|
-
chmod!(new_path, permissions)
|
190
|
-
self.file = new_path
|
191
|
-
self
|
192
189
|
end
|
193
190
|
|
194
191
|
##
|
@@ -209,13 +206,20 @@ module CarrierWave
|
|
209
206
|
new_path = File.expand_path(new_path)
|
210
207
|
|
211
208
|
mkdir!(new_path, directory_permissions)
|
209
|
+
copy!(new_path)
|
210
|
+
chmod!(new_path, permissions)
|
211
|
+
self.class.new({tempfile: new_path, content_type: declared_content_type})
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# Helper to create copy of file in new path.
|
216
|
+
#
|
217
|
+
def copy!(new_path)
|
212
218
|
if exists?
|
213
219
|
FileUtils.cp(path, new_path) unless new_path == path
|
214
220
|
else
|
215
221
|
File.open(new_path, "wb") { |f| f.write(read) }
|
216
222
|
end
|
217
|
-
chmod!(new_path, permissions)
|
218
|
-
self.class.new({:tempfile => new_path, :content_type => content_type})
|
219
223
|
end
|
220
224
|
|
221
225
|
##
|
@@ -246,9 +250,10 @@ module CarrierWave
|
|
246
250
|
#
|
247
251
|
def content_type
|
248
252
|
@content_type ||=
|
249
|
-
|
250
|
-
|
251
|
-
|
253
|
+
identified_content_type ||
|
254
|
+
declared_content_type ||
|
255
|
+
guessed_safe_content_type ||
|
256
|
+
Marcel::MimeType::BINARY
|
252
257
|
end
|
253
258
|
|
254
259
|
##
|
@@ -279,11 +284,11 @@ module CarrierWave
|
|
279
284
|
if file.is_a?(Hash)
|
280
285
|
@file = file["tempfile"] || file[:tempfile]
|
281
286
|
@original_filename = file["filename"] || file[:filename]
|
282
|
-
@
|
287
|
+
@declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
|
283
288
|
else
|
284
289
|
@file = file
|
285
290
|
@original_filename = nil
|
286
|
-
@
|
291
|
+
@declared_content_type = nil
|
287
292
|
end
|
288
293
|
end
|
289
294
|
|
@@ -291,7 +296,7 @@ module CarrierWave
|
|
291
296
|
def mkdir!(path, directory_permissions)
|
292
297
|
options = {}
|
293
298
|
options[:mode] = directory_permissions if directory_permissions
|
294
|
-
FileUtils.mkdir_p(File.dirname(path), options) unless File.
|
299
|
+
FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
|
295
300
|
end
|
296
301
|
|
297
302
|
def chmod!(path, permissions)
|
@@ -300,44 +305,57 @@ module CarrierWave
|
|
300
305
|
|
301
306
|
# Sanitize the filename, to prevent hacking
|
302
307
|
def sanitize(name)
|
303
|
-
name = name.
|
308
|
+
name = name.scrub
|
309
|
+
name = name.tr("\\", "/") # work-around for IE
|
304
310
|
name = File.basename(name)
|
305
|
-
name = name.gsub(sanitize_regexp,"_")
|
311
|
+
name = name.gsub(sanitize_regexp, "_")
|
306
312
|
name = "_#{name}" if name =~ /\A\.+\z/
|
307
|
-
name = "unnamed" if name.size
|
308
|
-
|
313
|
+
name = "unnamed" if name.size.zero?
|
314
|
+
name.to_s
|
309
315
|
end
|
310
316
|
|
311
|
-
def
|
312
|
-
|
313
|
-
@file.content_type.
|
314
|
-
|
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: @file.content_type.to_s.chomp)
|
321
|
+
end
|
315
322
|
end
|
316
323
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
nil
|
321
|
-
end
|
324
|
+
# Guess content type from its file extension. Limit what to be returned to prevent spoofing.
|
325
|
+
def guessed_safe_content_type
|
326
|
+
return unless path
|
322
327
|
|
323
|
-
|
324
|
-
|
328
|
+
type = Marcel::Magic.by_path(original_filename).to_s
|
329
|
+
type if type.start_with?('text/') || type.start_with?('application/json')
|
325
330
|
end
|
326
331
|
|
327
|
-
def
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
332
|
+
def identified_content_type
|
333
|
+
with_io do |io|
|
334
|
+
mimetype_by_magic = Marcel::Magic.by_magic(io)
|
335
|
+
mimetype_by_path = Marcel::Magic.by_path(path)
|
336
|
+
|
337
|
+
return nil if mimetype_by_magic.nil?
|
333
338
|
|
334
|
-
|
335
|
-
|
336
|
-
|
339
|
+
if mimetype_by_path&.child_of?(mimetype_by_magic.type)
|
340
|
+
mimetype_by_path.type
|
341
|
+
else
|
342
|
+
mimetype_by_magic.type
|
337
343
|
end
|
338
344
|
end
|
339
|
-
|
345
|
+
rescue Errno::ENOENT
|
346
|
+
nil
|
340
347
|
end
|
341
348
|
|
349
|
+
def with_io(&block)
|
350
|
+
if file.is_a?(IO)
|
351
|
+
begin
|
352
|
+
yield file
|
353
|
+
ensure
|
354
|
+
file.try(:rewind)
|
355
|
+
end
|
356
|
+
elsif path
|
357
|
+
File.open(path, &block)
|
358
|
+
end
|
359
|
+
end
|
342
360
|
end # SanitizedFile
|
343
361
|
end # CarrierWave
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module CarrierWave
|
4
2
|
module Storage
|
5
3
|
|
@@ -16,7 +14,7 @@ module CarrierWave
|
|
16
14
|
end
|
17
15
|
|
18
16
|
def identifier
|
19
|
-
uploader.
|
17
|
+
uploader.deduplicated_filename
|
20
18
|
end
|
21
19
|
|
22
20
|
def store!(file)
|
@@ -25,6 +23,21 @@ module CarrierWave
|
|
25
23
|
def retrieve!(identifier)
|
26
24
|
end
|
27
25
|
|
26
|
+
def cache!(new_file)
|
27
|
+
raise NotImplementedError, "Need to implement #cache! if you want to use #{self.class.name} as a cache storage."
|
28
|
+
end
|
29
|
+
|
30
|
+
def retrieve_from_cache!(identifier)
|
31
|
+
raise NotImplementedError, "Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage."
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete_dir!(path)
|
35
|
+
raise NotImplementedError, "Need to implement #delete_dir! if you want to use #{self.class.name} as a cache storage."
|
36
|
+
end
|
37
|
+
|
38
|
+
def clean_cache!(seconds)
|
39
|
+
raise NotImplementedError, "Need to implement #clean_cache! if you want to use #{self.class.name} as a cache storage."
|
40
|
+
end
|
28
41
|
end # Abstract
|
29
42
|
end # Storage
|
30
43
|
end # CarrierWave
|