From a16601dc10335966fff25d02cec681bf7609c19d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sun, 25 Oct 2026 07:47:06 +0900 Subject: [PATCH 1/2] Fix Zlib.gunzip with a header truncated in the file name or comment Reading the rest of the field went to the IO, which Zlib.gunzip does not have, instead of raising Zlib::GzipFile::Error as the other truncated header fields do. Co-Authored-By: Claude Opus 5.5 --- ext/zlib/zlib.c | 3 +++ test/zlib/test_zlib.rb | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index f13ef4d..208fcbe 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -2679,6 +2679,9 @@ gzfile_read_raw_until_zero(struct gzfile *gz, rb_len_t offset) p = memchr(RSTRING_PTR(gz->z.input) + offset, '\0', RSTRING_LEN(gz->z.input) - offset); if (p) break; + if (gz->io == Qundef) { + rb_raise(cGzError, "unexpected end of string"); + } str = gzfile_read_raw(gz, Qnil); if (NIL_P(str)) { rb_raise(cGzError, "unexpected end of file"); diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index e1ecbf8..0c22297 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -1715,6 +1715,12 @@ def test_gunzip src = %w[1f8b080000000000000].pack("H*") assert_raise(Zlib::GzipFile::Error){ Zlib.gunzip(src) } + + src = %w[1f8b08080000000000036e616d65].pack("H*") + assert_raise(Zlib::GzipFile::Error){ Zlib.gunzip(src) } + + src = %w[1f8b08100000000000036e616d65].pack("H*") + assert_raise(Zlib::GzipFile::Error){ Zlib.gunzip(src) } end # Zlib.gunzip input is always considered a binary string, regardless of its String#encoding. From 2fc5eb721f08cf9521b4186e97787786670ef762 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sun, 25 Oct 2026 09:38:12 +0900 Subject: [PATCH 2/2] Test Zlib.gunzip with optional gzip header fields Co-Authored-By: Claude Opus 5.5 --- test/zlib/test_zlib.rb | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index 0c22297..447ddb7 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -1723,6 +1723,38 @@ def test_gunzip assert_raise(Zlib::GzipFile::Error){ Zlib.gunzip(src) } end + def test_gunzip_orig_name_and_comment + [["X", nil], [nil, "Y"], ["X", "Y"], ["", ""]].each do |orig_name, comment| + gz = Zlib::GzipWriter.new(StringIO.new(s = "".dup)) + gz.orig_name = orig_name if orig_name + gz.comment = comment if comment + gz.print("foo") + gz.finish + assert_equal("foo", Zlib.gunzip(s), [orig_name, comment].inspect) + end + end + + def test_gunzip_corrupted_header + gz = Zlib::GzipWriter.new(StringIO.new(s = "".dup)) + gz.orig_name = "X" + gz.comment = "Y" + gz.print("foo") + gz.finish + # see TestZlibGzipReader#test_corrupted_header + 1.upto(14) do |idx| + assert_raise(Zlib::GzipFile::Error, idx.to_s) do + Zlib.gunzip(s[0, idx]) + end + end + + # FEXTRA cut in its length and in its data + %w[1f8b080400000000000304 1f8b0804000000000003040061].each do |hex| + assert_raise(Zlib::GzipFile::Error, hex) do + Zlib.gunzip([hex].pack("H*")) + end + end + end + # Zlib.gunzip input is always considered a binary string, regardless of its String#encoding. def test_gunzip_encoding # vvvvvvvv = mtime, but valid UTF-8 string of U+0080