zip_tricks 4.2.0 → 4.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|