zip_tricks 5.0.0 → 5.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 327b846ccfc7d19442d21e145a2f767da91f0547381afe52be27db6709dee4a4
4
- data.tar.gz: 4d80718a715063f1cebad32471ab83e77d38a805f3ceed82793b922d0d3a877f
3
+ metadata.gz: a9e1ad9dd7452add6a2f9187edecdd366699d81fba9260692ce2a96003401eca
4
+ data.tar.gz: 1f2665b7c3a5c99ce190b5dab76fa9334ac4c6a3307928bab94758acd98bcb56
5
5
  SHA512:
6
- metadata.gz: 982bc310eb658d67262c28efb043f1ddd3957db197ef1bcbe9108f3572431211069843221d36cf9b28231617d899249ad06be5b9c756189e8f0d383b168a340f
7
- data.tar.gz: 40fdde09881e02b5a816dae826554e14d76fb26a1bcf09cfcd053450668b34e2d3c63dc5bbf84417b6bdc44f021a35d38fdabd08d9ace5aabe1ff6c335b65e49
6
+ metadata.gz: 8864d6d56796e49e2024e46aee7cd1ac1c8ea575d757a6fbe6fd62a6e77db94eef6dc5b80bdbeae5bb932536e889fbdab224642862452f5b1acc0020e8a8c906
7
+ data.tar.gz: 67bc05db334c9a54804ac460f14f5919b9049913ee75532da090405f9a5b1f7e792ff4678aec0683a8b2a3136a9799ea3dd88aa3c9995491b28e4e6d88c4ac3a
@@ -11,3 +11,5 @@ Style/GlobalVars:
11
11
  - qa/*.rb
12
12
  - spec/spec_helper.rb
13
13
  - spec/support/zip_inspection.rb
14
+ Layout/IndentHeredoc:
15
+ Enabled: false
@@ -1,10 +1,6 @@
1
1
  rvm:
2
- - 2.1
3
- - 2.2.10
4
- - 2.3.7
5
- - 2.4.4
6
- - 2.5.4
7
- - 2.6.2
2
+ - 2.1.0
3
+ - 2.6.5
8
4
  - jruby-9.0
9
5
  sudo: false
10
6
  cache: bundler
@@ -1,3 +1,32 @@
1
+ ## 5.3.1
2
+
3
+ * Fix extended timestamp timestamp value encoding. Previously we would use an incorrect encoding for the timestamp value, which would output correct but nonsensical timestamps. The pack specifier is now changed to output the correct value.
4
+
5
+ ## 5.3.0
6
+
7
+ * Raise in `Streamer#close` when the IO offset of the Streamer does not match the size of the written entries. This is a situation which
8
+ can occur if one adds the local headers, writes the bodies of the files to the socket/output directly, and forgets to adjust the internal
9
+ Streamer offset. The unadjusted offset would then produce incorrect values in both the local headers which come after the missing
10
+ offset adjustment _and_ in the central directory headers. Some ZIP unarchivers are able to recover from this (ones that read
11
+ files "straight-ahead" but others aren't - if the ZIP unarchiver uses central directory entries it would be using incorrect offsets.
12
+ Instead of producing an invalid ZIP, raise an exception which explains what happened and how it can be resolved.
13
+
14
+ ## 5.2.0
15
+
16
+ * Remove `Streamer#add_compressed_entry` and `SizeEstimator#add_compressed_entry`
17
+
18
+ ## 5.1.1
19
+
20
+ * Fix extended timestamp extra field output. The first bit of the flag would be set instead of the last bit of
21
+ the flag, which made it impossible for Rubyzip to read the timestamp of the entry - and it would also make
22
+ the extra field useless for most reading applications.
23
+
24
+ ## 5.1.0
25
+
26
+ * Slightly rework `RemoteIO` and `RemoteUncap` and make sure they work correctly by spinning up a test webserver
27
+ to verify their operation. The changes to the documented API are fairly small so this is still marked as a minor
28
+ release.
29
+
1
30
  ## 5.0.0
2
31
 
3
32
  * Disable automatic filename deduplication by default, because it does not play nice with file/directory
@@ -19,16 +48,16 @@
19
48
 
20
49
  ## 4.7.4
21
50
 
22
- * Use a single fixed capacity string in StreamCRC32.from_io to avoid unnecessary allocations
51
+ * Use a single fixed capacity string in `StreamCRC32.from_io` to avoid unnecessary allocations
23
52
  * Fix a few tests that were calling out to external binaries
24
53
 
25
54
  ## 4.7.3
26
55
 
27
- * Fix RemoteUncap#request_object_size to function correctly
56
+ * Fix `RemoteUncap#request_object_size` to function correctly
28
57
 
29
58
  ## 4.7.2
30
59
 
31
- * Relax bundler dependency so that both 1.x and 2.x are supported cleanly
60
+ * Relax bundler dependency so that both bundler 1.x and 2.x are supported cleanly
32
61
 
33
62
  ## 4.7.1
34
63
 
@@ -82,7 +82,9 @@ class ZipTricks::FileReader
82
82
 
83
83
  private_constant :StoredReader, :InflatingReader
84
84
 
85
- # Represents a file within the ZIP archive being read
85
+ # Represents a file within the ZIP archive being read. This is different from
86
+ # the Entry object used in Streamer for ZIP writing, since during writing more
87
+ # data can be kept in memory for immediate use.
86
88
  class ZipEntry
87
89
  # @return [Fixnum] bit-packed version signature of the program that made the archive
88
90
  attr_accessor :made_by
@@ -8,14 +8,16 @@
8
8
  # actual fetches over HTTP and an object that expects a handful of IO methods to be
9
9
  # available.
10
10
  class ZipTricks::RemoteIO
11
- # @param fetcher[#request_object_size, #request_range] an object that perform fetches
12
- def initialize(fetcher = :NOT_SET)
11
+ # @param url[String, URI] the HTTP/HTTPS URL of the object to be retrieved
12
+ def initialize(url)
13
13
  @pos = 0
14
- @fetcher = fetcher
15
- @remote_size = false
14
+ @uri = URI(url)
15
+ @remote_size = nil
16
16
  end
17
17
 
18
18
  # Emulates IO#seek
19
+ # @param offset[Integer] absolute offset in the remote resource to seek to
20
+ # @param mode[Integer] The seek mode (only SEEK_SET is supported)
19
21
  def seek(offset, mode = IO::SEEK_SET)
20
22
  raise "Unsupported read mode #{mode}" unless mode == IO::SEEK_SET
21
23
  @remote_size ||= request_object_size
@@ -25,7 +27,7 @@ class ZipTricks::RemoteIO
25
27
 
26
28
  # Emulates IO#size.
27
29
  #
28
- # @return [Fixnum] the size of the remote resource
30
+ # @return [Integer] the size of the remote resource
29
31
  def size
30
32
  @remote_size ||= request_object_size
31
33
  end
@@ -39,21 +41,20 @@ class ZipTricks::RemoteIO
39
41
  # @param n_bytes[Fixnum, nil] how many bytes to read, or `nil` to read all the way to the end
40
42
  # @return [String] the read bytes
41
43
  def read(n_bytes = nil)
42
- @remote_size ||= request_object_size
43
-
44
44
  # If the resource is empty there is nothing to read
45
- return if @remote_size.zero?
45
+ return if size.zero?
46
46
 
47
- maximum_avaialable = @remote_size - @pos
47
+ maximum_avaialable = size - @pos
48
48
  n_bytes ||= maximum_avaialable # nil == read to the end of file
49
49
  return '' if n_bytes.zero?
50
50
  raise ArgumentError, "No negative reads(#{n_bytes})" if n_bytes < 0
51
51
 
52
52
  n_bytes = clamp(0, n_bytes, maximum_avaialable)
53
53
 
54
- read_n_bytes_from_remote(@pos, n_bytes).tap do |data|
54
+ http_range = (@pos..(@pos + n_bytes - 1))
55
+ request_range(http_range).tap do |data|
55
56
  raise "Remote read returned #{data.bytesize} bytes instead of #{n_bytes} as requested" if data.bytesize != n_bytes
56
- @pos = clamp(0, @pos + data.bytesize, @remote_size)
57
+ @pos = clamp(0, @pos + data.bytesize, size)
57
58
  end
58
59
  end
59
60
 
@@ -66,23 +67,41 @@ class ZipTricks::RemoteIO
66
67
 
67
68
  protected
68
69
 
70
+ # Only used internally when reading the remote ZIP.
71
+ #
72
+ # @param range[Range] the HTTP range of data to fetch from remote
73
+ # @return [String] the response body of the ranged request
69
74
  def request_range(range)
70
- @fetcher.request_range(range)
75
+ http = Net::HTTP.start(@uri.hostname, @uri.port)
76
+ request = Net::HTTP::Get.new(@uri)
77
+ request.range = range
78
+ response = http.request(request)
79
+ case response.code
80
+ when "206", "200"
81
+ response.body
82
+ else
83
+ raise "Remote at #{@uri} replied with code #{response.code}"
84
+ end
71
85
  end
72
86
 
87
+ # For working with S3 it is a better idea to perform a GET request for one byte, since doing a HEAD
88
+ # request needs a different permission - and standard GET presigned URLs are not allowed to perform it
89
+ #
90
+ # @return [Integer] the size of the remote resource, parsed either from Content-Length or Content-Range header
73
91
  def request_object_size
74
- @fetcher.request_object_size
75
- end
76
-
77
- # Reads N bytes at offset from remote
78
- def read_n_bytes_from_remote(start_at, n_bytes)
79
- range = (start_at..(start_at + n_bytes - 1))
80
- request_range(range)
81
- end
82
-
83
- # Reads the Content-Length and caches it
84
- def remote_size
85
- @remote_size ||= request_object_size
92
+ http = Net::HTTP.start(@uri.hostname, @uri.port)
93
+ request = Net::HTTP::Get.new(@uri)
94
+ request.range = 0..0
95
+ response = http.request(request)
96
+ case response.code
97
+ when "206"
98
+ content_range_header_value = response['Content-Range']
99
+ content_range_header_value.split('/').last.to_i
100
+ when "200"
101
+ response['Content-Length'].to_i
102
+ else
103
+ raise "Remote at #{@uri} replied with code #{response.code}"
104
+ end
86
105
  end
87
106
 
88
107
  private
@@ -7,42 +7,16 @@
7
7
  #
8
8
  # Please read the security warning in `FileReader` _VERY CAREFULLY_
9
9
  # before you use this module.
10
- class ZipTricks::RemoteUncap
10
+ module ZipTricks::RemoteUncap
11
11
  # @param uri[String] the HTTP(S) URL to read the ZIP footer from
12
12
  # @param reader_class[Class] which class to use for reading
13
13
  # @param options_for_zip_reader[Hash] any additional options to give to
14
14
  # {ZipTricks::FileReader} when reading
15
15
  # @return [Array<ZipTricks::FileReader::ZipEntry>] metadata about the
16
16
  # files within the remote archive
17
- def self.files_within_zip_at(uri,
18
- reader_class: ZipTricks::FileReader,
19
- **options_for_zip_reader)
20
- fetcher = new(uri)
21
- fake_io = ZipTricks::RemoteIO.new(fetcher)
17
+ def self.files_within_zip_at(uri, reader_class: ZipTricks::FileReader, **options_for_zip_reader)
18
+ fake_io = ZipTricks::RemoteIO.new(uri)
22
19
  reader = reader_class.new
23
20
  reader.read_zip_structure(io: fake_io, **options_for_zip_reader)
24
21
  end
25
-
26
- def initialize(uri)
27
- @uri = URI(uri)
28
- end
29
-
30
- # Only used internally when reading the remote ZIP.
31
- #
32
- # @param range[Range] the HTTP range of data to fetch from remote
33
- # @return [String] the response body of the ranged request
34
- def request_range(range)
35
- request = Net::HTTP::Get.new(@uri)
36
- request.range = range
37
- http = Net::HTTP.start(@uri.hostname, @uri.port)
38
- http.request(request).body
39
- end
40
-
41
- # Only used internally when reading the remote ZIP.
42
- #
43
- # @return [Fixnum] the byte size of the ranged request
44
- def request_object_size
45
- http = Net::HTTP.start(@uri.hostname, @uri.port)
46
- http.request_head(@uri)['Content-Length'].to_i
47
- end
48
22
  end
@@ -73,9 +73,6 @@ class ZipTricks::SizeEstimator
73
73
  self
74
74
  end
75
75
 
76
- # Will be phased out in ZipTricks 5.x
77
- alias_method :add_compressed_entry, :add_deflated_entry
78
-
79
76
  # Add an empty directory to the archive.
80
77
  #
81
78
  # @param dirname [String] the name of the directory
@@ -91,6 +91,7 @@ class ZipTricks::Streamer
91
91
  InvalidOutput = Class.new(ArgumentError)
92
92
  Overflow = Class.new(StandardError)
93
93
  UnknownMode = Class.new(StandardError)
94
+ OffsetOutOfSync = Class.new(StandardError)
94
95
 
95
96
  private_constant :DeflatedWriter, :StoredWriter, :STORED, :DEFLATED
96
97
 
@@ -149,7 +150,6 @@ class ZipTricks::Streamer
149
150
  @dedupe_filenames = auto_rename_duplicate_filenames
150
151
  @out = ZipTricks::WriteAndTell.new(stream)
151
152
  @files = []
152
- @local_header_offsets = []
153
153
  @path_set = ZipTricks::PathSet.new
154
154
  @writer = writer
155
155
  end
@@ -213,9 +213,6 @@ class ZipTricks::Streamer
213
213
  @out.tell
214
214
  end
215
215
 
216
- # Will be phased out in ZipTricks 5.x
217
- alias_method :add_compressed_entry, :add_deflated_entry
218
-
219
216
  # Writes out the local header for an entry (file in the ZIP) that is using
220
217
  # the stored storage model (is stored as-is).
221
218
  # Once this method is called, the `<<` method has to be called one or more
@@ -363,14 +360,16 @@ class ZipTricks::Streamer
363
360
  #
364
361
  # @return [Integer] the offset the output IO is at after closing the archive
365
362
  def close
363
+ # Make sure offsets are in order
364
+ verify_offsets!
365
+
366
366
  # Record the central directory offset, so that it can be written into the EOCD record
367
367
  cdir_starts_at = @out.tell
368
368
 
369
369
  # Write out the central directory entries, one for each file
370
- @files.each_with_index do |entry, i|
371
- header_loc = @local_header_offsets.fetch(i)
370
+ @files.each do |entry|
372
371
  @writer.write_central_directory_file_header(io: @out,
373
- local_file_header_location: header_loc,
372
+ local_file_header_location: entry.local_header_offset,
374
373
  gp_flags: entry.gp_flags,
375
374
  storage_mode: entry.storage_mode,
376
375
  compressed_size: entry.compressed_size,
@@ -423,15 +422,40 @@ class ZipTricks::Streamer
423
422
  last_entry.compressed_size = compressed_size
424
423
  last_entry.uncompressed_size = uncompressed_size
425
424
 
425
+ offset_before_data_descriptor = @out.tell
426
426
  @writer.write_data_descriptor(io: @out,
427
427
  crc32: last_entry.crc32,
428
428
  compressed_size: last_entry.compressed_size,
429
429
  uncompressed_size: last_entry.uncompressed_size)
430
+ last_entry.bytes_used_for_data_descriptor = @out.tell - offset_before_data_descriptor
431
+
430
432
  @out.tell
431
433
  end
432
434
 
433
435
  private
434
436
 
437
+ def verify_offsets!
438
+ # We need to check whether the offsets noted for the entries actually make sense
439
+ computed_offset = @files.map(&:total_bytes_used).inject(0, &:+)
440
+ actual_offset = @out.tell
441
+ if computed_offset != actual_offset
442
+ message = <<-EMS
443
+ The offset of the Streamer output IO is out of sync with the expected value. All entries written so far,
444
+ including their compressed bodies, local headers and data descriptors, add up to a certain offset,
445
+ but this offset does not match the actual offset of the IO.
446
+
447
+ Entries add up to #{computed_offset} bytes and the IO is at #{actual_offset} bytes.
448
+
449
+ This can happen if you write local headers for an entry, write the "body" of the entry directly to the IO
450
+ object which is your destination, but do not adjust the offset known to the Streamer object. To adjust
451
+ the offfset you need to call `Streamer#simulate_write(body_size)` after outputting the entry. Otherwise
452
+ the local header offsets of the entries you write are going to be incorrect and some ZIP applications
453
+ are going to have problems opening your archive.
454
+ EMS
455
+ raise OffsetOutOfSync, message
456
+ end
457
+ end
458
+
435
459
  def add_file_and_write_local_header(
436
460
  filename:,
437
461
  modification_time:,
@@ -464,16 +488,18 @@ class ZipTricks::Streamer
464
488
  uncompressed_size = 0
465
489
  end
466
490
 
491
+ local_header_starts_at = @out.tell
492
+
467
493
  e = Entry.new(filename,
468
494
  crc32,
469
495
  compressed_size,
470
496
  uncompressed_size,
471
497
  storage_mode,
472
498
  modification_time,
473
- use_data_descriptor)
474
-
475
- @files << e
476
- @local_header_offsets << @out.tell
499
+ use_data_descriptor,
500
+ _local_file_header_offset = local_header_starts_at,
501
+ _bytes_used_for_local_header = 0,
502
+ _bytes_used_for_data_descriptor = 0)
477
503
 
478
504
  @writer.write_local_file_header(io: @out,
479
505
  gp_flags: e.gp_flags,
@@ -483,6 +509,9 @@ class ZipTricks::Streamer
483
509
  mtime: e.mtime,
484
510
  filename: e.filename,
485
511
  storage_mode: e.storage_mode)
512
+ e.bytes_used_for_local_header = @out.tell - e.local_header_offset
513
+
514
+ @files << e
486
515
  end
487
516
 
488
517
  def remove_backslash(filename)
@@ -4,7 +4,7 @@
4
4
  # Normally you will not have to use this class directly
5
5
  class ZipTricks::Streamer::Entry < Struct.new(:filename, :crc32, :compressed_size,
6
6
  :uncompressed_size, :storage_mode, :mtime,
7
- :use_data_descriptor)
7
+ :use_data_descriptor, :local_header_offset, :bytes_used_for_local_header, :bytes_used_for_data_descriptor)
8
8
  def initialize(*)
9
9
  super
10
10
  filename.force_encoding(Encoding::UTF_8)
@@ -15,6 +15,10 @@ class ZipTricks::Streamer::Entry < Struct.new(:filename, :crc32, :compressed_siz
15
15
  end)
16
16
  end
17
17
 
18
+ def total_bytes_used
19
+ bytes_used_for_local_header + compressed_size + bytes_used_for_data_descriptor
20
+ end
21
+
18
22
  # Set the general purpose flags for the entry. We care about is the EFS
19
23
  # bit (bit 11) which should be set if the filename is UTF8. If it is, we need to set the
20
24
  # bit so that the unarchiving application knows that the filename in the archive is UTF-8
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZipTricks
4
- VERSION = '5.0.0'
4
+ VERSION = '5.3.1'
5
5
  end
@@ -57,7 +57,7 @@ class ZipTricks::ZipWriter
57
57
  C_UINT2 = 'v' # Encode a 2-byte unsigned little-endian uint
58
58
  C_UINT8 = 'Q<' # Encode an 8-byte unsigned little-endian uint
59
59
  C_CHAR = 'C' # For bit-encoded strings
60
- C_INT4 = 'N' # Encode a 4-byte signed little-endian int
60
+ C_INT4 = 'l<' # Encode a 4-byte signed little-endian int
61
61
 
62
62
  private_constant :FOUR_BYTE_MAX_UINT,
63
63
  :TWO_BYTE_MAX_UINT,
@@ -118,7 +118,7 @@ class ZipTricks::ZipWriter
118
118
  if requires_zip64
119
119
  extra_fields << zip_64_extra_for_local_file_header(compressed_size: compressed_size, uncompressed_size: uncompressed_size)
120
120
  end
121
- extra_fields << timestamp_extra(mtime)
121
+ extra_fields << timestamp_extra_for_local_file_header(mtime)
122
122
 
123
123
  io << [extra_fields.size].pack(C_UINT2) # extra field length 2 bytes
124
124
 
@@ -182,7 +182,7 @@ class ZipTricks::ZipWriter
182
182
  compressed_size: compressed_size,
183
183
  uncompressed_size: uncompressed_size)
184
184
  end
185
- extra_fields << timestamp_extra(mtime)
185
+ extra_fields << timestamp_extra_for_central_directory_entry(mtime)
186
186
 
187
187
  io << [extra_fields.size].pack(C_UINT2) # extra field length 2 bytes
188
188
 
@@ -345,12 +345,14 @@ class ZipTricks::ZipWriter
345
345
  pack_array(data_and_packspecs)
346
346
  end
347
347
 
348
- # Writes the extended timestamp information field. The spec defines 2
348
+ # Writes the extended timestamp information field for local headers.
349
+ #
350
+ # The spec defines 2
349
351
  # different formats - the one for the local file header can also accomodate the
350
352
  # atime and ctime, whereas the one for the central directory can only take
351
353
  # the mtime - and refers the reader to the local header extra to obtain the
352
354
  # remaining times
353
- def timestamp_extra(mtime)
355
+ def timestamp_extra_for_local_file_header(mtime)
354
356
  # Local-header version:
355
357
  #
356
358
  # Value Size Description
@@ -378,16 +380,21 @@ class ZipTricks::ZipWriter
378
380
  # bit 1 if set, access time is present
379
381
  # bit 2 if set, creation time is present
380
382
  # bits 3-7 reserved for additional timestamps; not set
381
- flags = 0b10000000 # Set bit 1 only to indicate only mtime is present
383
+ flags = 0b00000001 # Set the lowest bit only, to indicate that only mtime is present
382
384
  data_and_packspecs = [
383
385
  0x5455, C_UINT2, # tag for this extra block type ("UT")
384
- (1 + 4), C_UINT2, # the size of this block (1 byte used for the Flag + 1 long used for the timestamp)
386
+ (1 + 4), C_UINT2, # the size of this block (1 byte used for the Flag + 3 longs used for the timestamp)
385
387
  flags, C_CHAR, # encode a single byte
386
- mtime.utc.to_i, C_INT4, # Use a signed long, not the unsigned one used by the rest of the ZIP spec.
388
+ mtime.utc.to_i, C_INT4, # Use a signed int, not the unsigned one used by the rest of the ZIP spec.
387
389
  ]
390
+ # The atime and ctime can be omitted if not present
388
391
  pack_array(data_and_packspecs)
389
392
  end
390
393
 
394
+ # Since we do not supply atime or ctime, the contents of the two extra fields (central dir and local header)
395
+ # is exactly the same, so we can use a method alias.
396
+ alias_method :timestamp_extra_for_central_directory_entry, :timestamp_extra_for_local_file_header
397
+
391
398
  # Writes the Zip64 extra field for the central directory header.It differs from the extra used in the local file header because it
392
399
  # also contains the location of the local file header in the ZIP as an 8-byte int.
393
400
  #
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.require_paths = ['lib']
32
32
 
33
33
  spec.add_development_dependency 'bundler'
34
- spec.add_development_dependency 'rubyzip', '>= 1.2.2'
34
+ spec.add_development_dependency 'rubyzip', '~> 1'
35
35
  spec.add_development_dependency 'terminal-table'
36
36
  spec.add_development_dependency 'range_utils'
37
37
 
@@ -44,4 +44,5 @@ Gem::Specification.new do |spec|
44
44
  spec.add_development_dependency 'allocation_stats', '~> 0.1.5'
45
45
  spec.add_development_dependency 'yard', '~> 0.9'
46
46
  spec.add_development_dependency 'wetransfer_style', '0.6.0'
47
+ spec.add_development_dependency 'puma'
47
48
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zip_tricks
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 5.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2019-11-01 00:00:00.000000000 Z
14
+ date: 2020-06-16 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -31,16 +31,16 @@ dependencies:
31
31
  name: rubyzip
32
32
  requirement: !ruby/object:Gem::Requirement
33
33
  requirements:
34
- - - ">="
34
+ - - "~>"
35
35
  - !ruby/object:Gem::Version
36
- version: 1.2.2
36
+ version: '1'
37
37
  type: :development
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
- - - ">="
41
+ - - "~>"
42
42
  - !ruby/object:Gem::Version
43
- version: 1.2.2
43
+ version: '1'
44
44
  - !ruby/object:Gem::Dependency
45
45
  name: terminal-table
46
46
  requirement: !ruby/object:Gem::Requirement
@@ -195,6 +195,20 @@ dependencies:
195
195
  - - '='
196
196
  - !ruby/object:Gem::Version
197
197
  version: 0.6.0
198
+ - !ruby/object:Gem::Dependency
199
+ name: puma
200
+ requirement: !ruby/object:Gem::Requirement
201
+ requirements:
202
+ - - ">="
203
+ - !ruby/object:Gem::Version
204
+ version: '0'
205
+ type: :development
206
+ prerelease: false
207
+ version_requirements: !ruby/object:Gem::Requirement
208
+ requirements:
209
+ - - ">="
210
+ - !ruby/object:Gem::Version
211
+ version: '0'
198
212
  description: Stream out ZIP files from Ruby
199
213
  email:
200
214
  - me@julik.nl
@@ -278,7 +292,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
278
292
  - !ruby/object:Gem::Version
279
293
  version: '0'
280
294
  requirements: []
281
- rubygems_version: 3.0.6
295
+ rubygems_version: 3.0.3
282
296
  signing_key:
283
297
  specification_version: 4
284
298
  summary: Stream out ZIP files from Ruby