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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a529e0fc4dae54675a7ba880475ec6860ae6c4e
4
- data.tar.gz: e5ef83a503a377bfa504a9daeeb415367c9c680e
3
+ metadata.gz: 99c656baa62bbe09d48e090b2971d63ab3bb875a
4
+ data.tar.gz: d6b84eb8c621b7039ae205f9e3d9ff5d29887032
5
5
  SHA512:
6
- metadata.gz: 46d2a64d61873cf3b32889ef99408fab97aeb25eecc7cd76690f40c328c7bdcec9aa6f9d981726a775b79d1746709a35817156221aaf339dc7cda885e58796bc
7
- data.tar.gz: 458b3a982c1e533d321b2fffebd086125aeb29e0b5e687cfc187e340847200e946ce7944ee237298dae1c1702ef8e3a971ceb53b439e7156f12ae9c7ff995842
6
+ metadata.gz: fa0dfe2f578443c695be43c3e170b81bc24fdc836401e3b8e50f41379a7583bd425fffbb1ed1d618624988c63e45fd0732f234d57abb249ccea3ef4d20c863d9
7
+ data.tar.gz: 30a98f251dd26cde99a62f1cf93ff889aceb30f3ae3eebbba3516b01da3fd56d920e8816d5f742b4c0b17d731b6d8be1f235b4d5bc595b0dc3ecd3960e6b1618
@@ -1,6 +1,7 @@
1
1
  rvm:
2
2
  - 2.1
3
3
  - 2.2
4
+ - 2.3.0
4
5
  - jruby-9.0
5
6
  sudo: false
6
7
  cache: bundler
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  group :development do
4
- gem 'rubyzip', '~> 1.1', '>= 1.1.7'
4
+ gem 'rubyzip', '~> 1.1'
5
5
  gem 'terminal-table'
6
6
  gem 'range_utils'
7
7
  gem 'rack', '~> 1.6' # For Jeweler
data/Rakefile CHANGED
@@ -12,6 +12,9 @@ end
12
12
  require 'rake'
13
13
  require_relative 'lib/zip_tricks'
14
14
  require 'jeweler'
15
+
16
+
17
+
15
18
  Jeweler::Tasks.new do |gem|
16
19
  # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
17
20
  gem.name = "zip_tricks"
@@ -1,5 +1,5 @@
1
1
  module ZipTricks
2
- VERSION = '4.2.0'
2
+ VERSION = '4.2.1'
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__ }
@@ -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 let the
48
- # The central directory will be written automatically at the end of the block.
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
- if @files.any?{|e| e.filename == filename }
244
- raise DuplicateFilenames, "Filename #{filename.inspect} already used in the archive"
245
- end
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
- # The CRC
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
@@ -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.0 ruby lib
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.0"
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-09-17"
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>, [">= 1.1.7", "~> 1.1"])
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>, [">= 1.1.7", "~> 1.1"])
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>, [">= 1.1.7", "~> 1.1"])
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.0
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-09-17 00:00:00.000000000 Z
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