carrierwave 0.11.2 → 3.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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 +217 -182
- data/lib/carrierwave/mounter.rb +255 -0
- data/lib/carrierwave/orm/activerecord.rb +29 -35
- 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 +171 -123
- 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} +6 -10
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +135 -83
- 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
|
+
Marcel::MimeType.for(declared_type: @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
|