From 5924737c636018e781da324c53289cc23b8f535c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 26 Oct 2026 13:18:14 +0900 Subject: [PATCH 1/2] Clear next_in once the rest of the input is buffered Zlib::Deflate#params after breaking out of the block of #deflate compressed the input left by the break a second time, and the output no longer inflated to the original. Zlib::Inflate#inflate now checks the buffered input instead of avail_in to decide whether an empty string has anything to process. Co-Authored-By: Claude Opus 5.5 --- ext/zlib/zlib.c | 8 +++++++- test/zlib/test_zlib.rb | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index f13ef4d..d51ac30 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -1216,6 +1216,8 @@ zstream_run_try(VALUE value_arg) } } } + z->stream.next_in = Z_NULL; + z->stream.avail_in = 0; raise_zlib_error(err, z->stream.msg); } @@ -1225,6 +1227,9 @@ zstream_run_try(VALUE value_arg) if (!NIL_P(old_input)) { rb_str_resize(old_input, 0); } + /* the rest is in z->input now; do not leave next_in in old_input */ + z->stream.next_in = Z_NULL; + z->stream.avail_in = 0; return Qnil; } @@ -2144,7 +2149,8 @@ do_inflate(struct zstream *z, VALUE src) return; } StringValue(src); - if (RSTRING_LEN(src) > 0 || z->stream.avail_in > 0) { /* prevent Z_BUF_ERROR */ + if (RSTRING_LEN(src) > 0 || + (!NIL_P(z->input) && RSTRING_LEN(z->input) > 0)) { /* prevent Z_BUF_ERROR */ zstream_run(z, (Bytef*)RSTRING_PTR(src), RSTRING_LEN(src), Z_SYNC_FLUSH); } } diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index e1ecbf8..986d7df 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -102,6 +102,27 @@ def test_deflate_chunked_break assert_equal input, original end + def test_params_after_chunked_break + chunks = [] + r = Random.new 0 + + # small enough that params can flush what zlib holds without Z_BUF_ERROR + z = Zlib::Deflate.new(Zlib::DEFAULT_COMPRESSION, 9, 1) + + input = r.bytes(20000) + z.deflate(input) do |chunk| + chunks << chunk + break + end + + assert_operator 0, :<, z.avail_in + + z.params(Zlib::BEST_SPEED, Zlib::FILTERED) + chunks << z.finish + + assert_equal input, Zlib.inflate(chunks.join) + end + def test_addstr z = Zlib::Deflate.new z << "foo" @@ -620,6 +641,24 @@ def test_inflate_dictionary assert_equal "foofoofoo", out end + def test_inflate_empty_after_chunked_break + chunks = [] + input = Random.new(0).bytes(20000) + + z = Zlib::Inflate.new + + z.inflate(Zlib.deflate(input)) do |chunk| + chunks << chunk + break + end + + assert_operator 0, :<, z.avail_in + + chunks << z.inflate("") + + assert_equal input, chunks.join + end + def test_sync z = Zlib::Deflate.new s = z.deflate("foo" * 1000, Zlib::FULL_FLUSH) From 7b2736f4f598a06a4e109465b5e80168e3ca5647 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 26 Oct 2026 13:22:24 +0900 Subject: [PATCH 2/2] Test Zlib::Deflate#params after a failed deflate The chunked break test does not reach the reset of next_in on the error path. Co-Authored-By: Claude Opus 5.5 --- test/zlib/test_zlib.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index 986d7df..fd85f01 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -123,6 +123,17 @@ def test_params_after_chunked_break assert_equal input, Zlib.inflate(chunks.join) end + def test_params_after_failed_deflate + z = Zlib::Deflate.new + z << "foo" + assert_raise(Zlib::StreamError) { z.deflate("bar", 10000) } + assert_equal "bar", z.flush_next_in + + z.params(Zlib::BEST_SPEED, Zlib::FILTERED) + + assert_equal "foo", Zlib::Inflate.inflate(z.finish) + end + def test_addstr z = Zlib::Deflate.new z << "foo"