From 5eb504a3784705838750b9a47a75248949acd1b7 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Fri, 23 Oct 2026 22:00:10 -0700 Subject: [PATCH] Do not raise Zlib::BufError for a repeated flush with no new input zlib's deflate() returns Z_BUF_ERROR when it is asked to flush again without having consumed any input since the previous flush of the same or higher rank, and the binding treated every code other than Z_OK and Z_STREAM_END as fatal. Nothing is lost in that case, and zlib's own gzflush() ignores the code for the same reason. The same happened for Zlib::Deflate#flush called twice and for GzipWriter#write("") with sync = true. ```ruby gz = Zlib::GzipWriter.new(StringIO.new(+"")) gz.write("a") gz.flush gz.flush #=> Zlib::BufError (buffer error) ``` Co-Authored-By: Claude Mythos 5.1 --- ext/zlib/zlib.c | 7 +++++++ test/zlib/test_zlib.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 982933b..c804c53 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -1195,6 +1195,13 @@ zstream_run_try(VALUE value_arg) } } + if (err == Z_BUF_ERROR && z->func == &deflate_funcs && z->stream.avail_in == 0) { + /* deflate() reports Z_BUF_ERROR for a flush when no input was consumed + * since the previous flush of the same or higher rank. Nothing is lost, + * so treat it as a no-op like zlib's own gzflush() does. */ + err = Z_OK; + } + if (flush != Z_FINISH && err == Z_BUF_ERROR && z->stream.avail_out > 0) { z->flags |= ZSTREAM_FLAG_IN_STREAM; } diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index 1f9cb81..9cfd09e 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -121,6 +121,16 @@ def test_flush assert_equal("foobarbazqux", Zlib::Inflate.inflate(s)) end + def test_flush_twice + z = Zlib::Deflate.new + s = z.deflate("foo", Zlib::SYNC_FLUSH) + s << z.flush << z.flush + s << z.flush(Zlib::FULL_FLUSH) << z.flush(Zlib::SYNC_FLUSH) + s << z.deflate("", Zlib::SYNC_FLUSH) + s << z.finish + assert_equal("foo", Zlib::Inflate.inflate(s)) + end + def test_avail z = Zlib::Deflate.new assert_equal(0, z.avail_in) @@ -1535,6 +1545,22 @@ def test_puts } end + def test_flush_twice + io = StringIO.new("".b) + Zlib::GzipWriter.wrap(io) do |gz| + gz.write("a") + gz.flush + gz.flush + gz.flush(Zlib::FULL_FLUSH) + gz.flush(Zlib::SYNC_FLUSH) + gz.sync = true + gz.write("") + gz.write("") + gz.write("b") + end + assert_equal("ab", Zlib.gunzip(io.string)) + end + def test_writer_wrap Tempfile.create("test_zlib_gzip_writer_wrap") {|t| t.binmode