Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
unreleased
- Fall back to an in-memory database when a path can not be pread (such as a path inside a JAR on JRuby)

02-09-2026
- Version 2.0.0
- Require Ruby 3.3 or newer
Expand Down
42 changes: 40 additions & 2 deletions lib/mini_mime.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true
require "mini_mime/version"
require "stringio"

module MiniMime
def self.lookup_by_filename(filename)
Expand Down Expand Up @@ -126,6 +127,10 @@ def initialize(filename)
@file = ::File.open(filename, 'rb')
end

def size
@file.size
end

def readline(*args)
@file.readline(*args)
end
Expand All @@ -143,13 +148,36 @@ def close
end
end

# Holds the whole database in memory
class MemoryFile
def initialize(content)
@io = StringIO.new(content.b.freeze)
end

def size
@io.size
end

def readline(*args)
@io.readline(*args)
end

def pread(size, offset)
@io.pread(size, offset)
end

def close
@io.close
end
end

class RandomAccessDb
MAX_CACHED = 100

def initialize(path, sort_order)
@path = path
@file = PReadFile.new(@path)
@file_length = File.size(@path)
@file = open_file_path(path)
@file_length = @file.size
if @file_length.zero?
@file.close
raise ArgumentError, "MIME database is empty: #{@path}"
Expand Down Expand Up @@ -216,6 +244,16 @@ def resolve(row)

Info.new(buffer)
end

private

def open_file_path(source)
return PReadFile.new(source) if File.ftype(source) == "file"

# NOTE: only a real file has a descriptor to pread, JRuby hands out a File for a path
# inside a JAR archive ("uri:classloader:/...") but then reads from the stream position
MemoryFile.new(File.binread(source))
end
end

def initialize
Expand Down
29 changes: 29 additions & 0 deletions test/database_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ def test_random_access_database_search_boundaries
end
end

def test_falls_back_to_memory_for_a_path_that_is_not_a_file
with_ftype("unknown") do
with_database("a text/a 7bit\nz text/z 7bit\n") do |database|
assert_instance_of MiniMime::Db::MemoryFile, database_file(database)
assert_equal "text/a", database.lookup("a").content_type
assert_equal "text/z", database.lookup("z").content_type
end
end
end

def test_uses_the_file_backend_for_a_plain_path
with_database("a text/a 7bit\n") do |database|
assert_instance_of MiniMime::Db::PReadFile, database_file(database)
end
end

def test_rejects_an_empty_database
assert_invalid_database("")
end
Expand Down Expand Up @@ -108,6 +124,19 @@ def test_rebuild_uses_the_selected_types_preferred_extension

private

# JRuby reports "unknown" for a path inside an archive, a real file is always "file"
def with_ftype(ftype)
original = File.singleton_class.instance_method(:ftype)
File.singleton_class.define_method(:ftype) { |*| ftype }
yield
ensure
File.singleton_class.define_method(:ftype, original)
end

def database_file(database)
database.instance_variable_get(:@file)
end

def assert_invalid_database(contents)
Tempfile.create do |file|
file.binmode
Expand Down