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