zip_tricks 4.1.0 → 4.2.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
  SHA1:
3
- metadata.gz: f6d3a4df27461f1235e526d7d40eb089f4df5c64
4
- data.tar.gz: 179df69ea7d164f9164aabcc0c19d951e8d5748f
3
+ metadata.gz: 7a529e0fc4dae54675a7ba880475ec6860ae6c4e
4
+ data.tar.gz: e5ef83a503a377bfa504a9daeeb415367c9c680e
5
5
  SHA512:
6
- metadata.gz: 68131f0f180074731223f145f06d42f65d7ee327bfcc73c7445a9ae2be198ba8ed2372cffb9deba8729c7b3af46eb4cacdeb3ae408fd37a202c848933ce93ffa
7
- data.tar.gz: 9e38d0ce079b7e367b75db3b6ebd032fef11802f12926cf586f061bdb7f059c1087505b2556cfd76062e288601923d51e4c7a1ca5ca5bb928fcae385ee92807b
6
+ metadata.gz: 46d2a64d61873cf3b32889ef99408fab97aeb25eecc7cd76690f40c328c7bdcec9aa6f9d981726a775b79d1746709a35817156221aaf339dc7cda885e58796bc
7
+ data.tar.gz: 458b3a982c1e533d321b2fffebd086125aeb29e0b5e687cfc187e340847200e946ce7944ee237298dae1c1702ef8e3a971ceb53b439e7156f12ae9c7ff995842
@@ -1,5 +1,5 @@
1
1
  module ZipTricks
2
- VERSION = '4.1.0'
2
+ VERSION = '4.2.0'
3
3
 
4
4
  # Require all the sub-components except myself
5
5
  Dir.glob(__dir__ + '/**/*.rb').sort.each {|p| require p unless p == __FILE__ }
@@ -199,10 +199,22 @@ class ZipTricks::FileReader
199
199
  log { 'Located the central directory start at %d' % cdir_location }
200
200
  seek(io, cdir_location)
201
201
 
202
- # Read the entire central directory in one fell swoop
203
- central_directory_str = read_n(io, cdir_size)
202
+ # Read the entire central directory AND anything behind it, in one fell swoop.
203
+ # Strictly speaking, we should be able to read `cdir_size` bytes and not a byte more.
204
+ # However, we know for a fact that in some of our files the central directory size
205
+ # is in fact misreported. `zipinfo` then says:
206
+ #
207
+ # warning [ktsglobal-2b03bc.zip]: 1 extra byte at beginning or within zipfile
208
+ # (attempting to process anyway)
209
+ # error [ktsglobal-2b03bc.zip]: reported length of central directory is
210
+ # -1 bytes too long (Atari STZip zipfile? J.H.Holm ZIPSPLIT 1.1
211
+ # zipfile?). Compensating...
212
+ #
213
+ # Since the EOCD is not that big anyway, we just read the entire "tail" of the ZIP ignoring
214
+ # the central directory size alltogether.
215
+ central_directory_str = io.read # and not read_n(io, cdir_size), see above
204
216
  central_directory_io = StringIO.new(central_directory_str)
205
- log { 'Read %d bytes with central directory entries' % cdir_size }
217
+ log { 'Read %d bytes with central directory + EOCD record and locator' % central_directory_str.bytesize }
206
218
 
207
219
  entries = (0...num_files).map do |entry_n|
208
220
  log { 'Reading the central directory entry %d starting at offset %d' % [entry_n, cdir_location + central_directory_io.tell] }
@@ -70,16 +70,18 @@ class ZipTricks::Streamer
70
70
  #
71
71
  # @param stream [IO] the destination IO for the ZIP (should respond to `tell` and `<<`)
72
72
  # @yield [Streamer] the streamer that can be written to
73
- def self.open(stream)
74
- archive = new(stream)
73
+ def self.open(stream, **kwargs_for_new)
74
+ archive = new(stream, **kwargs_for_new)
75
75
  yield(archive)
76
76
  archive.close
77
77
  end
78
78
 
79
79
  # Creates a new Streamer on top of the given IO-ish object.
80
80
  #
81
- # @param stream [IO] the destination IO for the ZIP (should respond to `<<`)
82
- def initialize(stream)
81
+ # @param stream[IO] the destination IO for the ZIP (should respond to `<<`)
82
+ # @param writer[ZipTricks::ZipWriter] the object to be used as the writer.
83
+ # Defaults to an instance of ZipTricks::ZipWriter, normally you won't need to override it
84
+ def initialize(stream, writer: create_writer)
83
85
  raise InvalidOutput, "The stream must respond to #<<" unless stream.respond_to?(:<<)
84
86
  unless stream.respond_to?(:tell) && stream.respond_to?(:advance_position_by)
85
87
  stream = ZipTricks::WriteAndTell.new(stream)
@@ -88,7 +90,7 @@ class ZipTricks::Streamer
88
90
  @out = stream
89
91
  @files = []
90
92
  @local_header_offsets = []
91
- @writer = create_writer
93
+ @writer = writer
92
94
  end
93
95
 
94
96
  # Writes a part of a zip entry body (actual binary data of the entry) into the output stream.
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
  describe ZipTricks::FileReader do
3
3
 
4
- describe 'with a file without EOCD' do
4
+ context 'with a file without EOCD' do
5
5
  it 'raises the MissingEOCD exception and refuses to read' do
6
6
  f = StringIO.new
7
7
  10.times { f << ('A' * 1024 ) }
@@ -72,8 +72,47 @@ describe ZipTricks::FileReader do
72
72
  }.to raise_error(described_class::UnsupportedFeature)
73
73
  end
74
74
  end
75
-
76
- describe 'with an end-to-end ZIP file to read' do
75
+
76
+ context 'with a ZIP file where the size of the central directory is recorded incorrectly, and has a wrong value' do
77
+ it 'is still able to read the entries' do
78
+ # Purposefully create a ZIP that we will damage after
79
+ zipfile = StringIO.new
80
+ tolstoy = File.read(__dir__ + '/war-and-peace.txt')
81
+ tolstoy.force_encoding(Encoding::BINARY)
82
+
83
+ # Make a one-off Writer that corrupts the size of the central directory and says there is more data
84
+ # in it than there actually is
85
+ class EvilWriter < ZipTricks::ZipWriter
86
+ def write_end_of_central_directory(**kwargs)
87
+ kwargs[:central_directory_size] = kwargs[:central_directory_size] + 64 # Pretend there has to be more data
88
+ super(**kwargs)
89
+ end
90
+ end
91
+
92
+ ZipTricks::Streamer.open(zipfile, writer: EvilWriter.new) do |zip|
93
+ zip.write_deflated_file('text-1.txt') { |sink| sink << tolstoy }
94
+ zip.write_deflated_file('text-2.txt') { |sink| sink << tolstoy }
95
+ end
96
+
97
+ # Find the start of the EOCD record (signature, then the information about disk numbers and the information
98
+ # about the file counts
99
+ eocd_start_marker = [0x06054b50, 0, 0, 2, 2].pack('Vvvvv')
100
+ eocd_offset = zipfile.string.index(eocd_start_marker)
101
+ expect(eocd_offset).not_to be_nil
102
+
103
+ # Seek to the offset where the central directory size is going to be (just after the string we looked for)
104
+ zipfile.seek(eocd_offset + eocd_start_marker.bytesize)
105
+ # and overwrite it.
106
+ zipfile.write([118].pack('V'))
107
+ zipfile.rewind
108
+
109
+ entries = ZipTricks::FileReader.read_zip_structure(io: zipfile)
110
+
111
+ expect(entries.length).to eq(2)
112
+ end
113
+ end
114
+
115
+ context 'with an end-to-end ZIP file to read' do
77
116
  it 'reads and uncompresses the file written deflated with data descriptors' do
78
117
  zipfile = StringIO.new
79
118
  tolstoy = File.read(__dir__ + '/war-and-peace.txt')
@@ -24,7 +24,21 @@ describe ZipTricks::Streamer do
24
24
  described_class.new(nil)
25
25
  }.to raise_error(ZipTricks::Streamer::InvalidOutput)
26
26
  end
27
-
27
+
28
+ it 'allows the writer to be injectable' do
29
+ fake_writer = double('ZipWriter')
30
+ expect(fake_writer).to receive(:write_local_file_header)
31
+ expect(fake_writer).to receive(:write_data_descriptor)
32
+ expect(fake_writer).to receive(:write_central_directory_file_header)
33
+ expect(fake_writer).to receive(:write_end_of_central_directory)
34
+
35
+ described_class.open('', writer: fake_writer) do |zip|
36
+ zip.write_deflated_file('stored.txt') do |sink|
37
+ sink << File.read(__dir__ + '/war-and-peace.txt')
38
+ end
39
+ end
40
+ end
41
+
28
42
  it 'returns the position in the IO at every call' do
29
43
  io = StringIO.new
30
44
  zip = described_class.new(io)
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: zip_tricks 4.1.0 ruby lib
5
+ # stub: zip_tricks 4.2.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "zip_tricks"
9
- s.version = "4.1.0"
9
+ s.version = "4.2.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Julik Tarkhanov"]
14
- s.date = "2016-09-14"
14
+ s.date = "2016-09-17"
15
15
  s.description = "Stream out ZIP files from Ruby"
16
16
  s.email = "me@julik.nl"
17
17
  s.extra_rdoc_files = [
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zip_tricks
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-14 00:00:00.000000000 Z
11
+ date: 2016-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip