lib/carrierwave/sanitized_file.rb in carrierwave-3.0.0.beta vs lib/carrierwave/sanitized_file.rb in carrierwave-3.0.0.rc
- old
+ new
@@ -25,11 +25,11 @@
end
end
def initialize(file)
self.file = file
- @content = nil
+ @content = @content_type = nil
end
##
# Returns the filename as is, without sanitizing it.
#
@@ -37,11 +37,11 @@
#
# [String] the unsanitized filename
#
def original_filename
return @original_filename if @original_filename
- if @file and @file.respond_to?(:original_filename)
+ if @file && @file.respond_to?(:original_filename)
@file.original_filename
elsif path
File.basename(path)
end
end
@@ -107,11 +107,11 @@
# === Returns
#
# [Boolean] whether the file is valid and has a non-zero size
#
def empty?
- @file.nil? || self.size.nil? || (self.size.zero? && ! self.exists?)
+ @file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
end
##
# === Returns
#
@@ -126,18 +126,24 @@
#
# === Returns
#
# [String] contents of the file
#
- def read
+ def read(*args)
if @content
- @content
+ if args.empty?
+ @content
+ else
+ length, outbuf = args
+ raise ArgumentError, "outbuf argument not supported since the content is already loaded" if outbuf
+ @content[0, length]
+ end
elsif is_path?
- File.open(@file, "rb") {|file| file.read}
+ File.open(@file, "rb") {|file| file.read(*args)}
else
@file.try(:rewind)
- @content = @file.read
+ @content = @file.read(*args)
@file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
@content
end
end
@@ -155,17 +161,14 @@
new_path = File.expand_path(new_path)
mkdir!(new_path, directory_permissions)
move!(new_path)
chmod!(new_path, permissions)
- if keep_filename
- self.file = {:tempfile => new_path, :filename => original_filename, :content_type => @content_type}
- else
- self.file = {:tempfile => new_path, :content_type => @content_type}
- end
+ self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
self
end
+
##
# Helper to move file to new path.
#
def move!(new_path)
if exists?
@@ -193,11 +196,11 @@
new_path = File.expand_path(new_path)
mkdir!(new_path, directory_permissions)
copy!(new_path)
chmod!(new_path, permissions)
- self.class.new({:tempfile => new_path, :content_type => content_type})
+ self.class.new({tempfile: new_path, content_type: declared_content_type})
end
##
# Helper to create copy of file in new path.
#
@@ -235,13 +238,14 @@
#
# [String] the content type of the file
#
def content_type
@content_type ||=
- existing_content_type ||
- marcel_magic_by_mime_type ||
- marcel_magic_by_path
+ identified_content_type ||
+ declared_content_type ||
+ guessed_safe_content_type ||
+ Marcel::MimeType::BINARY
end
##
# Sets the content type of the file.
#
@@ -268,15 +272,15 @@
def file=(file)
if file.is_a?(Hash)
@file = file["tempfile"] || file[:tempfile]
@original_filename = file["filename"] || file[:filename]
- @content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
+ @declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
else
@file = file
@original_filename = nil
- @content_type = nil
+ @declared_content_type = nil
end
end
# create the directory if it doesn't exist
def mkdir!(path, directory_permissions)
@@ -292,41 +296,47 @@
# Sanitize the filename, to prevent hacking
def sanitize(name)
name = name.scrub
name = name.tr("\\", "/") # work-around for IE
name = File.basename(name)
- name = name.gsub(sanitize_regexp,"_")
+ name = name.gsub(sanitize_regexp, "_")
name = "_#{name}" if name =~ /\A\.+\z/
name = "unnamed" if name.size.zero?
- return name.mb_chars.to_s
+ name.mb_chars.to_s
end
- def existing_content_type
- if @file.respond_to?(:content_type) && @file.content_type
- @file.content_type.to_s.chomp
- end
+ def declared_content_type
+ @declared_content_type ||
+ if @file.respond_to?(:content_type) && @file.content_type
+ @file.content_type.to_s.chomp
+ end
end
- def marcel_magic_by_mime_type
+ # Guess content type from its file extension. Limit what to be returned to prevent spoofing.
+ def guessed_safe_content_type
return unless path
- type = File.open(path) do |file|
- Marcel::Magic.by_magic(file).try(:type)
- end
+ type = Marcel::Magic.by_path(original_filename).to_s
+ type if type.start_with?('text/') || type.start_with?('application/json')
+ end
- if type.nil?
- type = Marcel::Magic.by_path(path).try(:type)
- type = 'invalid/invalid' unless type.nil? || type.start_with?('text/') || type.start_with?('application/json')
+ def identified_content_type
+ with_io do |io|
+ Marcel::Magic.by_magic(io).try(:type)
end
-
- type
rescue Errno::ENOENT
nil
end
- def marcel_magic_by_path
- return unless path
-
- Marcel::Magic.by_path(path).to_s
+ def with_io(&block)
+ if file.is_a?(IO)
+ begin
+ yield file
+ ensure
+ file.try(:rewind)
+ end
+ elsif path
+ File.open(path, &block)
+ end
end
end # SanitizedFile
end # CarrierWave