From 44dc34465f9c02de2cf4510980d90006a2bd09a9 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Fri, 23 Oct 2026 22:28:29 -0700 Subject: [PATCH] Do not raise Zlib::BufError when the dictionary id ends the available input After Zlib::Inflate#add_dictionary has supplied the dictionary, zstream_run_try sets it and calls inflate() again at once. If the dictionary id was the last byte fed so far, that call has no input and returns Z_BUF_ERROR, which was raised although the dictionary was applied and the stream is fine. ```ruby d = Zlib::Deflate.new d.set_dictionary("hello world") data = d.deflate("hello", Zlib::FINISH) z = Zlib::Inflate.new z.add_dictionary("hello world") data.each_byte { |b| z.inflate(b.chr) } # expected: no error # actual: Zlib::BufError at the byte that completes the dictionary id ``` Only re-enter inflate() when input remains. Co-Authored-By: Claude Mythos 5.1 --- ext/zlib/zlib.c | 7 +++++-- test/zlib/test_zlib.rb | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 982933b..14b4d58 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -1212,11 +1212,14 @@ zstream_run_try(VALUE value_arg) VALUE dict = rb_hash_aref(dicts, rb_uint2inum(z->stream.adler)); if (!NIL_P(dict)) { rb_inflate_set_dictionary(self, dict); - goto loop; + if (z->stream.avail_in > 0) goto loop; + /* the dictionary id was the last byte available; inflate() + * would only report Z_BUF_ERROR now */ + err = Z_OK; } } } - raise_zlib_error(err, z->stream.msg); + if (err != Z_OK) raise_zlib_error(err, z->stream.msg); } if (z->stream.avail_in > 0) { diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index 1f9cb81..15cf4a4 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -283,6 +283,22 @@ def test_add_dictionary assert_equal "foofoofoo", out end + def test_add_dictionary_byte_at_a_time + dict = "hello world" + d = Zlib::Deflate.new + d.set_dictionary(dict) + deflated = d.deflate("hello", Zlib::FINISH) + d.close + z = Zlib::Inflate.new + z.add_dictionary(dict) + out = "".dup + # feeding the byte that completes the dictionary id used to raise BufError + deflated.each_byte { |b| out << z.inflate(b.chr) } + out << z.finish + assert_equal("hello", out) + z.close + end + def test_finish_chunked # zeros = Zlib::Deflate.deflate("0" * 100_000) zeros = "x\234\355\3011\001\000\000\000\302\240J\353\237\316\032\036@" \