Commit 9ebbf0d9 by Janko Marohnić

Support IO-like objects efficiently

Previously if we passed in an IO object which doesn't respond to #seek,
MimeMagic would read the whole IO into memory. And this is exactly the
type of objects that the Shrine file upload library deals with.

However, we can also "seek" by blank-reading that many bytes, and always
rewinding to the beginning, which is what we've implemented in this
change.

This change also doesn't require IOs to respond to #binmode.
parent 7cd73b35
...@@ -109,28 +109,27 @@ class MimeMagic ...@@ -109,28 +109,27 @@ class MimeMagic
end end
def self.magic_match(io, method) def self.magic_match(io, method)
if io.respond_to?(:seek) && io.respond_to?(:read) return magic_match(StringIO.new(io.to_s), method) unless io.respond_to?(:read)
io.binmode
io.set_encoding(Encoding::BINARY) if io.respond_to?(:set_encoding) io.binmode if io.respond_to?(:binmode)
buffer = "".force_encoding(Encoding::BINARY) io.set_encoding(Encoding::BINARY) if io.respond_to?(:set_encoding)
MAGIC.send(method) { |type, matches| magic_match_io(io, matches, buffer) } buffer = "".force_encoding(Encoding::BINARY)
else
str = io.respond_to?(:read) ? io.read : io.to_s MAGIC.send(method) { |type, matches| magic_match_io(io, matches, buffer) }
magic_match(StringIO.new(str), method)
end
end end
def self.magic_match_io(io, matches, buffer) def self.magic_match_io(io, matches, buffer)
matches.any? do |offset, value, children| matches.any? do |offset, value, children|
match = match =
if Range === offset if Range === offset
io.seek(offset.begin) io.read(offset.begin, buffer)
x = io.read(offset.end - offset.begin + value.bytesize, buffer) x = io.read(offset.end - offset.begin + value.bytesize, buffer)
x && x.include?(value) x && x.include?(value)
else else
io.seek(offset) io.read(offset, buffer)
io.read(value.bytesize, buffer) == value io.read(value.bytesize, buffer) == value
end end
io.rewind
match && (!children || magic_match_io(io, children, buffer)) match && (!children || magic_match_io(io, children, buffer))
end end
end end
......
require 'bacon' require 'bacon'
require 'mimemagic' require 'mimemagic'
require 'stringio' require 'stringio'
require 'forwardable'
describe 'MimeMagic' do describe 'MimeMagic' do
it 'should have type, mediatype and subtype' do it 'should have type, mediatype and subtype' do
...@@ -116,12 +117,15 @@ describe 'MimeMagic' do ...@@ -116,12 +117,15 @@ describe 'MimeMagic' do
it 'should handle different file objects' do it 'should handle different file objects' do
MimeMagic.add('application/mimemagic-test', magic: [[0, 'MAGICTEST']]) MimeMagic.add('application/mimemagic-test', magic: [[0, 'MAGICTEST']])
class ReadableObj class IOObject
def read def initialize
'MAGICTEST' @io = StringIO.new('MAGICTEST')
end end
extend Forwardable
delegate [:read, :size, :rewind, :eof?, :close] => :@io
end end
MimeMagic.by_magic(ReadableObj.new).should.equal 'application/mimemagic-test' MimeMagic.by_magic(IOObject.new).should.equal 'application/mimemagic-test'
class StringableObject class StringableObject
def to_s def to_s
'MAGICTEST' 'MAGICTEST'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment