diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 982933b..2771ff6 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -4376,7 +4376,9 @@ gzreader_charboundary(struct gzfile *gz, rb_len_t n) if (l < n) { int n_bytes = rb_enc_precise_mbclen(p, e, gz->enc); if (MBCLEN_NEEDMORE_P(n_bytes)) { - if ((l = gzfile_fill(gz, n + MBCLEN_NEEDMORE_LEN(n_bytes), Qnil)) > 0) { + /* the character needs MBCLEN_NEEDMORE_LEN bytes beyond the END of the + * buffer, which may lie past n */ + if ((l = gzfile_fill(gz, ZSTREAM_BUF_FILLED(&gz->z) + MBCLEN_NEEDMORE_LEN(n_bytes), Qnil)) > 0) { return l; } } diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index 1f9cb81..b932034 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -1367,6 +1367,22 @@ def test_reader_wrap } end + def test_gets_limit_multibyte_boundary + # The inflated buffer ends in the middle of a character whenever a + # 2048-byte read of compressed data does; gets(limit) must still return + # whole characters (same as IO#gets(limit)). + content = (("\u3042\u3044\u3046" * 700) + "\n") * 3 + [1, 4, 7, 10].each do |limit| + r = Zlib::GzipReader.new(StringIO.new(Zlib.gzip(content, level: 0))) + lines = [] + while (line = r.gets(limit)) + lines << line + end + assert_equal(content, lines.join) + assert_empty(lines.reject(&:valid_encoding?), "limit #{limit}") + end + end + def test_corrupted_header gz = Zlib::GzipWriter.new(StringIO.new(s = "".dup)) gz.orig_name = "X"