carrierwave 0.9.0 → 3.0.2
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 +7 -0
- data/README.md +508 -158
- data/lib/carrierwave/compatibility/paperclip.rb +31 -21
- 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 +220 -187
- data/lib/carrierwave/mounter.rb +255 -0
- data/lib/carrierwave/orm/activerecord.rb +24 -34
- data/lib/carrierwave/processing/mini_magick.rb +142 -79
- data/lib/carrierwave/processing/rmagick.rb +76 -35
- data/lib/carrierwave/processing/vips.rb +284 -0
- data/lib/carrierwave/processing.rb +1 -1
- data/lib/carrierwave/sanitized_file.rb +89 -70
- data/lib/carrierwave/storage/abstract.rb +16 -3
- data/lib/carrierwave/storage/file.rb +71 -3
- data/lib/carrierwave/storage/fog.rb +215 -58
- data/lib/carrierwave/storage.rb +1 -7
- data/lib/carrierwave/test/matchers.rb +88 -19
- data/lib/carrierwave/uploader/cache.rb +88 -44
- data/lib/carrierwave/uploader/callbacks.rb +1 -3
- data/lib/carrierwave/uploader/configuration.rb +81 -9
- 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 +5 -69
- 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 +54 -21
- data/lib/carrierwave/uploader/proxy.rb +30 -8
- data/lib/carrierwave/uploader/remove.rb +0 -2
- data/lib/carrierwave/uploader/serialization.rb +3 -5
- data/lib/carrierwave/uploader/store.rb +59 -28
- data/lib/carrierwave/uploader/url.rb +8 -7
- data/lib/carrierwave/uploader/versions.rb +173 -124
- data/lib/carrierwave/uploader.rb +12 -6
- data/lib/carrierwave/utilities/file_name.rb +47 -0
- data/lib/carrierwave/utilities/uri.rb +14 -12
- data/lib/carrierwave/utilities.rb +2 -3
- data/lib/carrierwave/validations/active_model.rb +7 -13
- data/lib/carrierwave/version.rb +1 -1
- data/lib/carrierwave.rb +41 -16
- data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -9
- data/lib/generators/uploader_generator.rb +3 -3
- metadata +224 -100
- data/lib/carrierwave/locale/cs.yml +0 -11
- data/lib/carrierwave/locale/de.yml +0 -11
- data/lib/carrierwave/locale/nl.yml +0 -11
- data/lib/carrierwave/locale/sk.yml +0 -11
- data/lib/carrierwave/processing/mime_types.rb +0 -73
- data/lib/carrierwave/uploader/extension_blacklist.rb +0 -47
- data/lib/carrierwave/uploader/extension_whitelist.rb +0 -49
@@ -1,7 +1,6 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
require 'pathname'
|
4
2
|
require 'active_support/core_ext/string/multibyte'
|
3
|
+
require 'marcel'
|
5
4
|
|
6
5
|
module CarrierWave
|
7
6
|
|
@@ -14,19 +13,21 @@ module CarrierWave
|
|
14
13
|
# It's probably needlessly comprehensive and complex. Help is appreciated.
|
15
14
|
#
|
16
15
|
class SanitizedFile
|
16
|
+
include CarrierWave::Utilities::FileName
|
17
17
|
|
18
|
-
|
18
|
+
attr_reader :file
|
19
19
|
|
20
20
|
class << self
|
21
21
|
attr_writer :sanitize_regexp
|
22
22
|
|
23
23
|
def sanitize_regexp
|
24
|
-
@sanitize_regexp ||= /[^
|
24
|
+
@sanitize_regexp ||= /[^[:word:]\.\-\+]/
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
def initialize(file)
|
29
29
|
self.file = file
|
30
|
+
@content = @content_type = nil
|
30
31
|
end
|
31
32
|
|
32
33
|
##
|
@@ -38,7 +39,7 @@ module CarrierWave
|
|
38
39
|
#
|
39
40
|
def original_filename
|
40
41
|
return @original_filename if @original_filename
|
41
|
-
if @file
|
42
|
+
if @file && @file.respond_to?(:original_filename)
|
42
43
|
@file.original_filename
|
43
44
|
elsif path
|
44
45
|
File.basename(path)
|
@@ -58,29 +59,6 @@ module CarrierWave
|
|
58
59
|
|
59
60
|
alias_method :identifier, :filename
|
60
61
|
|
61
|
-
##
|
62
|
-
# Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
|
63
|
-
# this would return 'test'
|
64
|
-
#
|
65
|
-
# === Returns
|
66
|
-
#
|
67
|
-
# [String] the first part of the filename
|
68
|
-
#
|
69
|
-
def basename
|
70
|
-
split_extension(filename)[0] if filename
|
71
|
-
end
|
72
|
-
|
73
|
-
##
|
74
|
-
# Returns the file extension
|
75
|
-
#
|
76
|
-
# === Returns
|
77
|
-
#
|
78
|
-
# [String] the extension
|
79
|
-
#
|
80
|
-
def extension
|
81
|
-
split_extension(filename)[1] if filename
|
82
|
-
end
|
83
|
-
|
84
62
|
##
|
85
63
|
# Returns the file's size.
|
86
64
|
#
|
@@ -108,12 +86,11 @@ module CarrierWave
|
|
108
86
|
# [String, nil] the path where the file is located.
|
109
87
|
#
|
110
88
|
def path
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
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)
|
117
94
|
end
|
118
95
|
end
|
119
96
|
|
@@ -132,7 +109,7 @@ module CarrierWave
|
|
132
109
|
# [Boolean] whether the file is valid and has a non-zero size
|
133
110
|
#
|
134
111
|
def empty?
|
135
|
-
@file.nil? || self.size.nil? || (self.size.zero? && !
|
112
|
+
@file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
|
136
113
|
end
|
137
114
|
|
138
115
|
##
|
@@ -141,8 +118,7 @@ module CarrierWave
|
|
141
118
|
# [Boolean] Whether the file exists
|
142
119
|
#
|
143
120
|
def exists?
|
144
|
-
|
145
|
-
return false
|
121
|
+
self.path.present? && File.exist?(self.path)
|
146
122
|
end
|
147
123
|
|
148
124
|
##
|
@@ -152,15 +128,21 @@ module CarrierWave
|
|
152
128
|
#
|
153
129
|
# [String] contents of the file
|
154
130
|
#
|
155
|
-
def read
|
131
|
+
def read(*args)
|
156
132
|
if @content
|
157
|
-
|
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
|
158
140
|
elsif is_path?
|
159
|
-
File.open(@file, "rb") {|file| file.read}
|
141
|
+
File.open(@file, "rb") {|file| file.read(*args)}
|
160
142
|
else
|
161
|
-
@file.
|
162
|
-
@content = @file.read
|
163
|
-
@file.close
|
143
|
+
@file.try(:rewind)
|
144
|
+
@content = @file.read(*args)
|
145
|
+
@file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
|
164
146
|
@content
|
165
147
|
end
|
166
148
|
end
|
@@ -174,19 +156,26 @@ module CarrierWave
|
|
174
156
|
# [permissions (Integer)] permissions to set on the file in its new location.
|
175
157
|
# [directory_permissions (Integer)] permissions to set on created directories.
|
176
158
|
#
|
177
|
-
def move_to(new_path, permissions=nil, directory_permissions=nil)
|
159
|
+
def move_to(new_path, permissions=nil, directory_permissions=nil, keep_filename=false)
|
178
160
|
return if self.empty?
|
179
161
|
new_path = File.expand_path(new_path)
|
180
162
|
|
181
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)
|
182
174
|
if exists?
|
183
|
-
FileUtils.mv(path, new_path) unless new_path
|
175
|
+
FileUtils.mv(path, new_path) unless File.identical?(new_path, path)
|
184
176
|
else
|
185
177
|
File.open(new_path, "wb") { |f| f.write(read) }
|
186
178
|
end
|
187
|
-
chmod!(new_path, permissions)
|
188
|
-
self.file = new_path
|
189
|
-
self
|
190
179
|
end
|
191
180
|
|
192
181
|
##
|
@@ -207,13 +196,20 @@ module CarrierWave
|
|
207
196
|
new_path = File.expand_path(new_path)
|
208
197
|
|
209
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)
|
210
208
|
if exists?
|
211
209
|
FileUtils.cp(path, new_path) unless new_path == path
|
212
210
|
else
|
213
211
|
File.open(new_path, "wb") { |f| f.write(read) }
|
214
212
|
end
|
215
|
-
chmod!(new_path, permissions)
|
216
|
-
self.class.new({:tempfile => new_path, :content_type => content_type})
|
217
213
|
end
|
218
214
|
|
219
215
|
##
|
@@ -243,8 +239,11 @@ module CarrierWave
|
|
243
239
|
# [String] the content type of the file
|
244
240
|
#
|
245
241
|
def content_type
|
246
|
-
|
247
|
-
|
242
|
+
@content_type ||=
|
243
|
+
identified_content_type ||
|
244
|
+
declared_content_type ||
|
245
|
+
guessed_safe_content_type ||
|
246
|
+
Marcel::MimeType::BINARY
|
248
247
|
end
|
249
248
|
|
250
249
|
##
|
@@ -275,11 +274,11 @@ module CarrierWave
|
|
275
274
|
if file.is_a?(Hash)
|
276
275
|
@file = file["tempfile"] || file[:tempfile]
|
277
276
|
@original_filename = file["filename"] || file[:filename]
|
278
|
-
@
|
277
|
+
@declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
|
279
278
|
else
|
280
279
|
@file = file
|
281
280
|
@original_filename = nil
|
282
|
-
@
|
281
|
+
@declared_content_type = nil
|
283
282
|
end
|
284
283
|
end
|
285
284
|
|
@@ -287,7 +286,7 @@ module CarrierWave
|
|
287
286
|
def mkdir!(path, directory_permissions)
|
288
287
|
options = {}
|
289
288
|
options[:mode] = directory_permissions if directory_permissions
|
290
|
-
FileUtils.mkdir_p(File.dirname(path), options) unless File.
|
289
|
+
FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
|
291
290
|
end
|
292
291
|
|
293
292
|
def chmod!(path, permissions)
|
@@ -296,28 +295,48 @@ module CarrierWave
|
|
296
295
|
|
297
296
|
# Sanitize the filename, to prevent hacking
|
298
297
|
def sanitize(name)
|
299
|
-
name = name.
|
298
|
+
name = name.scrub
|
299
|
+
name = name.tr("\\", "/") # work-around for IE
|
300
300
|
name = File.basename(name)
|
301
|
-
name = name.gsub(sanitize_regexp,"_")
|
301
|
+
name = name.gsub(sanitize_regexp, "_")
|
302
302
|
name = "_#{name}" if name =~ /\A\.+\z/
|
303
|
-
name = "unnamed" if name.size
|
304
|
-
|
303
|
+
name = "unnamed" if name.size.zero?
|
304
|
+
name.mb_chars.to_s
|
305
305
|
end
|
306
306
|
|
307
|
-
def
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
/\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
|
312
|
-
]
|
313
|
-
|
314
|
-
extension_matchers.each do |regexp|
|
315
|
-
if filename =~ regexp
|
316
|
-
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
|
317
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)
|
318
325
|
end
|
319
|
-
|
326
|
+
rescue Errno::ENOENT
|
327
|
+
nil
|
320
328
|
end
|
321
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
|
322
341
|
end # SanitizedFile
|
323
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
|