zip_tricks 5.2.0 → 5.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: b686d9ddb071cdb677559d12879dfa88a77ac63ce6eabad99ce2d8e674c80c28
4
- data.tar.gz: fc8d6382de69962cf2cb7585b9a7476e7694abaca77cf969f2cf9b905076d59c
3
+ metadata.gz: 96ce140b83c0f42fa013278de28cf3c2668fcec1bf1150951c6e6a6a00291125
4
+ data.tar.gz: f5f3fbca524376d336c04dd3f2e728bc7315187f70cefcaf97958a247b19e95d
5
5
  SHA512:
6
- metadata.gz: 2949e24de0a8bc731ed0ae4d5fc30534e97ad1102f11c07a353ab211c2948bc1957c35527b4cb00eeb5751713d17a7653adbf65ea188b4fa9d0b0f0567926afe
7
- data.tar.gz: 54972bf28272c68ce9bcada1f872840de04f7ae9a9e0276abeceb828373639f3ae9a999e788401937454d0354d86b87e462d4b60554e2478e7d8a7e6ac9c1348
6
+ metadata.gz: 9c794589191612f8ceba1fbef03e801a9610a7d29b5593a7d4ceecd27466706de10aa44ceb152771a13fc3aba42b38a8989104704284c4bfd18b14eb3d5828af
7
+ data.tar.gz: 65e8f07cd490e0a40a6770064eb76d85d9b43177f5fed7b5c46afa68d9347ae67f4c22e140924a9ae476932856e8ed9cd732d217ee5d7cc643971dac8b92729e
data/.rubocop.yml CHANGED
@@ -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
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 5.3
2
+
3
+ * 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
4
+ 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
5
+ Streamer offset. The unadjusted offset would then produce incorrect values in both the local headers which come after the missing
6
+ offset adjustment _and_ in the central directory headers. Some ZIP unarchivers are able to recover from this (ones that read
7
+ files "straight-ahead" but others aren't - if the ZIP unarchiver uses central directory entries it would be using incorrect offsets.
8
+ Instead of producing an invalid ZIP, raise an exception which explains what happened and how it can be resolved.
9
+
1
10
  ## 5.2
2
11
 
3
12
  * Remove `Streamer#add_compressed_entry` and `SizeEstimator#add_compressed_entry`
@@ -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
@@ -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
@@ -360,14 +360,16 @@ class ZipTricks::Streamer
360
360
  #
361
361
  # @return [Integer] the offset the output IO is at after closing the archive
362
362
  def close
363
+ # Make sure offsets are in order
364
+ verify_offsets!
365
+
363
366
  # Record the central directory offset, so that it can be written into the EOCD record
364
367
  cdir_starts_at = @out.tell
365
368
 
366
369
  # Write out the central directory entries, one for each file
367
- @files.each_with_index do |entry, i|
368
- header_loc = @local_header_offsets.fetch(i)
370
+ @files.each do |entry|
369
371
  @writer.write_central_directory_file_header(io: @out,
370
- local_file_header_location: header_loc,
372
+ local_file_header_location: entry.local_header_offset,
371
373
  gp_flags: entry.gp_flags,
372
374
  storage_mode: entry.storage_mode,
373
375
  compressed_size: entry.compressed_size,
@@ -420,15 +422,40 @@ class ZipTricks::Streamer
420
422
  last_entry.compressed_size = compressed_size
421
423
  last_entry.uncompressed_size = uncompressed_size
422
424
 
425
+ offset_before_data_descriptor = @out.tell
423
426
  @writer.write_data_descriptor(io: @out,
424
427
  crc32: last_entry.crc32,
425
428
  compressed_size: last_entry.compressed_size,
426
429
  uncompressed_size: last_entry.uncompressed_size)
430
+ last_entry.bytes_used_for_data_descriptor = @out.tell - offset_before_data_descriptor
431
+
427
432
  @out.tell
428
433
  end
429
434
 
430
435
  private
431
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
+
432
459
  def add_file_and_write_local_header(
433
460
  filename:,
434
461
  modification_time:,
@@ -461,16 +488,18 @@ class ZipTricks::Streamer
461
488
  uncompressed_size = 0
462
489
  end
463
490
 
491
+ local_header_starts_at = @out.tell
492
+
464
493
  e = Entry.new(filename,
465
494
  crc32,
466
495
  compressed_size,
467
496
  uncompressed_size,
468
497
  storage_mode,
469
498
  modification_time,
470
- use_data_descriptor)
471
-
472
- @files << e
473
- @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)
474
503
 
475
504
  @writer.write_local_file_header(io: @out,
476
505
  gp_flags: e.gp_flags,
@@ -480,6 +509,9 @@ class ZipTricks::Streamer
480
509
  mtime: e.mtime,
481
510
  filename: e.filename,
482
511
  storage_mode: e.storage_mode)
512
+ e.bytes_used_for_local_header = @out.tell - e.local_header_offset
513
+
514
+ @files << e
483
515
  end
484
516
 
485
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.2.0'
4
+ VERSION = '5.3.0'
5
5
  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.2.0
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov