zip_tricks 4.7.3 → 4.8.3

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
- SHA1:
3
- metadata.gz: 537945ff9983b6d84190281aaf71d52a265e49f5
4
- data.tar.gz: 1aa8b7a739b624dd7de4ea3e960d966f6f3d9ad3
2
+ SHA256:
3
+ metadata.gz: a8147080f700510b88cb5e807e61ac71caf94e4b35af7a13162f3fcb0557b221
4
+ data.tar.gz: 507c345c51f13b2874577497359f49179447b494eacf55f81c6a0b182feeb97a
5
5
  SHA512:
6
- metadata.gz: 4a98133de0c4902de0babccb5ce5c59e97d9636e1191c65d61bdf538e58c3b81847871bee79584609fc1864f32a0b3dfb8068781149f5b957e43ad1ed80a8b35
7
- data.tar.gz: 42e5c29411a5b1a3942bf88f359694572fe0c4a3efbd61006457e0f53c5d0b9948bf8d5f7f801a5c819d341b2e6fc52f2479030c5dd9248b53e5f031faf65423
6
+ metadata.gz: 328483359ec8a1ced39c3cdc4b853d0fadda7a2c66e1d36e0babc3e701648e57b212b85b5adf6cc24fc92d63eff28889216b11438252f8d1e32d9bdfbb1acaab
7
+ data.tar.gz: 99d3e5e9a31ab39ae1bbf39128a086373eadb29a44dee76fc4674eb73ad96ed9c190ca50d9d7227b16f6421d01b2603026386d0e16260eec33f417fd68f85e58
@@ -1,5 +1,7 @@
1
1
  inherit_gem:
2
2
  wetransfer_style: ruby/default.yml
3
+ AllCops:
4
+ TargetRubyVersion: 2.1
3
5
  Layout/FirstMethodArgumentLineBreak:
4
6
  Enabled: false
5
7
  Layout/FirstMethodParameterLineBreak:
@@ -7,4 +9,5 @@ Layout/FirstMethodParameterLineBreak:
7
9
  Style/GlobalVars:
8
10
  Exclude:
9
11
  - qa/*.rb
10
- - spec/spec_helper.rb
12
+ - spec/spec_helper.rb
13
+ - spec/support/zip_inspection.rb
@@ -1,3 +1,33 @@
1
+ ## 4.8.3
2
+
3
+ * Fix a leak of 1 zlib deflater object per deflated file when writing out compressed files with data descriptors. The deflater
4
+ needs to be closed explicitly, which we weren't previously doing.
5
+
6
+ ## 4.8.2
7
+
8
+ * Fix extended timestamp timestamp value encoding. Previously we would use an incorrect encoding for the timestamp value, which would output correct but nonsensical timestamps. The pack specifier is now changed to output the correct value.
9
+
10
+ ## 4.8.1
11
+
12
+ * Fix extended timestamp extra field output. The first bit of the flag would be set instead of the last bit of
13
+ the flag, which made it impossible for Rubyzip to read the timestamp of the entry - and it would also make
14
+ the extra field useless for most reading applications.
15
+
16
+ ## 4.8.0
17
+
18
+ * Make sure that when directories clobber files and vice versa we raise a clear error. Add `PathSet` which keeps track of entries
19
+ and all the directories needed to create them, document `PathSet`
20
+ * Move the `uniquify_filenames` function into a module for easier removal later
21
+ * Add the `auto_rename_duplicate_filenames` parameter to `Streamer` constructor. We need to make this optional
22
+ because making filenames unique can be very tricky when subdirectories are involved, and strictly
23
+ speaking we should not be applying this transformation at all - there should be no output of
24
+ duplicate filenames by the caller. So making the filenames should be available, but optional.
25
+
26
+ ## 4.7.4
27
+
28
+ * Use a single fixed capacity string in StreamCRC32.from_io to avoid unnecessary allocations
29
+ * Fix a few tests that were calling out to external binaries
30
+
1
31
  ## 4.7.3
2
32
 
3
33
  * Fix RemoteUncap#request_object_size to function correctly
@@ -1,4 +1,4 @@
1
- Copyright (c) 2016 WeTransfer
1
+ Copyright (c) 2019 WeTransfer
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # zip_tricks
2
2
 
3
- [![Build Status](https://travis-ci.org/WeTransfer/zip_tricks.svg?branch=master)](https://travis-ci.org/WeTransfer/zip_tricks)
4
- [![Gem Version](https://badge.fury.io/rb/zip_tricks.svg)](https://badge.fury.io/rb/zip_tricks)
3
+ [![Build Status](https://travis-ci.org/WeTransfer/zip_tricks.svg?branch=backports-v4)](https://travis-ci.org/WeTransfer/zip_tricks)
5
4
 
6
5
  Allows streaming, non-rewinding ZIP file output from Ruby.
7
6
 
@@ -30,7 +29,7 @@ The easiest is to use the Rails' built-in streaming feature:
30
29
  class ZipsController < ActionController::Base
31
30
  include ActionController::Live # required for streaming
32
31
  include ZipTricks::RailsStreaming
33
-
32
+
34
33
  def download
35
34
  zip_tricks_stream do |zip|
36
35
  zip.write_deflated_file('report1.csv') do |sink|
@@ -122,10 +121,10 @@ ZipTricks::Streamer.open(io) do | zip |
122
121
  # raw_file is written "as is" (STORED mode).
123
122
  # Write the local file header first..
124
123
  zip.add_stored_entry(filename: "first-file.bin", size: raw_file.size, crc32: raw_file_crc32)
125
-
124
+
126
125
  # then send the actual file contents bypassing the Streamer interface
127
126
  io.sendfile(my_temp_file)
128
-
127
+
129
128
  # ...and then adjust the ZIP offsets within the Streamer
130
129
  zip.simulate_write(my_temp_file.size)
131
130
  end
@@ -143,7 +142,7 @@ It is slightly more convenient for the purpose than using the raw Zlib library f
143
142
 
144
143
  ```ruby
145
144
  crc = ZipTricks::StreamCRC32.new
146
- crc << large_file.read(1024 * 12) until large_file.eof?
145
+ crc << next_chunk_of_data
147
146
  ...
148
147
 
149
148
  crc.to_i # Returns the actual CRC32 value computed so far
@@ -152,6 +151,12 @@ crc.to_i # Returns the actual CRC32 value computed so far
152
151
  crc.append(precomputed_crc32, size_of_the_blob_computed_from)
153
152
  ```
154
153
 
154
+ You can also compute the CRC32 for an entire IO object if it responds to `#eof?`:
155
+
156
+ ```ruby
157
+ crc = ZipTricks::StreamCRC32.from_io(file) # Returns an Integer
158
+ ```
159
+
155
160
  ## Reading ZIP files
156
161
 
157
162
  The library contains a reader module, play with it to see what is possible. It is not a complete ZIP reader
@@ -160,7 +165,7 @@ as such it performs it's function quite well. Please beware of the security impl
160
165
  that have not been formally verified (ours hasn't been).
161
166
 
162
167
  ## Contributing to zip_tricks
163
-
168
+
164
169
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
165
170
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
166
171
  * Fork the project.
@@ -172,4 +177,4 @@ that have not been formally verified (ours hasn't been).
172
177
 
173
178
  ## Copyright
174
179
 
175
- Copyright (c) 2016 WeTransfer. See LICENSE.txt for further details.
180
+ Copyright (c) 2019 WeTransfer. See LICENSE.txt for further details.
@@ -0,0 +1,148 @@
1
+ # rubocop:disable Layout/IndentHeredoc
2
+
3
+ # A ZIP archive contains a flat list of entries. These entries can implicitly
4
+ # create directories when the archive is expanded. For example, an entry with
5
+ # the filename of "some folder/file.docx" will make the unarchiving application
6
+ # create a directory called "some folder" automatically, and then deposit the
7
+ # file "file.docx" in that directory. These "implicit" directories can be
8
+ # arbitrarily nested, and create a tree structure of directories. That structure
9
+ # however is implicit as the archive contains a flat list.
10
+ #
11
+ # This creates opportunities for conflicts. For example, imagine the following
12
+ # structure:
13
+ #
14
+ # * `something/` - specifies an empty directory with the name "something"
15
+ # * `something` - specifies a file, creates a conflict
16
+ #
17
+ # This can be prevented with filename uniqueness checks. It does get funkier however
18
+ # as the rabbit hole goes down:
19
+ #
20
+ # * `dir/subdir/another_subdir/yet_another_subdir/file.bin` - declares a file and directories
21
+ # * `dir/subdir/another_subdir/yet_another_subdir` - declares a file at one of the levels, creates a conflict
22
+ #
23
+ # The results of this ZIP structure aren't very easy to predict as they depend on the
24
+ # application that opens the archive. For example, BOMArchiveHelper on macOS will expand files
25
+ # as they are declared in the ZIP, but once a conflict occurs it will fail with "error -21". It
26
+ # is not very transparent to the user why unarchiving fails, and it has to - and can reliably - only
27
+ # be prevented when the archive gets created.
28
+ #
29
+ # Unfortunately that conflicts with another "magical" feature of ZipTricks which automatically
30
+ # "fixes" duplicate filenames - filenames (paths) which have already been added to the archive.
31
+ # This fix is performed by appending (1), then (2) and so forth to the filename so that the
32
+ # conflict is avoided. This is not possible to apply to directories, because when one of the
33
+ # path components is reused in multiple filenames it means those entities should end up in
34
+ # the same directory (subdirectory) once the archive is opened.
35
+ class ZipTricks::PathSet
36
+ class Conflict < StandardError
37
+ end
38
+
39
+ class FileClobbersDirectory < Conflict
40
+ end
41
+
42
+ class DirectoryClobbersFile < Conflict
43
+ end
44
+
45
+ def initialize
46
+ @known_directories = Set.new
47
+ @known_files = Set.new
48
+ end
49
+
50
+ # Adds a directory path to the set of known paths, including
51
+ # all the directories that contain it. So, calling
52
+ # add_directory_path("dir/dir2/dir3")
53
+ # will add "dir", "dir/dir2", "dir/dir2/dir3".
54
+ #
55
+ # @param path[String] the path to the directory to add
56
+ # @return [void]
57
+ def add_directory_path(path)
58
+ path_and_ancestors(path).each do |parent_directory_path|
59
+ if @known_files.include?(parent_directory_path)
60
+ # Have to use the old-fashioned heredocs because ZipTricks
61
+ # aims to be compatible with MRI 2.1+ syntax, and squiggly
62
+ # heredoc is only available starting 2.3+
63
+ error_message = <<ERR
64
+ The path #{parent_directory_path.inspect} which has to be added
65
+ as a directory is already used for a file.
66
+
67
+ The directory at this path would get created implicitly
68
+ to produce #{path.inspect} during decompresison.
69
+
70
+ This would make some archive utilities refuse to open
71
+ the ZIP.
72
+ ERR
73
+ raise DirectoryClobbersFile, error_message
74
+ end
75
+ @known_directories << parent_directory_path
76
+ end
77
+ end
78
+
79
+ # Adds a file path to the set of known paths, including
80
+ # all the directories that contain it. Once a file has been added,
81
+ # it is no longer possible to add a directory having the same path
82
+ # as this would cause conflict.
83
+ #
84
+ # The operation also adds all the containing directories for the file, so
85
+ # add_file_path("dir/dir2/file.doc")
86
+ # will add "dir" and "dir/dir2" as directories, "dir/dir2/dir3".
87
+ #
88
+ # @param file_path[String] the path to the directory to add
89
+ # @return [void]
90
+ def add_file_path(file_path)
91
+ if @known_files.include?(file_path)
92
+ error_message = <<ERR
93
+ The file at #{file_path.inspect} has already been included
94
+ in the archive. Adding it the second time would cause
95
+ the first file to be overwritten during unarchiving, and
96
+ could also get the archive flagged as invalid.
97
+ ERR
98
+ raise Conflict, error_message
99
+ end
100
+
101
+ if @known_directories.include?(file_path)
102
+ error_message = <<ERR
103
+ The path #{file_path.inspect} is already used for
104
+ a directory, but you are trying to add it as a file.
105
+
106
+ This would make some archive utilities refuse
107
+ to open the ZIP.
108
+ ERR
109
+ raise FileClobbersDirectory, error_message
110
+ end
111
+
112
+ # Add all the directories which this file is contained in
113
+ *dir_components, _file_name = non_empty_path_components(file_path)
114
+ add_directory_path(dir_components.join('/'))
115
+
116
+ # ...and then the file itself
117
+ @known_files << file_path
118
+ end
119
+
120
+ # Tells whether a specific full path is already known to the PathSet.
121
+ # Can be a path for a directory or for a file.
122
+ #
123
+ # @param path_in_archive[String] the path to check for inclusion
124
+ # @return [Boolean]
125
+ def include?(path_in_archive)
126
+ @known_files.include?(path_in_archive) || @known_directories.include?(path_in_archive)
127
+ end
128
+
129
+ # Clears the contained sets
130
+ # @return [void]
131
+ def clear
132
+ @known_files.clear
133
+ @known_directories.clear
134
+ end
135
+
136
+ private
137
+
138
+ def non_empty_path_components(path)
139
+ path.split('/').reject(&:empty?)
140
+ end
141
+
142
+ def path_and_ancestors(path)
143
+ path_components = non_empty_path_components(path)
144
+ path_components.each_with_object([]) do |component, seen|
145
+ seen << [seen.last, component].compact.join('/')
146
+ end
147
+ end
148
+ end
@@ -20,10 +20,11 @@ class ZipTricks::SizeEstimator
20
20
  # uncompressed_size: 89281911, compressed_size: 121908)
21
21
  # end
22
22
  #
23
+ # @param kwargs_for_streamer_new Any options to pass to Streamer, see {Streamer#initialize}
23
24
  # @return [Integer] the size of the resulting archive, in bytes
24
25
  # @yield [SizeEstimator] the estimator
25
- def self.estimate
26
- streamer = ZipTricks::Streamer.new(ZipTricks::NullWriter)
26
+ def self.estimate(**kwargs_for_streamer_new)
27
+ streamer = ZipTricks::Streamer.new(ZipTricks::NullWriter, **kwargs_for_streamer_new)
27
28
  estimator = new(streamer)
28
29
  yield(estimator)
29
30
  streamer.close # Returns the .tell of the contained IO
@@ -2,13 +2,26 @@
2
2
 
3
3
  # A simple stateful class for keeping track of a CRC32 value through multiple writes
4
4
  class ZipTricks::StreamCRC32
5
+ STRINGS_HAVE_CAPACITY_SUPPORT = begin
6
+ String.new('', capacity: 1)
7
+ true
8
+ rescue ArgumentError
9
+ false
10
+ end
11
+ CRC_BUF_SIZE = 1024 * 512
12
+ private_constant :STRINGS_HAVE_CAPACITY_SUPPORT, :CRC_BUF_SIZE
13
+
5
14
  # Compute a CRC32 value from an IO object. The object should respond to `read` and `eof?`
6
15
  #
7
16
  # @param io[IO] the IO to read the data from
8
17
  # @return [Fixnum] the computed CRC32 value
9
18
  def self.from_io(io)
19
+ # If we can specify the string capacity upfront we will not have to resize
20
+ # the string during operation. This saves time but is only available on
21
+ # recent Ruby 2.x versions.
22
+ blob = STRINGS_HAVE_CAPACITY_SUPPORT ? String.new('', capacity: CRC_BUF_SIZE) : String.new('')
10
23
  crc = new
11
- crc << io.read(1024 * 512) until io.eof?
24
+ crc << io.read(CRC_BUF_SIZE, blob) until io.eof?
12
25
  crc.to_i
13
26
  end
14
27
 
@@ -140,13 +140,19 @@ class ZipTricks::Streamer
140
140
  # @param stream[IO] the destination IO for the ZIP. Anything that responds to `<<` can be used.
141
141
  # @param writer[ZipTricks::ZipWriter] the object to be used as the writer.
142
142
  # Defaults to an instance of ZipTricks::ZipWriter, normally you won't need to override it
143
- def initialize(stream, writer: create_writer)
143
+ # @param auto_rename_duplicate_filenames[Boolean] whether duplicate filenames, when encountered,
144
+ # should be suffixed with (1), (2) etc. Default value is `true` since it
145
+ # used to be the default behavior.
146
+ #
147
+ # **DEPRECATION NOTICE** In ZipTricks version 5 `auto_rename_duplicate_filenames` will default to `false`
148
+ def initialize(stream, writer: create_writer, auto_rename_duplicate_filenames: true)
144
149
  raise InvalidOutput, 'The stream must respond to #<<' unless stream.respond_to?(:<<)
145
150
 
151
+ @dedupe_filenames = auto_rename_duplicate_filenames
146
152
  @out = ZipTricks::WriteAndTell.new(stream)
147
153
  @files = []
148
154
  @local_header_offsets = []
149
- @filenames_set = Set.new
155
+ @path_set = ZipTricks::PathSet.new
150
156
  @writer = writer
151
157
  end
152
158
 
@@ -387,7 +393,7 @@ class ZipTricks::Streamer
387
393
 
388
394
  # Clear the files so that GC will not have to trace all the way to here to deallocate them
389
395
  @files.clear
390
- @filenames_set.clear
396
+ @path_set.clear
391
397
 
392
398
  # and return the final offset
393
399
  @out.tell
@@ -429,22 +435,31 @@ class ZipTricks::Streamer
429
435
  private
430
436
 
431
437
  def add_file_and_write_local_header(
432
- filename:,
433
- modification_time:,
434
- crc32:,
435
- storage_mode:,
436
- compressed_size:,
437
- uncompressed_size:,
438
- use_data_descriptor:)
439
-
440
- # Clean backslashes and uniqify filenames if there are duplicates
438
+ filename:,
439
+ modification_time:,
440
+ crc32:,
441
+ storage_mode:,
442
+ compressed_size:,
443
+ uncompressed_size:,
444
+ use_data_descriptor:)
445
+
446
+ # Clean backslashes
441
447
  filename = remove_backslash(filename)
442
- filename = uniquify_name(filename) if @filenames_set.include?(filename)
443
-
444
448
  raise UnknownMode, "Unknown compression mode #{storage_mode}" unless [STORED, DEFLATED].include?(storage_mode)
445
-
446
449
  raise Overflow, 'Filename is too long' if filename.bytesize > 0xFFFF
447
450
 
451
+ # If we need to massage filenames to enforce uniqueness,
452
+ # do so before we check for file/directory conflicts
453
+ filename = ZipTricks::UniquifyFilename.call(filename, @path_set) if @dedupe_filenames
454
+
455
+ # Make sure there is no file/directory clobbering (conflicts), or - if deduping is disabled -
456
+ # no duplicate filenames/paths
457
+ if filename.end_with?('/')
458
+ @path_set.add_directory_path(filename)
459
+ else
460
+ @path_set.add_file_path(filename)
461
+ end
462
+
448
463
  if use_data_descriptor
449
464
  crc32 = 0
450
465
  compressed_size = 0
@@ -460,7 +475,6 @@ use_data_descriptor:)
460
475
  use_data_descriptor)
461
476
 
462
477
  @files << e
463
- @filenames_set << e.filename
464
478
  @local_header_offsets << @out.tell
465
479
 
466
480
  @writer.write_local_file_header(io: @out,
@@ -476,28 +490,4 @@ use_data_descriptor:)
476
490
  def remove_backslash(filename)
477
491
  filename.tr('\\', '_')
478
492
  end
479
-
480
- def uniquify_name(filename)
481
- # we add (1), (2), (n) at the end of a filename if there is a duplicate
482
- copy_pattern = /\((\d+)\)$/
483
- parts = filename.split('.')
484
- ext = if parts.last =~ /gz|zip/ && parts.size > 2
485
- parts.pop(2)
486
- elsif parts.size > 1
487
- parts.pop
488
- end
489
- fn_last_part = parts.pop
490
-
491
- duplicate_counter = 1
492
- loop do
493
- fn_last_part = if fn_last_part =~ copy_pattern
494
- fn_last_part.sub(copy_pattern, "(#{duplicate_counter})")
495
- else
496
- "#{fn_last_part} (#{duplicate_counter})"
497
- end
498
- new_filename = (parts + [fn_last_part, ext]).compact.join('.')
499
- return new_filename unless @filenames_set.include?(new_filename)
500
- duplicate_counter += 1
501
- end
502
- end
503
493
  end
@@ -50,6 +50,8 @@ class ZipTricks::Streamer::DeflatedWriter
50
50
  def finish
51
51
  @compressed_io << @deflater.finish until @deflater.finished?
52
52
  {crc32: @crc.to_i, compressed_size: @compressed_io.tell, uncompressed_size: @uncompressed_size}
53
+ ensure
54
+ @deflater.close
53
55
  end
54
56
 
55
57
  private
@@ -0,0 +1,38 @@
1
+ module ZipTricks::UniquifyFilename
2
+
3
+ # Makes a given filename unique by appending a (n) suffix
4
+ # between just before the filename extension. So "file.txt" gets
5
+ # transformed into "file (1).txt". The transformation is applied
6
+ # repeatedly as long as the generated filename is present
7
+ # in `while_included_in` object
8
+ #
9
+ # @param path[String] the path to make unique
10
+ # @param while_included_in[#include?] an object that stores the list of already used paths
11
+ # @return [String] the path as is, or with the suffix required to make it unique
12
+ def self.call(path, while_included_in)
13
+ return path unless while_included_in.include?(path)
14
+
15
+ # we add (1), (2), (n) at the end of a filename before the filename extension,
16
+ # but only if there is a duplicate
17
+ copy_pattern = /\((\d+)\)$/
18
+ parts = path.split('.')
19
+ ext = if parts.last =~ /gz|zip/ && parts.size > 2
20
+ parts.pop(2)
21
+ elsif parts.size > 1
22
+ parts.pop
23
+ end
24
+ fn_last_part = parts.pop
25
+
26
+ duplicate_counter = 1
27
+ loop do
28
+ fn_last_part = if fn_last_part =~ copy_pattern
29
+ fn_last_part.sub(copy_pattern, "(#{duplicate_counter})")
30
+ else
31
+ "#{fn_last_part} (#{duplicate_counter})"
32
+ end
33
+ new_path = (parts + [fn_last_part, ext]).compact.join('.')
34
+ return new_path unless while_included_in.include?(new_path)
35
+ duplicate_counter += 1
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZipTricks
4
- VERSION = '4.7.3'
4
+ VERSION = '4.8.3'
5
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Some operations (such as CRC32) benefit when they are performed
2
4
  # on larger chunks of data. In certain use cases, it is possible that
3
5
  # the consumer of ZipTricks is going to be writing small chunks
@@ -57,7 +57,7 @@ class ZipTricks::ZipWriter
57
57
  C_UINT2 = 'v' # Encode a 2-byte unsigned little-endian uint
58
58
  C_UINT8 = 'Q<' # Encode an 8-byte unsigned little-endian uint
59
59
  C_CHAR = 'C' # For bit-encoded strings
60
- C_INT4 = 'N' # Encode a 4-byte signed little-endian int
60
+ C_INT4 = 'l<' # Encode a 4-byte signed little-endian int
61
61
 
62
62
  private_constant :FOUR_BYTE_MAX_UINT,
63
63
  :TWO_BYTE_MAX_UINT,
@@ -118,7 +118,7 @@ class ZipTricks::ZipWriter
118
118
  if requires_zip64
119
119
  extra_fields << zip_64_extra_for_local_file_header(compressed_size: compressed_size, uncompressed_size: uncompressed_size)
120
120
  end
121
- extra_fields << timestamp_extra(mtime)
121
+ extra_fields << timestamp_extra_for_local_file_header(mtime)
122
122
 
123
123
  io << [extra_fields.size].pack(C_UINT2) # extra field length 2 bytes
124
124
 
@@ -182,7 +182,7 @@ class ZipTricks::ZipWriter
182
182
  compressed_size: compressed_size,
183
183
  uncompressed_size: uncompressed_size)
184
184
  end
185
- extra_fields << timestamp_extra(mtime)
185
+ extra_fields << timestamp_extra_for_central_directory_entry(mtime)
186
186
 
187
187
  io << [extra_fields.size].pack(C_UINT2) # extra field length 2 bytes
188
188
 
@@ -345,12 +345,14 @@ class ZipTricks::ZipWriter
345
345
  pack_array(data_and_packspecs)
346
346
  end
347
347
 
348
- # Writes the extended timestamp information field. The spec defines 2
348
+ # Writes the extended timestamp information field for local headers.
349
+ #
350
+ # The spec defines 2
349
351
  # different formats - the one for the local file header can also accomodate the
350
352
  # atime and ctime, whereas the one for the central directory can only take
351
353
  # the mtime - and refers the reader to the local header extra to obtain the
352
354
  # remaining times
353
- def timestamp_extra(mtime)
355
+ def timestamp_extra_for_local_file_header(mtime)
354
356
  # Local-header version:
355
357
  #
356
358
  # Value Size Description
@@ -378,16 +380,21 @@ class ZipTricks::ZipWriter
378
380
  # bit 1 if set, access time is present
379
381
  # bit 2 if set, creation time is present
380
382
  # bits 3-7 reserved for additional timestamps; not set
381
- flags = 0b10000000 # Set bit 1 only to indicate only mtime is present
383
+ flags = 0b00000001 # Set the lowest bit only, to indicate that only mtime is present
382
384
  data_and_packspecs = [
383
385
  0x5455, C_UINT2, # tag for this extra block type ("UT")
384
- (1 + 4), C_UINT2, # the size of this block (1 byte used for the Flag + 1 long used for the timestamp)
386
+ (1 + 4), C_UINT2, # the size of this block (1 byte used for the Flag + 3 longs used for the timestamp)
385
387
  flags, C_CHAR, # encode a single byte
386
- mtime.utc.to_i, C_INT4, # Use a signed long, not the unsigned one used by the rest of the ZIP spec.
388
+ mtime.utc.to_i, C_INT4, # Use a signed int, not the unsigned one used by the rest of the ZIP spec.
387
389
  ]
390
+ # The atime and ctime can be omitted if not present
388
391
  pack_array(data_and_packspecs)
389
392
  end
390
393
 
394
+ # Since we do not supply atime or ctime, the contents of the two extra fields (central dir and local header)
395
+ # is exactly the same, so we can use a method alias.
396
+ alias_method :timestamp_extra_for_central_directory_entry, :timestamp_extra_for_local_file_header
397
+
391
398
  # Writes the Zip64 extra field for the central directory header.It differs from the extra used in the local file header because it
392
399
  # also contains the location of the local file header in the ZIP as an 8-byte int.
393
400
  #
@@ -40,6 +40,7 @@ Gem::Specification.new do |spec|
40
40
  spec.add_development_dependency 'complexity_assert'
41
41
  spec.add_development_dependency 'coderay'
42
42
  spec.add_development_dependency 'benchmark-ips'
43
+ spec.add_development_dependency 'allocation_stats', '~> 0.1.5'
43
44
  spec.add_development_dependency 'yard', '~> 0.9'
44
45
  spec.add_development_dependency 'wetransfer_style', '0.6.0'
45
46
  end
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.7.3
4
+ version: 4.8.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-06 00:00:00.000000000 Z
11
+ date: 2020-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -150,6 +150,20 @@ dependencies:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: allocation_stats
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 0.1.5
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 0.1.5
153
167
  - !ruby/object:Gem::Dependency
154
168
  name: yard
155
169
  requirement: !ruby/object:Gem::Requirement
@@ -214,6 +228,7 @@ files:
214
228
  - lib/zip_tricks/file_reader/stored_reader.rb
215
229
  - lib/zip_tricks/null_writer.rb
216
230
  - lib/zip_tricks/output_enumerator.rb
231
+ - lib/zip_tricks/path_set.rb
217
232
  - lib/zip_tricks/rack_body.rb
218
233
  - lib/zip_tricks/rails_streaming.rb
219
234
  - lib/zip_tricks/remote_io.rb
@@ -225,6 +240,7 @@ files:
225
240
  - lib/zip_tricks/streamer/entry.rb
226
241
  - lib/zip_tricks/streamer/stored_writer.rb
227
242
  - lib/zip_tricks/streamer/writable.rb
243
+ - lib/zip_tricks/uniquify_filename.rb
228
244
  - lib/zip_tricks/version.rb
229
245
  - lib/zip_tricks/write_and_tell.rb
230
246
  - lib/zip_tricks/write_buffer.rb
@@ -258,8 +274,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
258
274
  - !ruby/object:Gem::Version
259
275
  version: '0'
260
276
  requirements: []
261
- rubyforge_project:
262
- rubygems_version: 2.6.11
277
+ rubygems_version: 3.1.2
263
278
  signing_key:
264
279
  specification_version: 4
265
280
  summary: Stream out ZIP files from Ruby