zip_tricks 5.2.0 → 5.3.0

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: 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