zip_tricks 4.2.0 → 4.2.1
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 +4 -4
- data/.travis.yml +1 -0
- data/Gemfile +1 -1
- data/Rakefile +3 -0
- data/lib/zip_tricks.rb +1 -1
- data/lib/zip_tricks/streamer.rb +50 -22
- data/spec/zip_tricks/streamer_spec.rb +27 -10
- data/zip_tricks.gemspec +7 -7
- metadata +3 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99c656baa62bbe09d48e090b2971d63ab3bb875a
|
4
|
+
data.tar.gz: d6b84eb8c621b7039ae205f9e3d9ff5d29887032
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa0dfe2f578443c695be43c3e170b81bc24fdc836401e3b8e50f41379a7583bd425fffbb1ed1d618624988c63e45fd0732f234d57abb249ccea3ef4d20c863d9
|
7
|
+
data.tar.gz: 30a98f251dd26cde99a62f1cf93ff889aceb30f3ae3eebbba3516b01da3fd56d920e8816d5f742b4c0b17d731b6d8be1f235b4d5bc595b0dc3ecd3960e6b1618
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/lib/zip_tricks.rb
CHANGED
data/lib/zip_tricks/streamer.rb
CHANGED
@@ -37,15 +37,21 @@
|
|
37
37
|
#
|
38
38
|
# ZipTricks::Streamer.open(socket) do | zip |
|
39
39
|
# zip.add_stored_entry(filename: "myfile1.bin", size: 9090821, crc32: 12485)
|
40
|
-
# zip.simulate_write(tempfile1.size)
|
41
40
|
# socket.sendfile(tempfile1)
|
41
|
+
# zip.simulate_write(tempfile1.size)
|
42
|
+
#
|
42
43
|
# zip.add_stored_entry(filename: "myfile2.bin", size: 458678, crc32: 89568)
|
43
|
-
# zip.simulate_write(tempfile2.size)
|
44
44
|
# socket.sendfile(tempfile2)
|
45
|
+
# zip.simulate_write(tempfile2.size)
|
45
46
|
# end
|
46
47
|
#
|
47
|
-
# Note that you need to use `simulate_write` to
|
48
|
-
#
|
48
|
+
# Note that you need to use `simulate_write` in this case. This needs to happen since Streamer
|
49
|
+
# writes absolute offsets into the ZIP (local file header offsets and the like),
|
50
|
+
# and it relies on the output object to tell it how many bytes have been written
|
51
|
+
# so far. When using `sendfile` the Ruby write methods get bypassed entirely, and the
|
52
|
+
# offsets in the IO will not be updated - which will result in an invalid ZIP.
|
53
|
+
#
|
54
|
+
# The central directory will be written automatically at the end of the `open` block.
|
49
55
|
class ZipTricks::Streamer
|
50
56
|
require_relative 'streamer/deflated_writer'
|
51
57
|
require_relative 'streamer/writable'
|
@@ -58,8 +64,6 @@ class ZipTricks::Streamer
|
|
58
64
|
EntryBodySizeMismatch = Class.new(StandardError)
|
59
65
|
InvalidOutput = Class.new(ArgumentError)
|
60
66
|
Overflow = Class.new(StandardError)
|
61
|
-
PathError = Class.new(StandardError)
|
62
|
-
DuplicateFilenames = Class.new(StandardError)
|
63
67
|
UnknownMode = Class.new(StandardError)
|
64
68
|
|
65
69
|
private_constant :DeflatedWriter, :StoredWriter, :STORED, :DEFLATED
|
@@ -86,7 +90,7 @@ class ZipTricks::Streamer
|
|
86
90
|
unless stream.respond_to?(:tell) && stream.respond_to?(:advance_position_by)
|
87
91
|
stream = ZipTricks::WriteAndTell.new(stream)
|
88
92
|
end
|
89
|
-
|
93
|
+
|
90
94
|
@out = stream
|
91
95
|
@files = []
|
92
96
|
@local_header_offsets = []
|
@@ -208,7 +212,7 @@ class ZipTricks::Streamer
|
|
208
212
|
def close
|
209
213
|
# Record the central directory offset, so that it can be written into the EOCD record
|
210
214
|
cdir_starts_at = @out.tell
|
211
|
-
|
215
|
+
|
212
216
|
# Write out the central directory entries, one for each file
|
213
217
|
@files.each_with_index do |entry, i|
|
214
218
|
header_loc = @local_header_offsets.fetch(i)
|
@@ -217,7 +221,7 @@ class ZipTricks::Streamer
|
|
217
221
|
compressed_size: entry.compressed_size, uncompressed_size: entry.uncompressed_size,
|
218
222
|
mtime: entry.mtime, crc32: entry.crc32, filename: entry.filename) #, external_attrs: DEFAULT_EXTERNAL_ATTRS)
|
219
223
|
end
|
220
|
-
|
224
|
+
|
221
225
|
# Record the central directory size, for the EOCDR
|
222
226
|
cdir_size = @out.tell - cdir_starts_at
|
223
227
|
|
@@ -226,7 +230,7 @@ class ZipTricks::Streamer
|
|
226
230
|
central_directory_size: cdir_size, num_files_in_archive: @files.length)
|
227
231
|
@out.tell
|
228
232
|
end
|
229
|
-
|
233
|
+
|
230
234
|
# Sets up the ZipWriter with wrappers if necessary. The method is called once, when the Streamer
|
231
235
|
# gets instantiated - the Writer then gets reused. This method is primarily there so that you
|
232
236
|
# can override it.
|
@@ -235,22 +239,18 @@ class ZipTricks::Streamer
|
|
235
239
|
def create_writer
|
236
240
|
ZipTricks::ZipWriter.new
|
237
241
|
end
|
238
|
-
|
242
|
+
|
239
243
|
private
|
240
|
-
|
244
|
+
|
241
245
|
def add_file_and_write_local_header(filename:, crc32:, storage_mode:, compressed_size:,
|
242
246
|
uncompressed_size:, use_data_descriptor: false)
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
+
|
248
|
+
# Clean backslashes and uniqify filenames if there are duplicates
|
249
|
+
filename = remove_backslash(filename)
|
250
|
+
filename = uniquify_name(filename) if @files.any? { |e| e.filename == filename }
|
251
|
+
|
247
252
|
raise UnknownMode, "Unknown compression mode #{storage_mode}" unless [STORED, DEFLATED].include?(storage_mode)
|
248
253
|
raise Overflow, "Filename is too long" if filename.bytesize > 0xFFFF
|
249
|
-
raise PathError, "Paths in ZIP may only contain forward slashes (UNIX separators)" if filename.include?('\\')
|
250
|
-
|
251
|
-
@check_compressed_size_after_leaving_body = !use_data_descriptor
|
252
|
-
@bytes_written_for_entry = 0
|
253
|
-
@expected_bytes_for_entry = compressed_size
|
254
254
|
|
255
255
|
e = Entry.new(filename, crc32, compressed_size, uncompressed_size, storage_mode, mtime=Time.now.utc, use_data_descriptor)
|
256
256
|
@files << e
|
@@ -258,9 +258,37 @@ class ZipTricks::Streamer
|
|
258
258
|
@writer.write_local_file_header(io: @out, gp_flags: e.gp_flags, crc32: e.crc32, compressed_size: e.compressed_size,
|
259
259
|
uncompressed_size: e.uncompressed_size, mtime: e.mtime, filename: e.filename, storage_mode: e.storage_mode)
|
260
260
|
end
|
261
|
-
|
261
|
+
|
262
262
|
def write_data_descriptor_for_last_entry
|
263
263
|
e = @files.fetch(-1)
|
264
264
|
@writer.write_data_descriptor(io: @out, crc32: 0, compressed_size: e.compressed_size, uncompressed_size: e.uncompressed_size)
|
265
265
|
end
|
266
|
+
|
267
|
+
def remove_backslash(filename)
|
268
|
+
filename.tr('\\', '_')
|
269
|
+
end
|
270
|
+
|
271
|
+
def uniquify_name(filename)
|
272
|
+
files = Set.new(@files.map(&:filename))
|
273
|
+
copy_pattern = /\((\d+)\)$/ # we add (1), (2), (n) at the end of a filename if there is a duplicate
|
274
|
+
parts = filename.split(".")
|
275
|
+
ext = if parts.last =~ /gz|zip/ && parts.size > 2
|
276
|
+
parts.pop(2)
|
277
|
+
elsif parts.size > 1
|
278
|
+
parts.pop
|
279
|
+
end
|
280
|
+
fn_last_part = parts.pop
|
281
|
+
|
282
|
+
duplicate_counter = 1
|
283
|
+
loop do
|
284
|
+
if fn_last_part =~ copy_pattern
|
285
|
+
fn_last_part.sub!(copy_pattern, "(#{duplicate_counter})")
|
286
|
+
else
|
287
|
+
fn_last_part = "#{fn_last_part} (#{duplicate_counter})"
|
288
|
+
end
|
289
|
+
new_filename = (parts + [fn_last_part, ext]).compact.join(".")
|
290
|
+
return new_filename unless files.include?(new_filename)
|
291
|
+
duplicate_counter += 1
|
292
|
+
end
|
293
|
+
end
|
266
294
|
end
|
@@ -24,21 +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
28
|
it 'allows the writer to be injectable' do
|
29
29
|
fake_writer = double('ZipWriter')
|
30
30
|
expect(fake_writer).to receive(:write_local_file_header)
|
31
31
|
expect(fake_writer).to receive(:write_data_descriptor)
|
32
32
|
expect(fake_writer).to receive(:write_central_directory_file_header)
|
33
33
|
expect(fake_writer).to receive(:write_end_of_central_directory)
|
34
|
-
|
34
|
+
|
35
35
|
described_class.open('', writer: fake_writer) do |zip|
|
36
36
|
zip.write_deflated_file('stored.txt') do |sink|
|
37
37
|
sink << File.read(__dir__ + '/war-and-peace.txt')
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
it 'returns the position in the IO at every call' do
|
43
43
|
io = StringIO.new
|
44
44
|
zip = described_class.new(io)
|
@@ -135,7 +135,7 @@ describe ZipTricks::Streamer do
|
|
135
135
|
outbuf.flush
|
136
136
|
File.unlink('test.zip') rescue nil
|
137
137
|
File.rename(outbuf.path, 'osx-archive-test.zip')
|
138
|
-
|
138
|
+
|
139
139
|
# Mark this test as skipped if the system does not have the binary
|
140
140
|
open_zip_with_archive_utility('osx-archive-test.zip', skip_if_missing: true)
|
141
141
|
end
|
@@ -226,7 +226,7 @@ describe ZipTricks::Streamer do
|
|
226
226
|
expect(second_entry.name).to eq("второй-файл.bin".force_encoding(Encoding::BINARY))
|
227
227
|
end
|
228
228
|
end
|
229
|
-
|
229
|
+
|
230
230
|
it 'creates an archive with data descriptors that can be opened by Rubyzip, with a small number of very tiny text files' do
|
231
231
|
tf = ManagedTempfile.new('zip')
|
232
232
|
z = described_class.open(tf) do |zip|
|
@@ -238,14 +238,14 @@ describe ZipTricks::Streamer do
|
|
238
238
|
end
|
239
239
|
end
|
240
240
|
tf.flush
|
241
|
-
|
241
|
+
|
242
242
|
pending 'https://github.com/rubyzip/rubyzip/issues/295'
|
243
|
-
|
243
|
+
|
244
244
|
Zip::File.foreach(tf.path) do |entry|
|
245
245
|
# Make sure it is tagged as UNIX
|
246
246
|
expect(entry.fstype).to eq(3)
|
247
247
|
|
248
|
-
|
248
|
+
# The CRC
|
249
249
|
expect(entry.crc).to eq(Zlib.crc32(File.read(__dir__ + '/war-and-peace.txt')))
|
250
250
|
|
251
251
|
# Check the name
|
@@ -253,7 +253,7 @@ describe ZipTricks::Streamer do
|
|
253
253
|
|
254
254
|
# Check the right external attributes (non-executable on UNIX)
|
255
255
|
expect(entry.external_file_attributes).to eq(2175008768)
|
256
|
-
|
256
|
+
|
257
257
|
# Check the file contents
|
258
258
|
readback = entry.get_input_stream.read
|
259
259
|
readback.force_encoding(Encoding::BINARY)
|
@@ -265,7 +265,7 @@ describe ZipTricks::Streamer do
|
|
265
265
|
|
266
266
|
it 'can create a valid ZIP archive without any files' do
|
267
267
|
tf = ManagedTempfile.new('zip')
|
268
|
-
|
268
|
+
|
269
269
|
described_class.open(tf) do |zip|
|
270
270
|
end
|
271
271
|
|
@@ -276,4 +276,21 @@ describe ZipTricks::Streamer do
|
|
276
276
|
Zip::File.foreach(tf.path, &b)
|
277
277
|
}.not_to yield_control
|
278
278
|
end
|
279
|
+
|
280
|
+
it 'prevents duplicates in the stored files' do
|
281
|
+
files = ["README", "README", "file.one\\two.jpg", "file_one.jpg", "file_one (1).jpg",
|
282
|
+
"file\\one.jpg", "My.Super.file.txt.zip", "My.Super.file.txt.zip"]
|
283
|
+
fake_writer = double('Writer').as_null_object
|
284
|
+
seen_filenames = []
|
285
|
+
allow(fake_writer).to receive(:write_local_file_header) {|filename:, **others|
|
286
|
+
seen_filenames << filename
|
287
|
+
}
|
288
|
+
zip_streamer = described_class.new(StringIO.new, writer: fake_writer)
|
289
|
+
files.each do |fn|
|
290
|
+
zip_streamer.add_stored_entry(filename: fn, size: 1024, crc32: 0xCC)
|
291
|
+
end
|
292
|
+
expect(seen_filenames).to eq(["README", "README (1)", "file.one_two.jpg", "file_one.jpg",
|
293
|
+
"file_one (1).jpg", "file_one (2).jpg", "My.Super.file.txt.zip",
|
294
|
+
"My.Super.file (1).txt.zip"])
|
295
|
+
end
|
279
296
|
end
|
data/zip_tricks.gemspec
CHANGED
@@ -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.2.
|
5
|
+
# stub: zip_tricks 4.2.1 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "zip_tricks"
|
9
|
-
s.version = "4.2.
|
9
|
+
s.version = "4.2.1"
|
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-
|
14
|
+
s.date = "2016-10-10"
|
15
15
|
s.description = "Stream out ZIP files from Ruby"
|
16
16
|
s.email = "me@julik.nl"
|
17
17
|
s.extra_rdoc_files = [
|
@@ -70,14 +70,14 @@ Gem::Specification.new do |s|
|
|
70
70
|
]
|
71
71
|
s.homepage = "http://github.com/wetransfer/zip_tricks"
|
72
72
|
s.licenses = ["MIT"]
|
73
|
-
s.rubygems_version = "2.5.1"
|
73
|
+
s.rubygems_version = "2.4.5.1"
|
74
74
|
s.summary = "Stream out ZIP files from Ruby"
|
75
75
|
|
76
76
|
if s.respond_to? :specification_version then
|
77
77
|
s.specification_version = 4
|
78
78
|
|
79
79
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
80
|
-
s.add_development_dependency(%q<rubyzip>, ["
|
80
|
+
s.add_development_dependency(%q<rubyzip>, ["~> 1.1"])
|
81
81
|
s.add_development_dependency(%q<terminal-table>, [">= 0"])
|
82
82
|
s.add_development_dependency(%q<range_utils>, [">= 0"])
|
83
83
|
s.add_development_dependency(%q<rack>, ["~> 1.6"])
|
@@ -88,7 +88,7 @@ Gem::Specification.new do |s|
|
|
88
88
|
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
89
89
|
s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
|
90
90
|
else
|
91
|
-
s.add_dependency(%q<rubyzip>, ["
|
91
|
+
s.add_dependency(%q<rubyzip>, ["~> 1.1"])
|
92
92
|
s.add_dependency(%q<terminal-table>, [">= 0"])
|
93
93
|
s.add_dependency(%q<range_utils>, [">= 0"])
|
94
94
|
s.add_dependency(%q<rack>, ["~> 1.6"])
|
@@ -100,7 +100,7 @@ Gem::Specification.new do |s|
|
|
100
100
|
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
101
101
|
end
|
102
102
|
else
|
103
|
-
s.add_dependency(%q<rubyzip>, ["
|
103
|
+
s.add_dependency(%q<rubyzip>, ["~> 1.1"])
|
104
104
|
s.add_dependency(%q<terminal-table>, [">= 0"])
|
105
105
|
s.add_dependency(%q<range_utils>, [">= 0"])
|
106
106
|
s.add_dependency(%q<rack>, ["~> 1.6"])
|
metadata
CHANGED
@@ -1,22 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zip_tricks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.2.
|
4
|
+
version: 4.2.1
|
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-
|
11
|
+
date: 2016-10-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubyzip
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 1.1.7
|
20
17
|
- - "~>"
|
21
18
|
- !ruby/object:Gem::Version
|
22
19
|
version: '1.1'
|
@@ -24,9 +21,6 @@ dependencies:
|
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- - ">="
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: 1.1.7
|
30
24
|
- - "~>"
|
31
25
|
- !ruby/object:Gem::Version
|
32
26
|
version: '1.1'
|
@@ -238,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
238
232
|
version: '0'
|
239
233
|
requirements: []
|
240
234
|
rubyforge_project:
|
241
|
-
rubygems_version: 2.5.1
|
235
|
+
rubygems_version: 2.4.5.1
|
242
236
|
signing_key:
|
243
237
|
specification_version: 4
|
244
238
|
summary: Stream out ZIP files from Ruby
|