rubyzip 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67852d915e2ec168efb617d7577ac60196bf7433b3c2800981a14c5a43b939c5
4
- data.tar.gz: '039b39e7e9e7f46e056da4d0070554eba081e26a4dd99e3dd47d76428628d29f'
3
+ metadata.gz: 301afd00cf28573bc4e6b12cd9a7f6dca91dcd260244423ac8dab1c97e4dcbec
4
+ data.tar.gz: 8c6d46366f1fe3064632b0de1a00a82f44184438ae7febdc3db82407092965f2
5
5
  SHA512:
6
- metadata.gz: ba24363c26265acbd295289685d5baa4d351a913d98de67064171c35f0055f04ac34b2e6db384af248e124b12abc505bac558205eadb2975d3d4da59178b9a54
7
- data.tar.gz: b654167d21076c70ea58b6a185f4be83a6ef172d208bf4f8a8928ac82aa909773b20df462870dc833b1182f669334e18fd7283e23cf4040b770faea1a49d1e6a
6
+ metadata.gz: 44a3a3e3176ea832db64716d81ae5dcf2919d975e395012a27ca849f3d6d22a2b9e199f73bae152f2ae2acb62f4ccbce3de33b4ffd26f21935d2ab997f2abb19
7
+ data.tar.gz: beeec7382ef911feeb9e4403dc6025a02904f74cc31cb9948f180c060b8706aa9b080d0e2bbbcfaae9c89465e0922799169713bba5563d275db3d677dc0962d4
data/Changelog.md CHANGED
@@ -1,3 +1,13 @@
1
+ # 3.3.0 (2026-05-02)
2
+
3
+ - Refactor `InputStream` and `AbstractInputStream`.
4
+ [#661](https://github.com/rubyzip/rubyzip/pull/661)
5
+
6
+ Tooling/internal:
7
+
8
+ - Update Actions to use checkout@v5.
9
+ - Add Ruby4.0 to the CI matrix. [#659](https://github.com/rubyzip/rubyzip/pull/659)
10
+
1
11
  # 3.2.2 (2025-11-02)
2
12
 
3
13
  - Fix reading EOCDs when header signatures are in an Entry payload. [#656](https://github.com/rubyzip/rubyzip/issues/656)
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  BSD 2-Clause License
2
2
 
3
- Copyright (c) 2002-2025, The Rubyzip Developers
3
+ Copyright (c) 2002-2026, The Rubyzip Developers
4
4
 
5
5
  Redistribution and use in source and binary forms, with or without
6
6
  modification, are permitted provided that the following conditions are met:
@@ -8,13 +8,13 @@ module Zip
8
8
  @io = io
9
9
  @decrypter = decrypter
10
10
  @bytes_remaining = compressed_size
11
- @buffer = +''
11
+ @buffer = +''.b
12
12
  end
13
13
 
14
- def read(length = nil, outbuf = +'')
15
- return (length.nil? || length.zero? ? '' : nil) if eof?
14
+ def read(maxlen = nil)
15
+ return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
16
16
 
17
- while length.nil? || (@buffer.bytesize < length)
17
+ while maxlen.nil? || (@buffer.bytesize < maxlen)
18
18
  break if input_finished?
19
19
 
20
20
  @buffer << produce_input
@@ -22,7 +22,7 @@ module Zip
22
22
 
23
23
  @decrypter.check_integrity!(@io) if input_finished?
24
24
 
25
- outbuf.replace(@buffer.slice!(0...(length || @buffer.bytesize)))
25
+ @buffer.slice!(0...(maxlen || @buffer.bytesize))
26
26
  end
27
27
 
28
28
  private
data/lib/zip/entry.rb CHANGED
@@ -751,21 +751,20 @@ module Zip
751
751
 
752
752
  def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc })
753
753
  if ::File.exist?(dest_path) && !yield(self, dest_path)
754
- raise ::Zip::DestinationExistsError, dest_path
754
+ raise DestinationExistsError, dest_path
755
755
  end
756
756
 
757
757
  ::File.open(dest_path, 'wb') do |os|
758
758
  get_input_stream do |is|
759
759
  bytes_written = 0
760
760
  warned = false
761
- buf = +''
762
- while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf))
761
+ while (buf = is.sysread(Decompressor::CHUNK_SIZE))
763
762
  os << buf
764
763
  bytes_written += buf.bytesize
765
764
  next unless bytes_written > size && !warned
766
765
 
767
- error = ::Zip::EntrySizeError.new(self)
768
- raise error if ::Zip.validate_entry_sizes
766
+ error = EntrySizeError.new(self)
767
+ raise error if Zip.validate_entry_sizes
769
768
 
770
769
  warn "WARNING: #{error.message}"
771
770
  warned = true
data/lib/zip/inflater.rb CHANGED
@@ -5,20 +5,20 @@ module Zip
5
5
  def initialize(*args)
6
6
  super
7
7
 
8
- @buffer = +''
8
+ @buffer = +''.b
9
9
  @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS)
10
10
  end
11
11
 
12
- def read(length = nil, outbuf = +'')
13
- return (length.nil? || length.zero? ? '' : nil) if eof?
12
+ def read(maxlen = nil)
13
+ return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
14
14
 
15
- while length.nil? || (@buffer.bytesize < length)
15
+ while maxlen.nil? || (@buffer.bytesize < maxlen)
16
16
  break if input_finished?
17
17
 
18
18
  @buffer << produce_input
19
19
  end
20
20
 
21
- outbuf.replace(@buffer.slice!(0...(length || @buffer.bytesize)))
21
+ @buffer.slice!(0...(maxlen || @buffer.bytesize))
22
22
  end
23
23
 
24
24
  def eof?
@@ -89,9 +89,28 @@ module Zip
89
89
  open_entry
90
90
  end
91
91
 
92
- # Modeled after IO.sysread
93
- def sysread(length = nil, outbuf = +'')
94
- @decompressor.read(length, outbuf)
92
+ # Modelled after IO#sysread.
93
+ #
94
+ # Reads up to maxlen bytes from the stream; returns a string
95
+ # (either a new string or the given out_string).
96
+ # Its encoding is the unchanged encoding of out_string, if out_string is
97
+ # given; ASCII-8BIT, otherwise. Output contains maxlen bytes from the
98
+ # stream, if available; otherwise contains all available bytes, if any
99
+ # available; otherwise is an empty string.
100
+ #
101
+ # This method should not be used with buffered input stream-reader methods,
102
+ # such as #read, #readline, #gets.
103
+ def sysread(maxlen, out_string = nil)
104
+ return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
105
+
106
+ output = produce_input(maxlen)
107
+
108
+ if out_string.nil?
109
+ output.force_encoding(Encoding::ASCII_8BIT)
110
+ else
111
+ encoding = out_string.encoding
112
+ out_string.replace(output).force_encoding(encoding)
113
+ end
95
114
  end
96
115
 
97
116
  # Returns the size of the current entry, or `nil` if there isn't one.
@@ -197,8 +216,8 @@ module Zip
197
216
  decompressor_class.new(io, decompressed_size)
198
217
  end
199
218
 
200
- def produce_input # :nodoc:
201
- @decompressor.read(CHUNK_SIZE)
219
+ def produce_input(maxlen = CHUNK_SIZE) # :nodoc:
220
+ @decompressor.read(maxlen)
202
221
  end
203
222
 
204
223
  def input_finished? # :nodoc:
@@ -3,126 +3,208 @@
3
3
  module Zip
4
4
  module IOExtras # :nodoc:
5
5
  # Implements many of the convenience methods of IO
6
- # such as gets, getc, readline and readlines
6
+ # such as gets, getc, read, readline and readlines
7
7
  # depends on: input_finished?, produce_input and read
8
- module AbstractInputStream # :nodoc:
8
+ module AbstractInputStream
9
9
  include Enumerable
10
10
  include FakeIO
11
11
 
12
- def initialize
12
+ def initialize # :nodoc:
13
13
  super
14
14
  @lineno = 0
15
15
  @pos = 0
16
- @output_buffer = +''
16
+ @output_buffer = +''.b
17
17
  end
18
18
 
19
+ # Returns (or sets) the current line number in the decompressed
20
+ # (possibly decrypted) data stream. See the Line Number documentation
21
+ # for the IO class for more information.
19
22
  attr_accessor :lineno
23
+
24
+ # Returns the current position (in bytes) in the decompressed (possibly
25
+ # decrypted) data stream.
20
26
  attr_reader :pos
21
27
 
22
- def read(number_of_bytes = nil, buf = +'')
28
+ # Reads bytes from the stream decompressed (possibly decrypted) data
29
+ # stream. If `maxlen` is `nil`, reads all bytes; otherwise, reads up to
30
+ # `maxlen` bytes. If `maxlen` is zero, returns an empty string.
31
+ #
32
+ # Returns a string (either a new string or the given `out_string`)
33
+ # containing the bytes read. The string's encoding is the unchanged
34
+ # encoding of `out_string`, if `out_string` is given; `ASCII-8BIT`,
35
+ # otherwise.
36
+ def read(maxlen = nil, out_string = nil) # rubocop:disable Metrics/PerceivedComplexity
37
+ return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
38
+
23
39
  tbuf = if @output_buffer.bytesize > 0
24
- if number_of_bytes && number_of_bytes <= @output_buffer.bytesize
25
- @output_buffer.slice!(0, number_of_bytes)
40
+ if maxlen && maxlen <= @output_buffer.bytesize
41
+ @output_buffer.slice!(0, maxlen)
26
42
  else
27
- number_of_bytes -= @output_buffer.bytesize if number_of_bytes
28
- rbuf = sysread(number_of_bytes, buf)
43
+ maxlen -= @output_buffer.bytesize if maxlen
44
+ rbuf = produce_input(maxlen)
29
45
  out = @output_buffer
30
46
  out << rbuf if rbuf
31
- @output_buffer = ''
47
+ @output_buffer = +''.b
32
48
  out
33
49
  end
34
50
  else
35
- sysread(number_of_bytes, buf)
51
+ produce_input(maxlen)
36
52
  end
37
53
 
38
54
  if tbuf.nil? || tbuf.empty?
39
- return nil if number_of_bytes&.positive?
55
+ return nil if maxlen&.positive?
40
56
 
41
57
  return ''
42
58
  end
43
59
 
44
60
  @pos += tbuf.length
45
61
 
46
- if buf
47
- buf.replace(tbuf)
62
+ if out_string.nil?
63
+ tbuf.force_encoding(Encoding::ASCII_8BIT)
48
64
  else
49
- buf = tbuf
65
+ encoding = out_string.encoding
66
+ out_string.replace(tbuf).force_encoding(encoding)
50
67
  end
51
- buf
52
68
  end
53
69
 
54
- def readlines(a_sep_string = $INPUT_RECORD_SEPARATOR)
55
- ret_val = []
56
- each_line(a_sep_string) { |line| ret_val << line }
57
- ret_val
70
+ # Reads and returns all remaining lines from the stream. See the Line IO
71
+ # documentation in the IO class for more information.
72
+ #
73
+ # With no arguments given, returns lines as determined by line
74
+ # separator `$/`, or `nil` if none.
75
+ #
76
+ # With only string argument `sep` given, returns lines as
77
+ # determined by line separator `sep`, or `nil` if none. See the
78
+ # Line Separator documentation in the IO class for more information.
79
+ # The two special values for `sep` (`nil` and `""`) are honoured.
80
+ #
81
+ # With only integer argument `limit` given, limits the number of bytes
82
+ # in each line; see the Line Limit documentation in the IO class for more
83
+ # information.
84
+ #
85
+ # With arguments `sep` and `limit` given, combines the two behaviors.
86
+ #
87
+ # Optional keyword argument `chomp` specifies whether line separators
88
+ # are to be omitted.
89
+ def readlines(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
90
+ each(sep, limit, chomp: chomp).to_a
58
91
  end
59
92
 
60
- def gets(a_sep_string = $INPUT_RECORD_SEPARATOR, number_of_bytes = nil)
61
- @lineno = @lineno.next
93
+ # Reads and returns a line from the stream. See the Line IO
94
+ # documentation in the IO class for more information.
95
+ #
96
+ # With no arguments given, returns the next line as determined by line
97
+ # separator `$/`, or `nil` if none.
98
+ #
99
+ # With only string argument `sep` given, returns the next line as
100
+ # determined by line separator `sep`, or `nil` if none. See the
101
+ # Line Separator documentation in the IO class for more information.
102
+ # The two special values for `sep` (`nil` and `""`) are honoured.
103
+ #
104
+ # With only integer argument `limit` given, limits the number of bytes
105
+ # in the line; see the Line Limit documentation in the IO class for more
106
+ # information.
107
+ #
108
+ # With arguments `sep` and `limit` given, combines the two behaviors.
109
+ #
110
+ # Optional keyword argument `chomp` specifies whether line separators
111
+ # are to be omitted.
112
+ def gets(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
113
+ if sep.nil?
114
+ return nil if eof?
115
+
116
+ @lineno = @lineno.next
117
+ return read(limit)
118
+ end
62
119
 
63
- if number_of_bytes.respond_to?(:to_int)
64
- number_of_bytes = number_of_bytes.to_int
65
- a_sep_string = a_sep_string.to_str if a_sep_string
66
- elsif a_sep_string.respond_to?(:to_int)
67
- number_of_bytes = a_sep_string.to_int
68
- a_sep_string = $INPUT_RECORD_SEPARATOR
69
- else
70
- number_of_bytes = nil
71
- a_sep_string = a_sep_string.to_str if a_sep_string
120
+ if sep.respond_to?(:to_int)
121
+ limit = sep.to_int
122
+ sep = $INPUT_RECORD_SEPARATOR
123
+ elsif sep&.empty?
124
+ sep = "#{$INPUT_RECORD_SEPARATOR}#{$INPUT_RECORD_SEPARATOR}"
72
125
  end
73
126
 
74
- return read(number_of_bytes) if a_sep_string.nil?
127
+ buffer_index = 0
128
+ while (sep_index = @output_buffer.index(sep, buffer_index)).nil?
129
+ break if limit && @output_buffer.bytesize >= limit
75
130
 
76
- a_sep_string = "#{$INPUT_RECORD_SEPARATOR}#{$INPUT_RECORD_SEPARATOR}" if a_sep_string.empty?
131
+ if input_finished?
132
+ return nil if @output_buffer.empty?
77
133
 
78
- buffer_index = 0
79
- over_limit = number_of_bytes && @output_buffer.bytesize >= number_of_bytes
80
- while (match_index = @output_buffer.index(a_sep_string, buffer_index)).nil? && !over_limit
81
- buffer_index = [buffer_index, @output_buffer.bytesize - a_sep_string.bytesize].max
82
- return @output_buffer.empty? ? nil : flush if input_finished?
134
+ @lineno = @lineno.next
135
+ @pos += @output_buffer.bytesize
136
+ return @output_buffer.slice!(0..)
137
+ end
83
138
 
139
+ buffer_index = [buffer_index, @output_buffer.bytesize - sep.bytesize].max
84
140
  @output_buffer << produce_input
85
- over_limit = number_of_bytes && @output_buffer.bytesize >= number_of_bytes
86
141
  end
87
- sep_index = [
88
- match_index + a_sep_string.bytesize,
89
- number_of_bytes || @output_buffer.bytesize
90
- ].min
91
- @pos += sep_index
92
- @output_buffer.slice!(0...sep_index)
142
+
143
+ limit ||= @output_buffer.bytesize
144
+ cut_index = sep_index ? [sep_index + sep.bytesize, limit].min : limit
145
+ @lineno = @lineno.next
146
+ @pos += cut_index
147
+ chomp ? @output_buffer.slice!(0, cut_index).chomp(sep) : @output_buffer.slice!(0, cut_index)
93
148
  end
94
149
 
95
- def ungetc(byte)
150
+ def ungetc(byte) # :nodoc:
96
151
  @output_buffer = byte.chr + @output_buffer
97
152
  end
98
153
 
99
- def flush
100
- ret_val = @output_buffer
101
- @output_buffer = +''
102
- ret_val
154
+ def flush # :nodoc:
155
+ @output_buffer.slice!(0..)
103
156
  end
104
157
 
105
- def readline(a_sep_string = $INPUT_RECORD_SEPARATOR)
106
- ret_val = gets(a_sep_string)
107
- raise EOFError unless ret_val
158
+ # Reads a line as with #gets, but raises `EOFError` if already at
159
+ # end-of-stream.
160
+ #
161
+ # Optional keyword argument `chomp` specifies whether line separators
162
+ # are to be omitted.
163
+ def readline(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
164
+ raise EOFError if eof?
108
165
 
109
- ret_val
166
+ gets(sep, limit, chomp: chomp)
110
167
  end
111
168
 
112
- def each_line(a_sep_string = $INPUT_RECORD_SEPARATOR)
113
- loop { yield readline(a_sep_string) }
114
- rescue EOFError
115
- # We just need to catch this; we don't need to handle it.
169
+ # Calls the block with each remaining line read from the stream.
170
+ # Does nothing if already at end-of-stream. See the Line IO
171
+ # documentation in the IO class for more information.
172
+ #
173
+ # With no arguments given, reads lines as determined by line separator
174
+ # `$/`. With only string argument `sep` given, reads lines as determined
175
+ # by line separator `sep`. See the Line Separator documentation in the
176
+ # IO class for more information. The two special values for `sep`
177
+ # (`nil` and `""`) are honoured.
178
+ #
179
+ # With only integer argument `limit` given, limits the number of bytes
180
+ # in each line; see the Line Limit documentation in the IO class for
181
+ # more information.
182
+ #
183
+ # With arguments `sep` and `limit` given, combines the two behaviors.
184
+ #
185
+ # Optional keyword argument `chomp` specifies whether line separators
186
+ # are to be omitted.
187
+ #
188
+ # Returns an `Enumerator` if no block is given.
189
+ def each(sep = $INPUT_RECORD_SEPARATOR, limit = nil, chomp: false)
190
+ return to_enum(:each, sep, limit, chomp: chomp) unless block_given?
191
+
192
+ while (line = gets(sep, limit, chomp: chomp))
193
+ yield line
194
+ end
116
195
  end
117
196
 
118
- alias each each_line
197
+ alias each_line each
119
198
 
199
+ # Returns `true` if the stream is positioned at its end, `false`
200
+ # otherwise. See Position documentation in the IO class for more
201
+ # information.
120
202
  def eof?
121
203
  @output_buffer.empty? && input_finished?
122
204
  end
123
205
 
124
206
  # Alias for compatibility. Remove for version 4.
125
- alias eof eof?
207
+ alias eof eof? # :nodoc:
126
208
  end
127
209
  end
128
210
  end
@@ -7,15 +7,15 @@ module Zip
7
7
  @read_so_far = 0
8
8
  end
9
9
 
10
- def read(length = nil, outbuf = +'')
11
- return (length.nil? || length.zero? ? '' : nil) if eof?
10
+ def read(maxlen = nil)
11
+ return (maxlen.nil? || maxlen.zero? ? '' : nil) if eof?
12
12
 
13
- if length.nil? || (@read_so_far + length) > decompressed_size
14
- length = decompressed_size - @read_so_far
13
+ if maxlen.nil? || (@read_so_far + maxlen) > decompressed_size
14
+ maxlen = decompressed_size - @read_so_far
15
15
  end
16
16
 
17
- @read_so_far += length
18
- input_stream.read(length, outbuf)
17
+ @read_so_far += maxlen
18
+ input_stream.read(maxlen)
19
19
  end
20
20
 
21
21
  def eof?
data/lib/zip/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Zip
4
4
  # The version of the Rubyzip library.
5
- VERSION = '3.2.2'
5
+ VERSION = '3.3.0'
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyzip
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.2
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Haines
@@ -195,9 +195,9 @@ licenses:
195
195
  - BSD-2-Clause
196
196
  metadata:
197
197
  bug_tracker_uri: https://github.com/rubyzip/rubyzip/issues
198
- changelog_uri: https://github.com/rubyzip/rubyzip/blob/v3.2.2/Changelog.md
199
- documentation_uri: https://www.rubydoc.info/gems/rubyzip/3.2.2
200
- source_code_uri: https://github.com/rubyzip/rubyzip/tree/v3.2.2
198
+ changelog_uri: https://github.com/rubyzip/rubyzip/blob/v3.3.0/Changelog.md
199
+ documentation_uri: https://www.rubydoc.info/gems/rubyzip/3.3.0
200
+ source_code_uri: https://github.com/rubyzip/rubyzip/tree/v3.3.0
201
201
  wiki_uri: https://github.com/rubyzip/rubyzip/wiki
202
202
  rubygems_mfa_required: 'true'
203
203
  rdoc_options: []