From 462bbf1c3a73371acf27a6f2858a008752805485 Mon Sep 17 00:00:00 2001 From: Samuel Giddins Date: Fri, 6 Oct 2023 14:30:35 -0600 Subject: [PATCH] Fix readpartial without over-reading --- ext/zlib/zlib.c | 21 +++++++++++---------- test/zlib/test_zlib.rb | 24 +++++++++++++++++++++++- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index e7c32e0..d765156 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -2947,10 +2947,7 @@ gzfile_readpartial(struct gzfile *gz, long len, VALUE outbuf) } } - // fill stream until the buffer can be filled. while this isn't strictly - // necessary, it does help cut down on the number of calls to readpartial - // in the typical case. - while (!ZSTREAM_IS_FINISHED(&gz->z) && ZSTREAM_BUF_FILLED(&gz->z) < len) { + while (!ZSTREAM_IS_FINISHED(&gz->z) && ZSTREAM_BUF_FILLED(&gz->z) == 0) { gzfile_read_more(gz, outbuf); } @@ -2960,10 +2957,14 @@ gzfile_readpartial(struct gzfile *gz, long len, VALUE outbuf) if (GZFILE_IS_FINISHED(gz)) { if (!(gz->z.flags & GZFILE_FLAG_FOOTER_FINISHED)) { gzfile_check_footer(gz, dst); - } - if (RSTRING_LEN(dst) <= 0) + } else if (RSTRING_LEN(dst) <= 0) { + // allow one read past the end of the stream to return an empty string + // in case the stream is being read in a loop `until eof?`, + // since gzeof will return false until a read is attempted _past_ + // the end of the stream. rb_raise(rb_eEOFError, "end of file reached"); } + } return dst; } @@ -2978,10 +2979,10 @@ gzfile_read_all(struct gzfile *gz, VALUE dst) if (!(gz->z.flags & GZFILE_FLAG_FOOTER_FINISHED)) { gzfile_check_footer(gz, dst); } - if (!NIL_P(dst)) { - rb_str_resize(dst, 0); - return dst; - } + if (!NIL_P(dst)) { + rb_str_resize(dst, 0); + return dst; + } return rb_str_new(0, 0); } diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index bcf950d..ddc28bb 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -1005,6 +1005,19 @@ def test_read assert_equal "".b, s assert_predicate f, :eof? end + + File.open(File.expand_path("../fixtures/ruby-progressbar-1.13.0.gem.data.tar.gz", __dir__), "rb") do |f| + Zlib::GzipReader.wrap(f) do |gzio| + count = 0 + until gzio.eof? + s = gzio.read(16_384) + count += s.size + assert_predicate gzio, :eof? if s.empty? + end + assert_equal 59904, count + end + assert_predicate f, :closed? + end } end @@ -1032,17 +1045,25 @@ def test_readpartial File.open(File.expand_path("../fixtures/ruby-progressbar-1.13.0.gem.data.tar.gz", __dir__), "rb") do |f| Zlib::GzipReader.wrap(f) do |gzio| - gzio.readpartial(16_384) until gzio.eof? + count = 0 + until gzio.eof? + s = gzio.readpartial(16_384) + count += s.size + assert_predicate gzio, :eof? if s.empty? + end + assert_equal 59904, count end assert_predicate f, :closed? end File.open(File.expand_path("../fixtures/ruby-progressbar-1.13.0.gem.data.tar.gz", __dir__), "rb") do |f| Zlib::GzipReader.wrap(f) do |gzio| + count = 0 s = String.new(capacity: 16_384, encoding: Encoding::BINARY) loop do partial = gzio.readpartial(16_384, s) assert_same s, partial + count += s.size if gzio.eof? assert_raise(EOFError) { gzio.readpartial(16_384, s) } assert_equal "", s @@ -1050,6 +1071,7 @@ def test_readpartial break end end + assert_equal 59904, count end end end