diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 982933b..0bd55dc 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -4549,6 +4549,25 @@ rb_gzreader_readline(int argc, VALUE *argv, VALUE obj) return dst; } +/* + * IO#each_line and IO#readlines reject a zero limit: gets(..., 0) returns "" + * without consuming anything, so a loop over it would never terminate. + */ +static void +gzreader_check_limit(int argc, VALUE *argv, const char *name) +{ + VALUE rs, lim; + + if (argc == 0) return; + rb_scan_args(argc, argv, "11", &rs, &lim); + if (NIL_P(lim) && !NIL_P(rs) && NIL_P(rb_check_string_type(rs))) { + lim = rs; + } + if (!NIL_P(lim) && NUM2LEN(lim) == 0) { + rb_raise(rb_eArgError, "invalid limit: 0 for %s", name); + } +} + /* * Document-method: Zlib::GzipReader#each * @@ -4561,6 +4580,7 @@ rb_gzreader_each(int argc, VALUE *argv, VALUE obj) RETURN_ENUMERATOR(obj, 0, 0); + gzreader_check_limit(argc, argv, "each_line"); while (!NIL_P(str = gzreader_gets(argc, argv, obj))) { rb_yield(str); } @@ -4576,6 +4596,7 @@ static VALUE rb_gzreader_readlines(int argc, VALUE *argv, VALUE obj) { VALUE str, dst; + gzreader_check_limit(argc, argv, "readlines"); dst = rb_ary_new(); while (!NIL_P(str = gzreader_gets(argc, argv, obj))) { rb_ary_push(dst, str); diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index 1f9cb81..e80a0c1 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -1367,6 +1367,20 @@ def test_reader_wrap } end + def test_each_line_limit_zero + # IO#each_line / IO#readlines raise for a zero limit; gets(0) returns "" + # without consuming anything, so GzipReader used to loop forever here. + s = Zlib.gzip("ab\ncd\n") + [[0], [nil, 0], ["\n", 0]].each do |args| + r = Zlib::GzipReader.new(StringIO.new(s)) + assert_raise(ArgumentError) { r.readlines(*args) } + r = Zlib::GzipReader.new(StringIO.new(s)) + assert_raise(ArgumentError) { r.each_line(*args) { } } + r = Zlib::GzipReader.new(StringIO.new(s)) + assert_equal("", r.gets(*args)) + end + end + def test_corrupted_header gz = Zlib::GzipWriter.new(StringIO.new(s = "".dup)) gz.orig_name = "X"