carrierwave 0.11.2 → 3.0.3
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 +5 -5
- data/README.md +452 -178
- 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 +212 -182
- data/lib/carrierwave/mounter.rb +255 -0
- data/lib/carrierwave/orm/activerecord.rb +22 -33
- data/lib/carrierwave/processing/mini_magick.rb +140 -84
- data/lib/carrierwave/processing/rmagick.rb +72 -21
- data/lib/carrierwave/processing/vips.rb +284 -0
- data/lib/carrierwave/processing.rb +1 -1
- data/lib/carrierwave/sanitized_file.rb +83 -84
- data/lib/carrierwave/storage/abstract.rb +16 -3
- data/lib/carrierwave/storage/file.rb +71 -3
- data/lib/carrierwave/storage/fog.rb +215 -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 +80 -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 +48 -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 +59 -28
- data/lib/carrierwave/uploader/url.rb +8 -7
- data/lib/carrierwave/uploader/versions.rb +170 -122
- 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 +39 -21
- data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -9
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +132 -80
- 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
@@ -1,9 +1,6 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
require 'pathname'
|
4
2
|
require 'active_support/core_ext/string/multibyte'
|
5
|
-
require '
|
6
|
-
require 'mimemagic'
|
3
|
+
require 'marcel'
|
7
4
|
|
8
5
|
module CarrierWave
|
9
6
|
|
@@ -16,19 +13,21 @@ module CarrierWave
|
|
16
13
|
# It's probably needlessly comprehensive and complex. Help is appreciated.
|
17
14
|
#
|
18
15
|
class SanitizedFile
|
16
|
+
include CarrierWave::Utilities::FileName
|
19
17
|
|
20
|
-
|
18
|
+
attr_reader :file
|
21
19
|
|
22
20
|
class << self
|
23
21
|
attr_writer :sanitize_regexp
|
24
22
|
|
25
23
|
def sanitize_regexp
|
26
|
-
@sanitize_regexp ||= /[^
|
24
|
+
@sanitize_regexp ||= /[^[:word:]\.\-\+]/
|
27
25
|
end
|
28
26
|
end
|
29
27
|
|
30
28
|
def initialize(file)
|
31
29
|
self.file = file
|
30
|
+
@content = @content_type = nil
|
32
31
|
end
|
33
32
|
|
34
33
|
##
|
@@ -40,7 +39,7 @@ module CarrierWave
|
|
40
39
|
#
|
41
40
|
def original_filename
|
42
41
|
return @original_filename if @original_filename
|
43
|
-
if @file
|
42
|
+
if @file && @file.respond_to?(:original_filename)
|
44
43
|
@file.original_filename
|
45
44
|
elsif path
|
46
45
|
File.basename(path)
|
@@ -60,29 +59,6 @@ module CarrierWave
|
|
60
59
|
|
61
60
|
alias_method :identifier, :filename
|
62
61
|
|
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
62
|
##
|
87
63
|
# Returns the file's size.
|
88
64
|
#
|
@@ -110,12 +86,11 @@ module CarrierWave
|
|
110
86
|
# [String, nil] the path where the file is located.
|
111
87
|
#
|
112
88
|
def path
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
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)
|
119
94
|
end
|
120
95
|
end
|
121
96
|
|
@@ -134,7 +109,7 @@ module CarrierWave
|
|
134
109
|
# [Boolean] whether the file is valid and has a non-zero size
|
135
110
|
#
|
136
111
|
def empty?
|
137
|
-
@file.nil? || self.size.nil? || (self.size.zero? && !
|
112
|
+
@file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
|
138
113
|
end
|
139
114
|
|
140
115
|
##
|
@@ -143,8 +118,7 @@ module CarrierWave
|
|
143
118
|
# [Boolean] Whether the file exists
|
144
119
|
#
|
145
120
|
def exists?
|
146
|
-
|
147
|
-
return false
|
121
|
+
self.path.present? && File.exist?(self.path)
|
148
122
|
end
|
149
123
|
|
150
124
|
##
|
@@ -154,15 +128,21 @@ module CarrierWave
|
|
154
128
|
#
|
155
129
|
# [String] contents of the file
|
156
130
|
#
|
157
|
-
def read
|
131
|
+
def read(*args)
|
158
132
|
if @content
|
159
|
-
|
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
|
160
140
|
elsif is_path?
|
161
|
-
File.open(@file, "rb") {|file| file.read}
|
141
|
+
File.open(@file, "rb") {|file| file.read(*args)}
|
162
142
|
else
|
163
|
-
@file.
|
164
|
-
@content = @file.read
|
165
|
-
@file.close
|
143
|
+
@file.try(:rewind)
|
144
|
+
@content = @file.read(*args)
|
145
|
+
@file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
|
166
146
|
@content
|
167
147
|
end
|
168
148
|
end
|
@@ -176,19 +156,26 @@ module CarrierWave
|
|
176
156
|
# [permissions (Integer)] permissions to set on the file in its new location.
|
177
157
|
# [directory_permissions (Integer)] permissions to set on created directories.
|
178
158
|
#
|
179
|
-
def move_to(new_path, permissions=nil, directory_permissions=nil)
|
159
|
+
def move_to(new_path, permissions=nil, directory_permissions=nil, keep_filename=false)
|
180
160
|
return if self.empty?
|
181
161
|
new_path = File.expand_path(new_path)
|
182
162
|
|
183
163
|
mkdir!(new_path, directory_permissions)
|
164
|
+
move!(new_path)
|
165
|
+
chmod!(new_path, permissions)
|
166
|
+
self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
|
167
|
+
self
|
168
|
+
end
|
169
|
+
|
170
|
+
##
|
171
|
+
# Helper to move file to new path.
|
172
|
+
#
|
173
|
+
def move!(new_path)
|
184
174
|
if exists?
|
185
|
-
FileUtils.mv(path, new_path) unless new_path
|
175
|
+
FileUtils.mv(path, new_path) unless File.identical?(new_path, path)
|
186
176
|
else
|
187
177
|
File.open(new_path, "wb") { |f| f.write(read) }
|
188
178
|
end
|
189
|
-
chmod!(new_path, permissions)
|
190
|
-
self.file = new_path
|
191
|
-
self
|
192
179
|
end
|
193
180
|
|
194
181
|
##
|
@@ -209,13 +196,20 @@ module CarrierWave
|
|
209
196
|
new_path = File.expand_path(new_path)
|
210
197
|
|
211
198
|
mkdir!(new_path, directory_permissions)
|
199
|
+
copy!(new_path)
|
200
|
+
chmod!(new_path, permissions)
|
201
|
+
self.class.new({tempfile: new_path, content_type: declared_content_type})
|
202
|
+
end
|
203
|
+
|
204
|
+
##
|
205
|
+
# Helper to create copy of file in new path.
|
206
|
+
#
|
207
|
+
def copy!(new_path)
|
212
208
|
if exists?
|
213
209
|
FileUtils.cp(path, new_path) unless new_path == path
|
214
210
|
else
|
215
211
|
File.open(new_path, "wb") { |f| f.write(read) }
|
216
212
|
end
|
217
|
-
chmod!(new_path, permissions)
|
218
|
-
self.class.new({:tempfile => new_path, :content_type => content_type})
|
219
213
|
end
|
220
214
|
|
221
215
|
##
|
@@ -246,9 +240,10 @@ module CarrierWave
|
|
246
240
|
#
|
247
241
|
def content_type
|
248
242
|
@content_type ||=
|
249
|
-
|
250
|
-
|
251
|
-
|
243
|
+
identified_content_type ||
|
244
|
+
declared_content_type ||
|
245
|
+
guessed_safe_content_type ||
|
246
|
+
Marcel::MimeType::BINARY
|
252
247
|
end
|
253
248
|
|
254
249
|
##
|
@@ -279,11 +274,11 @@ module CarrierWave
|
|
279
274
|
if file.is_a?(Hash)
|
280
275
|
@file = file["tempfile"] || file[:tempfile]
|
281
276
|
@original_filename = file["filename"] || file[:filename]
|
282
|
-
@
|
277
|
+
@declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
|
283
278
|
else
|
284
279
|
@file = file
|
285
280
|
@original_filename = nil
|
286
|
-
@
|
281
|
+
@declared_content_type = nil
|
287
282
|
end
|
288
283
|
end
|
289
284
|
|
@@ -291,7 +286,7 @@ module CarrierWave
|
|
291
286
|
def mkdir!(path, directory_permissions)
|
292
287
|
options = {}
|
293
288
|
options[:mode] = directory_permissions if directory_permissions
|
294
|
-
FileUtils.mkdir_p(File.dirname(path), options) unless File.
|
289
|
+
FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
|
295
290
|
end
|
296
291
|
|
297
292
|
def chmod!(path, permissions)
|
@@ -300,44 +295,48 @@ module CarrierWave
|
|
300
295
|
|
301
296
|
# Sanitize the filename, to prevent hacking
|
302
297
|
def sanitize(name)
|
303
|
-
name = name.
|
298
|
+
name = name.scrub
|
299
|
+
name = name.tr("\\", "/") # work-around for IE
|
304
300
|
name = File.basename(name)
|
305
|
-
name = name.gsub(sanitize_regexp,"_")
|
301
|
+
name = name.gsub(sanitize_regexp, "_")
|
306
302
|
name = "_#{name}" if name =~ /\A\.+\z/
|
307
|
-
name = "unnamed" if name.size
|
308
|
-
|
303
|
+
name = "unnamed" if name.size.zero?
|
304
|
+
name.mb_chars.to_s
|
309
305
|
end
|
310
306
|
|
311
|
-
def
|
312
|
-
|
313
|
-
@file.content_type.
|
314
|
-
|
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
|
315
312
|
end
|
316
313
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
nil
|
321
|
-
end
|
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
|
322
317
|
|
323
|
-
|
324
|
-
|
318
|
+
type = Marcel::Magic.by_path(original_filename).to_s
|
319
|
+
type if type.start_with?('text/') || type.start_with?('application/json')
|
325
320
|
end
|
326
321
|
|
327
|
-
def
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
322
|
+
def identified_content_type
|
323
|
+
with_io do |io|
|
324
|
+
Marcel::Magic.by_magic(io).try(:type)
|
325
|
+
end
|
326
|
+
rescue Errno::ENOENT
|
327
|
+
nil
|
328
|
+
end
|
333
329
|
|
334
|
-
|
335
|
-
|
336
|
-
|
330
|
+
def with_io(&block)
|
331
|
+
if file.is_a?(IO)
|
332
|
+
begin
|
333
|
+
yield file
|
334
|
+
ensure
|
335
|
+
file.try(:rewind)
|
337
336
|
end
|
337
|
+
elsif path
|
338
|
+
File.open(path, &block)
|
338
339
|
end
|
339
|
-
return filename, "" # In case we weren't able to split the extension
|
340
340
|
end
|
341
|
-
|
342
341
|
end # SanitizedFile
|
343
342
|
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
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module CarrierWave
|
4
2
|
module Storage
|
5
3
|
|
@@ -9,13 +7,17 @@ module CarrierWave
|
|
9
7
|
# pretty much it.
|
10
8
|
#
|
11
9
|
class File < Abstract
|
10
|
+
def initialize(*)
|
11
|
+
super
|
12
|
+
@cache_called = nil
|
13
|
+
end
|
12
14
|
|
13
15
|
##
|
14
16
|
# Move the file to the uploader's store path.
|
15
17
|
#
|
16
18
|
# By default, store!() uses copy_to(), which operates by copying the file
|
17
19
|
# from the cache to the store, then deleting the file from the cache.
|
18
|
-
# If move_to_store() is
|
20
|
+
# If move_to_store() is overridden to return true, then store!() uses move_to(),
|
19
21
|
# which simply moves the file from cache to store. Useful for large files.
|
20
22
|
#
|
21
23
|
# === Parameters
|
@@ -51,6 +53,72 @@ module CarrierWave
|
|
51
53
|
CarrierWave::SanitizedFile.new(path)
|
52
54
|
end
|
53
55
|
|
56
|
+
##
|
57
|
+
# Stores given file to cache directory.
|
58
|
+
#
|
59
|
+
# === Parameters
|
60
|
+
#
|
61
|
+
# [new_file (File, IOString, Tempfile)] any kind of file object
|
62
|
+
#
|
63
|
+
# === Returns
|
64
|
+
#
|
65
|
+
# [CarrierWave::SanitizedFile] a sanitized file
|
66
|
+
#
|
67
|
+
def cache!(new_file)
|
68
|
+
new_file.move_to(::File.expand_path(uploader.cache_path, uploader.root), uploader.permissions, uploader.directory_permissions, true)
|
69
|
+
rescue Errno::EMLINK, Errno::ENOSPC => e
|
70
|
+
raise(e) if @cache_called
|
71
|
+
@cache_called = true
|
72
|
+
|
73
|
+
# NOTE: Remove cached files older than 10 minutes
|
74
|
+
clean_cache!(600)
|
75
|
+
|
76
|
+
cache!(new_file)
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Retrieves the file with the given cache_name from the cache.
|
81
|
+
#
|
82
|
+
# === Parameters
|
83
|
+
#
|
84
|
+
# [cache_name (String)] uniquely identifies a cache file
|
85
|
+
#
|
86
|
+
# === Raises
|
87
|
+
#
|
88
|
+
# [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
|
89
|
+
#
|
90
|
+
def retrieve_from_cache!(identifier)
|
91
|
+
CarrierWave::SanitizedFile.new(::File.expand_path(uploader.cache_path(identifier), uploader.root))
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Deletes a cache dir
|
96
|
+
#
|
97
|
+
def delete_dir!(path)
|
98
|
+
if path
|
99
|
+
begin
|
100
|
+
Dir.rmdir(::File.expand_path(path, uploader.root))
|
101
|
+
rescue Errno::ENOENT
|
102
|
+
# Ignore: path does not exist
|
103
|
+
rescue Errno::ENOTDIR
|
104
|
+
# Ignore: path is not a dir
|
105
|
+
rescue Errno::ENOTEMPTY, Errno::EEXIST
|
106
|
+
# Ignore: dir is not empty
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def clean_cache!(seconds)
|
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)
|
117
|
+
if time < (Time.now.utc - seconds)
|
118
|
+
FileUtils.rm_rf(dir)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
54
122
|
end # File
|
55
123
|
end # Storage
|
56
124
|
end # CarrierWave
|