diff --git a/CHANGELOG b/CHANGELOG index 3236357..1098bfa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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 diff --git a/lib/mini_mime.rb b/lib/mini_mime.rb index 1aa6fda..d488cde 100644 --- a/lib/mini_mime.rb +++ b/lib/mini_mime.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require "mini_mime/version" +require "stringio" module MiniMime def self.lookup_by_filename(filename) @@ -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 @@ -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}" @@ -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 diff --git a/test/database_test.rb b/test/database_test.rb index 504def0..b614130 100644 --- a/test/database_test.rb +++ b/test/database_test.rb @@ -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 @@ -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