From 0bc0da3324d7e04ad7c9fa7d856df3152319b802 Mon Sep 17 00:00:00 2001 From: kares Date: Mon, 28 Sep 2026 11:44:17 +0200 Subject: [PATCH 1/4] first draft with weird auto-detection --- lib/mini_mime.rb | 68 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/lib/mini_mime.rb b/lib/mini_mime.rb index 1aa6fda..4efb8a9 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,14 @@ def initialize(filename) @file = ::File.open(filename, 'rb') end + def size + @file.size + end + + def rewind + @file.rewind + end + def readline(*args) @file.readline(*args) end @@ -143,13 +152,40 @@ 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 rewind + @io.rewind + 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 +252,34 @@ def resolve(row) Info.new(buffer) end + + private + + def open_file_path(source) + if source.respond_to?(:read) # an IO like source, e.g. StringIO + source.rewind if source.respond_to?(:rewind) + return MemoryFile.new(source.read) + end + + file = PReadFile.new(source) + if preads?(file) + file.rewind + return file + end + + # NOTE: a path can look like a plain file and still not pread, on JRuby a + # "uri:classloader:" resource packaged inside a JAR reads from the wrong offset + file.close + MemoryFile.new(File.binread(source)) + end + + # probes after a readline, a broken pread still looks correct while the stream is at 0 + def preads?(file) + row = file.readline("\n") + file.pread(row.bytesize, 0) == row + rescue StandardError + false + end end def initialize From 10e2efac0f623319f6c0ec2ed6a3cdba7a6a4ddd Mon Sep 17 00:00:00 2001 From: kares Date: Mon, 28 Sep 2026 11:55:28 +0200 Subject: [PATCH 2/4] implement a auto-detection based on File.ftype --- lib/mini_mime.rb | 25 ++++----------------- test/database_test.rb | 51 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/lib/mini_mime.rb b/lib/mini_mime.rb index 4efb8a9..9e39264 100644 --- a/lib/mini_mime.rb +++ b/lib/mini_mime.rb @@ -131,10 +131,6 @@ def size @file.size end - def rewind - @file.rewind - end - def readline(*args) @file.readline(*args) end @@ -256,30 +252,17 @@ def resolve(row) private def open_file_path(source) - if source.respond_to?(:read) # an IO like source, e.g. StringIO + if source.respond_to?(:read) # an IO like source (e.g. StringIO) source.rewind if source.respond_to?(:rewind) return MemoryFile.new(source.read) end - file = PReadFile.new(source) - if preads?(file) - file.rewind - return file - end + return PReadFile.new(source) if File.ftype(source) == "file" - # NOTE: a path can look like a plain file and still not pread, on JRuby a - # "uri:classloader:" resource packaged inside a JAR reads from the wrong offset - file.close + # NOTE: only a real file has a descriptor to pread, JRuby hands out a File for a path + # inside a JAR ("uri:classloader:/...") but then reads it from the stream position MemoryFile.new(File.binread(source)) end - - # probes after a readline, a broken pread still looks correct while the stream is at 0 - def preads?(file) - row = file.readline("\n") - file.pread(row.bytesize, 0) == row - rescue StandardError - false - end end def initialize diff --git a/test/database_test.rb b/test/database_test.rb index 504def0..52f58c4 100644 --- a/test/database_test.rb +++ b/test/database_test.rb @@ -44,6 +44,44 @@ def test_random_access_database_search_boundaries end end + def test_reads_an_io_source_into_memory + source = StringIO.new("a text/a 7bit\nz text/z 7bit\n") + database = MiniMime::Db::RandomAccessDb.new(source, 0) + + 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 + ensure + database&.close + end + + def test_reads_an_io_source_that_was_already_consumed + source = StringIO.new("a text/a 7bit\n") + source.read + + database = MiniMime::Db::RandomAccessDb.new(source, 0) + + assert_equal "text/a", database.lookup("a").content_type + ensure + database&.close + 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 +146,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 From 9f2800497cd7c98f6db3d41220acde2ea26d9479 Mon Sep 17 00:00:00 2001 From: kares Date: Mon, 28 Sep 2026 11:59:26 +0200 Subject: [PATCH 3/4] let's just do the minimal thing --- lib/mini_mime.rb | 11 +---------- test/database_test.rb | 22 ---------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/lib/mini_mime.rb b/lib/mini_mime.rb index 9e39264..d488cde 100644 --- a/lib/mini_mime.rb +++ b/lib/mini_mime.rb @@ -158,10 +158,6 @@ def size @io.size end - def rewind - @io.rewind - end - def readline(*args) @io.readline(*args) end @@ -252,15 +248,10 @@ def resolve(row) private def open_file_path(source) - if source.respond_to?(:read) # an IO like source (e.g. StringIO) - source.rewind if source.respond_to?(:rewind) - return MemoryFile.new(source.read) - end - 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 ("uri:classloader:/...") but then reads it from the stream position + # inside a JAR archive ("uri:classloader:/...") but then reads from the stream position MemoryFile.new(File.binread(source)) end end diff --git a/test/database_test.rb b/test/database_test.rb index 52f58c4..b614130 100644 --- a/test/database_test.rb +++ b/test/database_test.rb @@ -44,28 +44,6 @@ def test_random_access_database_search_boundaries end end - def test_reads_an_io_source_into_memory - source = StringIO.new("a text/a 7bit\nz text/z 7bit\n") - database = MiniMime::Db::RandomAccessDb.new(source, 0) - - 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 - ensure - database&.close - end - - def test_reads_an_io_source_that_was_already_consumed - source = StringIO.new("a text/a 7bit\n") - source.read - - database = MiniMime::Db::RandomAccessDb.new(source, 0) - - assert_equal "text/a", database.lookup("a").content_type - ensure - database&.close - 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| From 3c56b2fecae26b004fe1e14b271c0f678555e737 Mon Sep 17 00:00:00 2001 From: kares Date: Mon, 28 Sep 2026 12:03:52 +0200 Subject: [PATCH 4/4] changelog entry --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) 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