zip_tricks 4.5.2 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -162,14 +162,17 @@ class ZipTricks::Streamer
162
162
  # Streamer, because otherwise it is impossible to know it's size upfront.
163
163
  #
164
164
  # @param filename [String] the name of the file in the entry
165
+ # @param modification_time [Time] the modification time of the file in the archive
165
166
  # @param compressed_size [Integer] the size of the compressed entry that
166
167
  # is going to be written into the archive
167
168
  # @param uncompressed_size [Integer] the size of the entry when uncompressed, in bytes
168
169
  # @param crc32 [Integer] the CRC32 checksum of the entry when uncompressed
169
170
  # @param use_data_descriptor [Boolean] whether the entry body will be followed by a data descriptor
170
171
  # @return [Integer] the offset the output IO is at after writing the entry header
171
- def add_deflated_entry(filename:, compressed_size: 0, uncompressed_size: 0, crc32: 0, use_data_descriptor: false)
172
- add_file_and_write_local_header(filename: filename, crc32: crc32,
172
+ def add_deflated_entry(filename:, modification_time: Time.now.utc, compressed_size: 0, uncompressed_size: 0, crc32: 0, use_data_descriptor: false)
173
+ add_file_and_write_local_header(filename: filename,
174
+ modification_time: modification_time,
175
+ crc32: crc32,
173
176
  storage_mode: DEFLATED,
174
177
  compressed_size: compressed_size,
175
178
  uncompressed_size: uncompressed_size,
@@ -186,12 +189,14 @@ class ZipTricks::Streamer
186
189
  # times to write the actual contents of the body.
187
190
  #
188
191
  # @param filename [String] the name of the file in the entry
192
+ # @param modification_time [Time] the modification time of the file in the archive
189
193
  # @param size [Integer] the size of the file when uncompressed, in bytes
190
194
  # @param crc32 [Integer] the CRC32 checksum of the entry when uncompressed
191
195
  # @param use_data_descriptor [Boolean] whether the entry body will be followed by a data descriptor. When in use
192
196
  # @return [Integer] the offset the output IO is at after writing the entry header
193
- def add_stored_entry(filename:, size: 0, crc32: 0, use_data_descriptor: false)
197
+ def add_stored_entry(filename:, modification_time: Time.now.utc, size: 0, crc32: 0, use_data_descriptor: false)
194
198
  add_file_and_write_local_header(filename: filename,
199
+ modification_time: modification_time,
195
200
  crc32: crc32,
196
201
  storage_mode: STORED,
197
202
  compressed_size: size,
@@ -203,9 +208,11 @@ class ZipTricks::Streamer
203
208
  # Adds an empty directory to the archive with a size of 0 and permissions of 755.
204
209
  #
205
210
  # @param dirname [String] the name of the directory in the archive
211
+ # @param modification_time [Time] the modification time of the directory in the archive
206
212
  # @return [Integer] the offset the output IO is at after writing the entry header
207
- def add_empty_directory(dirname:)
213
+ def add_empty_directory(dirname:, modification_time: Time.now.utc)
208
214
  add_file_and_write_local_header(filename: dirname.to_s + '/',
215
+ modification_time: modification_time,
209
216
  crc32: 0,
210
217
  storage_mode: STORED,
211
218
  compressed_size: 0,
@@ -246,10 +253,12 @@ class ZipTricks::Streamer
246
253
  # and attention is recommended.
247
254
  #
248
255
  # @param filename[String] the name of the file in the archive
256
+ # @param modification_time [Time] the modification time of the file in the archive
249
257
  # @yield [#<<, #write] an object that the file contents must be written to that will be automatically closed
250
258
  # @return [#<<, #write, #close] an object that the file contents must be written to, has to be closed manually
251
- def write_stored_file(filename)
259
+ def write_stored_file(filename, modification_time: Time.now.utc)
252
260
  add_stored_entry(filename: filename,
261
+ modification_time: modification_time,
253
262
  use_data_descriptor: true,
254
263
  crc32: 0,
255
264
  size: 0)
@@ -296,9 +305,11 @@ class ZipTricks::Streamer
296
305
  # and attention is recommended.
297
306
  #
298
307
  # @param filename[String] the name of the file in the archive
308
+ # @param modification_time [Time] the modification time of the file in the archive
299
309
  # @yield [#<<, #write] an object that the file contents must be written to
300
- def write_deflated_file(filename)
310
+ def write_deflated_file(filename, modification_time: Time.now.utc)
301
311
  add_deflated_entry(filename: filename,
312
+ modification_time: modification_time,
302
313
  use_data_descriptor: true,
303
314
  crc32: 0,
304
315
  compressed_size: 0,
@@ -388,20 +399,20 @@ class ZipTricks::Streamer
388
399
 
389
400
  private
390
401
 
391
- def add_file_and_write_local_header(filename:,
392
- crc32:,
393
- storage_mode:,
394
- compressed_size:,
395
- uncompressed_size:,
396
- use_data_descriptor:)
402
+ def add_file_and_write_local_header(
403
+ filename:,
404
+ modification_time:,
405
+ crc32:,
406
+ storage_mode:,
407
+ compressed_size:,
408
+ uncompressed_size:,
409
+ use_data_descriptor:)
397
410
 
398
411
  # Clean backslashes and uniqify filenames if there are duplicates
399
412
  filename = remove_backslash(filename)
400
413
  filename = uniquify_name(filename) if @filenames_set.include?(filename)
401
414
 
402
- unless [STORED, DEFLATED].include?(storage_mode)
403
- raise UnknownMode, "Unknown compression mode #{storage_mode}"
404
- end
415
+ raise UnknownMode, "Unknown compression mode #{storage_mode}" unless [STORED, DEFLATED].include?(storage_mode)
405
416
 
406
417
  raise Overflow, 'Filename is too long' if filename.bytesize > 0xFFFF
407
418
 
@@ -416,11 +427,13 @@ class ZipTricks::Streamer
416
427
  compressed_size,
417
428
  uncompressed_size,
418
429
  storage_mode,
419
- mtime = Time.now.utc,
430
+ modification_time,
420
431
  use_data_descriptor)
432
+
421
433
  @files << e
422
434
  @filenames_set << e.filename
423
435
  @local_header_offsets << @out.tell
436
+
424
437
  @writer.write_local_file_header(io: @out,
425
438
  gp_flags: e.gp_flags,
426
439
  crc32: e.crc32,
@@ -1,39 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Sends writes to the given `io` compressed using a {Zlib::Deflate}. Also
4
+ # registers data passing through it in a CRC32 checksum calculator. Is made to be completely
5
+ # interchangeable with the StoredWriter in terms of interface.
3
6
  class ZipTricks::Streamer::DeflatedWriter
4
7
  # After how many bytes of incoming data the deflater for the
5
8
  # contents must be flushed. This is done to prevent unreasonable
6
- # memory use when archiving large files.
9
+ # memory use when archiving large files, and to ensure we write to
10
+ # the socket often enough while still maintaining acceptable
11
+ # compression
7
12
  FLUSH_EVERY_N_BYTES = 1024 * 1024 * 5
8
13
 
14
+ # The amount of bytes we will buffer before computing the intermediate
15
+ # CRC32 checksums. Benchmarks show that the optimum is 64KB (see
16
+ # `bench/buffered_crc32_bench.rb), if that is exceeded Zlib is going
17
+ # to perform internal CRC combine calls which will make the speed go down again.
18
+ CRC32_BUFFER_SIZE = 64 * 1024
19
+
9
20
  def initialize(io)
10
- @io = io
21
+ @compressed_io = ZipTricks::WriteAndTell.new(io)
11
22
  @uncompressed_size = 0
12
- @started_at = @io.tell
13
- @crc = ZipTricks::StreamCRC32.new
14
23
  @deflater = ::Zlib::Deflate.new(Zlib::DEFAULT_COMPRESSION, -::Zlib::MAX_WBITS)
24
+ @crc = ZipTricks::WriteBuffer.new(ZipTricks::StreamCRC32.new, CRC32_BUFFER_SIZE)
15
25
  @bytes_since_last_flush = 0
16
26
  end
17
27
 
18
- def finish
19
- @io << @deflater.finish until @deflater.finished?
20
- {crc32: @crc.to_i, compressed_size: @io.tell - @started_at, uncompressed_size: @uncompressed_size}
21
- end
22
-
28
+ # Writes the given data into the deflater, and flushes the deflater
29
+ # after having written more than FLUSH_EVERY_N_BYTES bytes of data
30
+ #
31
+ # @param data[String] data to be written
32
+ # @return self
23
33
  def <<(data)
24
34
  @uncompressed_size += data.bytesize
25
35
  @bytes_since_last_flush += data.bytesize
26
- @io << @deflater.deflate(data)
36
+ @compressed_io << @deflater.deflate(data)
27
37
  @crc << data
38
+
28
39
  interim_flush
40
+
29
41
  self
30
42
  end
31
43
 
44
+ # Returns the amount of data received for writing, the amount of
45
+ # compressed data written and the CRC32 checksum. The return value
46
+ # can be directly used as the argument to {Streamer#update_last_entry_and_write_data_descriptor}
47
+ #
48
+ # @param data[String] data to be written
49
+ # @return [Hash] a hash of `{crc32, compressed_size, uncompressed_size}`
50
+ def finish
51
+ @compressed_io << @deflater.finish until @deflater.finished?
52
+ {crc32: @crc.to_i, compressed_size: @compressed_io.tell, uncompressed_size: @uncompressed_size}
53
+ end
54
+
32
55
  private
33
56
 
34
57
  def interim_flush
35
58
  return if @bytes_since_last_flush < FLUSH_EVERY_N_BYTES
36
- @io << @deflater.flush
59
+ @compressed_io << @deflater.flush
37
60
  @bytes_since_last_flush = 0
38
61
  end
39
62
  end
@@ -8,7 +8,6 @@ class ZipTricks::Streamer::Entry < Struct.new(:filename, :crc32, :compressed_siz
8
8
  def initialize(*)
9
9
  super
10
10
  filename.force_encoding(Encoding::UTF_8)
11
- # Rubocop: convention: Avoid using rescue in its modifier form.
12
11
  @requires_efs_flag = !(begin
13
12
  filename.encode(Encoding::ASCII)
14
13
  rescue
@@ -1,23 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Rubocop: convention: Missing top-level class documentation comment.
3
+ # Sends writes to the given `io`, and also registers all the data passing
4
+ # through it in a CRC32 checksum calculator. Is made to be completely
5
+ # interchangeable with the DeflatedWriter in terms of interface.
4
6
  class ZipTricks::Streamer::StoredWriter
7
+ # The amount of bytes we will buffer before computing the intermediate
8
+ # CRC32 checksums. Benchmarks show that the optimum is 64KB (see
9
+ # `bench/buffered_crc32_bench.rb), if that is exceeded Zlib is going
10
+ # to perform internal CRC combine calls which will make the speed go down again.
11
+ CRC32_BUFFER_SIZE = 64 * 1024
12
+
5
13
  def initialize(io)
6
- @io = io
7
- @uncompressed_size = 0
8
- @compressed_size = 0
9
- @started_at = @io.tell
10
- @crc = ZipTricks::StreamCRC32.new
14
+ @io = ZipTricks::WriteAndTell.new(io)
15
+ @crc = ZipTricks::WriteBuffer.new(ZipTricks::StreamCRC32.new, CRC32_BUFFER_SIZE)
11
16
  end
12
17
 
18
+ # Writes the given data to the contained IO object.
19
+ #
20
+ # @param data[String] data to be written
21
+ # @return self
13
22
  def <<(data)
14
23
  @io << data
15
24
  @crc << data
16
25
  self
17
26
  end
18
27
 
28
+ # Returns the amount of data written and the CRC32 checksum. The return value
29
+ # can be directly used as the argument to {Streamer#update_last_entry_and_write_data_descriptor}
30
+ #
31
+ # @param data[String] data to be written
32
+ # @return [Hash] a hash of `{crc32, compressed_size, uncompressed_size}`
19
33
  def finish
20
- size = @io.tell - @started_at
21
- {crc32: @crc.to_i, compressed_size: size, uncompressed_size: size}
34
+ {crc32: @crc.to_i, compressed_size: @io.tell, uncompressed_size: @io.tell}
22
35
  end
23
36
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZipTricks
4
- VERSION = '4.5.2'
4
+ VERSION = '4.6.0'
5
5
  end
@@ -0,0 +1,49 @@
1
+ # Some operations (such as CRC32) benefit when they are performed
2
+ # on larger chunks of data. In certain use cases, it is possible that
3
+ # the consumer of ZipTricks is going to be writing small chunks
4
+ # in rapid succession, so CRC32 is going to have to perform a lot of
5
+ # CRC32 combine operations - and this adds up. Since the CRC32 value
6
+ # is usually not needed until the complete output has completed
7
+ # we can buffer at least some amount of data before computing CRC32 over it.
8
+ class ZipTricks::WriteBuffer
9
+ # Creates a new WriteBuffer bypassing into a given writable object
10
+ #
11
+ # @param writable[#<<] An object that responds to `#<<` with string as argument
12
+ # @param buffer_size[Integer] How many bytes to buffer
13
+ def initialize(writable, buffer_size)
14
+ @buf = StringIO.new
15
+ @buffer_size = buffer_size
16
+ @writable = writable
17
+ end
18
+
19
+ # Appends the given data to the write buffer, and flushes the buffer into the
20
+ # writable if the buffer size exceeds the `buffer_size` given at initialization
21
+ #
22
+ # @param data[String] data to be written
23
+ # @return self
24
+ def <<(data)
25
+ @buf << data
26
+ flush! if @buf.size > @buffer_size
27
+ self
28
+ end
29
+
30
+ # Explicitly flushes the buffer if it contains anything
31
+ #
32
+ # @return self
33
+ def flush!
34
+ @writable << @buf.string if @buf.size > 0
35
+ @buf.truncate(0)
36
+ @buf.rewind
37
+ self
38
+ end
39
+
40
+ # Flushes the buffer and returns the result of `#to_i` of the contained `writable`.
41
+ # Primarily facilitates working with StreamCRC32 objects where you finish the
42
+ # computation by retrieving the CRC as an integer
43
+ #
44
+ # @return [Integer] the return value of `writable#to_i`
45
+ def to_i
46
+ flush!
47
+ @writable.to_i
48
+ end
49
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Layout/CommentIndentation, Metrics/LineLength, Metrics/AbcSize, Style/RedundantParentheses, Metrics/PerceivedComplexity, Layout/MultilineOperationIndentation, Layout/AlignParameters, Style/ConditionalAssignment, Layout/ExtraSpacing, Metrics/CyclomaticComplexity, Lint/UselessAssignment, Metrics/ParameterLists, Layout/LeadingCommentSpace, Naming/ConstantName
4
- #
5
3
  # A low-level ZIP file data writer. You can use it to write out various headers and central directory elements
6
4
  # separately. The class handles the actual encoding of the data according to the ZIP format APPNOTE document.
7
5
  #
@@ -29,7 +27,7 @@
29
27
  class ZipTricks::ZipWriter
30
28
  FOUR_BYTE_MAX_UINT = 0xFFFFFFFF
31
29
  TWO_BYTE_MAX_UINT = 0xFFFF
32
- ZIP_TRICKS_COMMENT = 'Written using ZipTricks %s' % ZipTricks::VERSION
30
+ ZIP_TRICKS_COMMENT = 'Written using ZipTricks %<version>s' % {version: ZipTricks::VERSION}
33
31
  VERSION_MADE_BY = 52
34
32
  VERSION_NEEDED_TO_EXTRACT = 20
35
33
  VERSION_NEEDED_TO_EXTRACT_ZIP64 = 45
@@ -41,13 +39,13 @@ class ZipTricks::ZipWriter
41
39
  # We snatch the incantations from Rubyzip for this.
42
40
  unix_perms = 0o644
43
41
  file_type_file = 0o10
44
- external_attrs = (file_type_file << 12 | (unix_perms & 0o7777)) << 16
42
+ (file_type_file << 12 | (unix_perms & 0o7777)) << 16
45
43
  end
46
44
  EMPTY_DIRECTORY_EXTERNAL_ATTRS = begin
47
45
  # Applies permissions to an empty directory.
48
46
  unix_perms = 0o755
49
47
  file_type_dir = 0o04
50
- external_attrs = (file_type_dir << 12 | (unix_perms & 0o7777)) << 16
48
+ (file_type_dir << 12 | (unix_perms & 0o7777)) << 16
51
49
  end
52
50
  MADE_BY_SIGNATURE = begin
53
51
  # A combination of the VERSION_MADE_BY low byte and the OS type high byte
@@ -55,16 +53,23 @@ class ZipTricks::ZipWriter
55
53
  [VERSION_MADE_BY, os_type].pack('CC')
56
54
  end
57
55
 
58
- C_V = 'V' # Encode a 4-byte unsigned little-endian uint
59
- C_v = 'v' # Encode a 2-byte unsigned little-endian uint
60
- C_Qe = 'Q<' # Encode an 8-byte unsigned little-endian uint
61
- C_C = 'C' # For bit-encoded strings
62
- C_N = 'N' # Encode a 4-byte signed little-endian int
63
-
64
- private_constant :FOUR_BYTE_MAX_UINT, :TWO_BYTE_MAX_UINT,
65
- :VERSION_MADE_BY, :VERSION_NEEDED_TO_EXTRACT, :VERSION_NEEDED_TO_EXTRACT_ZIP64,
66
- :DEFAULT_EXTERNAL_ATTRS, :MADE_BY_SIGNATURE,
67
- :C_V, :C_v, :C_Qe, :ZIP_TRICKS_COMMENT
56
+ C_UINT4 = 'V' # Encode a 4-byte unsigned little-endian uint
57
+ C_UINT2 = 'v' # Encode a 2-byte unsigned little-endian uint
58
+ C_UINT8 = 'Q<' # Encode an 8-byte unsigned little-endian uint
59
+ C_CHAR = 'C' # For bit-encoded strings
60
+ C_INT4 = 'N' # Encode a 4-byte signed little-endian int
61
+
62
+ private_constant :FOUR_BYTE_MAX_UINT,
63
+ :TWO_BYTE_MAX_UINT,
64
+ :VERSION_MADE_BY,
65
+ :VERSION_NEEDED_TO_EXTRACT,
66
+ :VERSION_NEEDED_TO_EXTRACT_ZIP64,
67
+ :DEFAULT_EXTERNAL_ATTRS,
68
+ :MADE_BY_SIGNATURE,
69
+ :C_UINT4,
70
+ :C_UINT2,
71
+ :C_UINT8,
72
+ :ZIP_TRICKS_COMMENT
68
73
 
69
74
  # Writes the local file header, that precedes the actual file _data_.
70
75
  #
@@ -80,29 +85,29 @@ class ZipTricks::ZipWriter
80
85
  def write_local_file_header(io:, filename:, compressed_size:, uncompressed_size:, crc32:, gp_flags:, mtime:, storage_mode:)
81
86
  requires_zip64 = (compressed_size > FOUR_BYTE_MAX_UINT || uncompressed_size > FOUR_BYTE_MAX_UINT)
82
87
 
83
- io << [0x04034b50].pack(C_V) # local file header signature 4 bytes (0x04034b50)
84
- if requires_zip64 # version needed to extract 2 bytes
85
- io << [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_v)
88
+ io << [0x04034b50].pack(C_UINT4) # local file header signature 4 bytes (0x04034b50)
89
+ io << if requires_zip64 # version needed to extract 2 bytes
90
+ [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_UINT2)
86
91
  else
87
- io << [VERSION_NEEDED_TO_EXTRACT].pack(C_v)
92
+ [VERSION_NEEDED_TO_EXTRACT].pack(C_UINT2)
88
93
  end
89
94
 
90
- io << [gp_flags].pack(C_v) # general purpose bit flag 2 bytes
91
- io << [storage_mode].pack(C_v) # compression method 2 bytes
92
- io << [to_binary_dos_time(mtime)].pack(C_v) # last mod file time 2 bytes
93
- io << [to_binary_dos_date(mtime)].pack(C_v) # last mod file date 2 bytes
94
- io << [crc32].pack(C_V) # crc-32 4 bytes
95
+ io << [gp_flags].pack(C_UINT2) # general purpose bit flag 2 bytes
96
+ io << [storage_mode].pack(C_UINT2) # compression method 2 bytes
97
+ io << [to_binary_dos_time(mtime)].pack(C_UINT2) # last mod file time 2 bytes
98
+ io << [to_binary_dos_date(mtime)].pack(C_UINT2) # last mod file date 2 bytes
99
+ io << [crc32].pack(C_UINT4) # crc-32 4 bytes
95
100
 
96
101
  if requires_zip64
97
- io << [FOUR_BYTE_MAX_UINT].pack(C_V) # compressed size 4 bytes
98
- io << [FOUR_BYTE_MAX_UINT].pack(C_V) # uncompressed size 4 bytes
102
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # compressed size 4 bytes
103
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # uncompressed size 4 bytes
99
104
  else
100
- io << [compressed_size].pack(C_V) # compressed size 4 bytes
101
- io << [uncompressed_size].pack(C_V) # uncompressed size 4 bytes
105
+ io << [compressed_size].pack(C_UINT4) # compressed size 4 bytes
106
+ io << [uncompressed_size].pack(C_UINT4) # uncompressed size 4 bytes
102
107
  end
103
108
 
104
109
  # Filename should not be longer than 0xFFFF otherwise this wont fit here
105
- io << [filename.bytesize].pack(C_v) # file name length 2 bytes
110
+ io << [filename.bytesize].pack(C_UINT2) # file name length 2 bytes
106
111
 
107
112
  extra_fields = StringIO.new
108
113
 
@@ -115,7 +120,7 @@ class ZipTricks::ZipWriter
115
120
  end
116
121
  extra_fields << timestamp_extra(mtime)
117
122
 
118
- io << [extra_fields.size].pack(C_v) # extra field length 2 bytes
123
+ io << [extra_fields.size].pack(C_UINT2) # extra field length 2 bytes
119
124
 
120
125
  io << filename # file name (variable size)
121
126
  io << extra_fields.string
@@ -144,32 +149,32 @@ class ZipTricks::ZipWriter
144
149
  # At this point if the header begins somewhere beyound 0xFFFFFFFF we _have_ to record the offset
145
150
  # of the local file header as a zip64 extra field, so we give up, give in, you loose, love will always win...
146
151
  add_zip64 = (local_file_header_location > FOUR_BYTE_MAX_UINT) ||
147
- (compressed_size > FOUR_BYTE_MAX_UINT) || (uncompressed_size > FOUR_BYTE_MAX_UINT)
152
+ (compressed_size > FOUR_BYTE_MAX_UINT) || (uncompressed_size > FOUR_BYTE_MAX_UINT)
148
153
 
149
- io << [0x02014b50].pack(C_V) # central file header signature 4 bytes (0x02014b50)
154
+ io << [0x02014b50].pack(C_UINT4) # central file header signature 4 bytes (0x02014b50)
150
155
  io << MADE_BY_SIGNATURE # version made by 2 bytes
151
- if add_zip64
152
- io << [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_v) # version needed to extract 2 bytes
156
+ io << if add_zip64
157
+ [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_UINT2) # version needed to extract 2 bytes
153
158
  else
154
- io << [VERSION_NEEDED_TO_EXTRACT].pack(C_v) # version needed to extract 2 bytes
159
+ [VERSION_NEEDED_TO_EXTRACT].pack(C_UINT2) # version needed to extract 2 bytes
155
160
  end
156
161
 
157
- io << [gp_flags].pack(C_v) # general purpose bit flag 2 bytes
158
- io << [storage_mode].pack(C_v) # compression method 2 bytes
159
- io << [to_binary_dos_time(mtime)].pack(C_v) # last mod file time 2 bytes
160
- io << [to_binary_dos_date(mtime)].pack(C_v) # last mod file date 2 bytes
161
- io << [crc32].pack(C_V) # crc-32 4 bytes
162
+ io << [gp_flags].pack(C_UINT2) # general purpose bit flag 2 bytes
163
+ io << [storage_mode].pack(C_UINT2) # compression method 2 bytes
164
+ io << [to_binary_dos_time(mtime)].pack(C_UINT2) # last mod file time 2 bytes
165
+ io << [to_binary_dos_date(mtime)].pack(C_UINT2) # last mod file date 2 bytes
166
+ io << [crc32].pack(C_UINT4) # crc-32 4 bytes
162
167
 
163
168
  if add_zip64
164
- io << [FOUR_BYTE_MAX_UINT].pack(C_V) # compressed size 4 bytes
165
- io << [FOUR_BYTE_MAX_UINT].pack(C_V) # uncompressed size 4 bytes
169
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # compressed size 4 bytes
170
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # uncompressed size 4 bytes
166
171
  else
167
- io << [compressed_size].pack(C_V) # compressed size 4 bytes
168
- io << [uncompressed_size].pack(C_V) # uncompressed size 4 bytes
172
+ io << [compressed_size].pack(C_UINT4) # compressed size 4 bytes
173
+ io << [uncompressed_size].pack(C_UINT4) # uncompressed size 4 bytes
169
174
  end
170
175
 
171
176
  # Filename should not be longer than 0xFFFF otherwise this wont fit here
172
- io << [filename.bytesize].pack(C_v) # file name length 2 bytes
177
+ io << [filename.bytesize].pack(C_UINT2) # file name length 2 bytes
173
178
 
174
179
  extra_fields = StringIO.new
175
180
  if add_zip64
@@ -179,36 +184,37 @@ class ZipTricks::ZipWriter
179
184
  end
180
185
  extra_fields << timestamp_extra(mtime)
181
186
 
182
- io << [extra_fields.size].pack(C_v) # extra field length 2 bytes
187
+ io << [extra_fields.size].pack(C_UINT2) # extra field length 2 bytes
183
188
 
184
- io << [0].pack(C_v) # file comment length 2 bytes
189
+ io << [0].pack(C_UINT2) # file comment length 2 bytes
185
190
 
186
191
  # For The Unarchiver < 3.11.1 this field has to be set to the overflow value if zip64 is used
187
192
  # because otherwise it does not properly advance the pointer when reading the Zip64 extra field
188
193
  # https://bitbucket.org/WAHa_06x36/theunarchiver/pull-requests/2/bug-fix-for-zip64-extra-field-parser/diff
189
- if add_zip64 # disk number start 2 bytes
190
- io << [TWO_BYTE_MAX_UINT].pack(C_v)
194
+ io << if add_zip64 # disk number start 2 bytes
195
+ [TWO_BYTE_MAX_UINT].pack(C_UINT2)
191
196
  else
192
- io << [0].pack(C_v)
193
- end
194
- io << [0].pack(C_v) # internal file attributes 2 bytes
197
+ [0].pack(C_UINT2)
198
+ end
199
+ io << [0].pack(C_UINT2) # internal file attributes 2 bytes
195
200
 
196
201
  # Because the add_empty_directory method will create a directory with a trailing "/",
197
202
  # this check can be used to assign proper permissions to the created directory.
198
- if filename.end_with?('/')
199
- io << [EMPTY_DIRECTORY_EXTERNAL_ATTRS].pack(C_V)
203
+ io << if filename.end_with?('/')
204
+ [EMPTY_DIRECTORY_EXTERNAL_ATTRS].pack(C_UINT4)
200
205
  else
201
- io << [DEFAULT_EXTERNAL_ATTRS].pack(C_V) # external file attributes 4 bytes
206
+ [DEFAULT_EXTERNAL_ATTRS].pack(C_UINT4) # external file attributes 4 bytes
202
207
  end
203
208
 
204
- if add_zip64 # relative offset of local header 4 bytes
205
- io << [FOUR_BYTE_MAX_UINT].pack(C_V)
209
+ io << if add_zip64 # relative offset of local header 4 bytes
210
+ [FOUR_BYTE_MAX_UINT].pack(C_UINT4)
206
211
  else
207
- io << [local_file_header_location].pack(C_V)
212
+ [local_file_header_location].pack(C_UINT4)
208
213
  end
214
+
209
215
  io << filename # file name (variable size)
210
216
  io << extra_fields.string # extra field (variable size)
211
- #(empty) # file comment (variable size)
217
+ # (empty) # file comment (variable size)
212
218
  end
213
219
 
214
220
  # Writes the data descriptor following the file data for a file whose local file header
@@ -221,16 +227,16 @@ class ZipTricks::ZipWriter
221
227
  # @param uncompressed_size[Fixnum] The size of the file once extracted
222
228
  # @return [void]
223
229
  def write_data_descriptor(io:, compressed_size:, uncompressed_size:, crc32:)
224
- io << [0x08074b50].pack(C_V) # Although not originally assigned a signature, the value
225
- # 0x08074b50 has commonly been adopted as a signature value
226
- # for the data descriptor record.
227
- io << [crc32].pack(C_V) # crc-32 4 bytes
230
+ io << [0x08074b50].pack(C_UINT4) # Although not originally assigned a signature, the value
231
+ # 0x08074b50 has commonly been adopted as a signature value
232
+ # for the data descriptor record.
233
+ io << [crc32].pack(C_UINT4) # crc-32 4 bytes
228
234
 
229
235
  # If one of the sizes is above 0xFFFFFFF use ZIP64 lengths (8 bytes) instead. A good unarchiver
230
236
  # will decide to unpack it as such if it finds the Zip64 extra for the file in the central directory.
231
237
  # So also use the opportune moment to switch the entry to Zip64 if needed
232
238
  requires_zip64 = (compressed_size > FOUR_BYTE_MAX_UINT || uncompressed_size > FOUR_BYTE_MAX_UINT)
233
- pack_spec = requires_zip64 ? C_Qe : C_V
239
+ pack_spec = requires_zip64 ? C_UINT8 : C_UINT4
234
240
 
235
241
  io << [compressed_size].pack(pack_spec) # compressed size 4 bytes, or 8 bytes for ZIP64
236
242
  io << [uncompressed_size].pack(pack_spec) # uncompressed size 4 bytes, or 8 bytes for ZIP64
@@ -248,77 +254,77 @@ class ZipTricks::ZipWriter
248
254
  zip64_eocdr_offset = start_of_central_directory_location + central_directory_size
249
255
 
250
256
  zip64_required = central_directory_size > FOUR_BYTE_MAX_UINT ||
251
- start_of_central_directory_location > FOUR_BYTE_MAX_UINT ||
252
- zip64_eocdr_offset > FOUR_BYTE_MAX_UINT ||
253
- num_files_in_archive > TWO_BYTE_MAX_UINT
257
+ start_of_central_directory_location > FOUR_BYTE_MAX_UINT ||
258
+ zip64_eocdr_offset > FOUR_BYTE_MAX_UINT ||
259
+ num_files_in_archive > TWO_BYTE_MAX_UINT
254
260
 
255
261
  # Then, if zip64 is used
256
262
  if zip64_required
257
263
  # [zip64 end of central directory record]
258
- # zip64 end of central dir
259
- io << [0x06064b50].pack(C_V) # signature 4 bytes (0x06064b50)
260
- io << [44].pack(C_Qe) # size of zip64 end of central
261
- # directory record 8 bytes
262
- # (this is ex. the 12 bytes of the signature and the size value itself).
263
- # Without the extensible data sector (which we are not using)
264
- # it is always 44 bytes.
264
+ # zip64 end of central dir
265
+ io << [0x06064b50].pack(C_UINT4) # signature 4 bytes (0x06064b50)
266
+ io << [44].pack(C_UINT8) # size of zip64 end of central
267
+ # directory record 8 bytes
268
+ # (this is ex. the 12 bytes of the signature and the size value itself).
269
+ # Without the extensible data sector (which we are not using)
270
+ # it is always 44 bytes.
265
271
  io << MADE_BY_SIGNATURE # version made by 2 bytes
266
- io << [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_v) # version needed to extract 2 bytes
267
- io << [0].pack(C_V) # number of this disk 4 bytes
268
- io << [0].pack(C_V) # number of the disk with the
269
- # start of the central directory 4 bytes
270
- io << [num_files_in_archive].pack(C_Qe) # total number of entries in the
271
- # central directory on this disk 8 bytes
272
- io << [num_files_in_archive].pack(C_Qe) # total number of entries in the
273
- # central directory 8 bytes
274
- io << [central_directory_size].pack(C_Qe) # size of the central directory 8 bytes
275
- # offset of start of central
276
- # directory with respect to
277
- io << [start_of_central_directory_location].pack(C_Qe) # the starting disk number 8 bytes
278
- # zip64 extensible data sector (variable size), blank for us
272
+ io << [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_UINT2) # version needed to extract 2 bytes
273
+ io << [0].pack(C_UINT4) # number of this disk 4 bytes
274
+ io << [0].pack(C_UINT4) # number of the disk with the
275
+ # start of the central directory 4 bytes
276
+ io << [num_files_in_archive].pack(C_UINT8) # total number of entries in the
277
+ # central directory on this disk 8 bytes
278
+ io << [num_files_in_archive].pack(C_UINT8) # total number of entries in the
279
+ # central directory 8 bytes
280
+ io << [central_directory_size].pack(C_UINT8) # size of the central directory 8 bytes
281
+ # offset of start of central
282
+ # directory with respect to
283
+ io << [start_of_central_directory_location].pack(C_UINT8) # the starting disk number 8 bytes
284
+ # zip64 extensible data sector (variable size), blank for us
279
285
 
280
286
  # [zip64 end of central directory locator]
281
- io << [0x07064b50].pack(C_V) # zip64 end of central dir locator
282
- # signature 4 bytes (0x07064b50)
283
- io << [0].pack(C_V) # number of the disk with the
284
- # start of the zip64 end of
285
- # central directory 4 bytes
286
- io << [zip64_eocdr_offset].pack(C_Qe) # relative offset of the zip64
287
- # end of central directory record 8 bytes
288
- # (note: "relative" is actually "from the start of the file")
289
- io << [1].pack(C_V) # total number of disks 4 bytes
287
+ io << [0x07064b50].pack(C_UINT4) # zip64 end of central dir locator
288
+ # signature 4 bytes (0x07064b50)
289
+ io << [0].pack(C_UINT4) # number of the disk with the
290
+ # start of the zip64 end of
291
+ # central directory 4 bytes
292
+ io << [zip64_eocdr_offset].pack(C_UINT8) # relative offset of the zip64
293
+ # end of central directory record 8 bytes
294
+ # (note: "relative" is actually "from the start of the file")
295
+ io << [1].pack(C_UINT4) # total number of disks 4 bytes
290
296
  end
291
297
 
292
298
  # Then the end of central directory record:
293
- io << [0x06054b50].pack(C_V) # end of central dir signature 4 bytes (0x06054b50)
294
- io << [0].pack(C_v) # number of this disk 2 bytes
295
- io << [0].pack(C_v) # number of the disk with the
296
- # start of the central directory 2 bytes
299
+ io << [0x06054b50].pack(C_UINT4) # end of central dir signature 4 bytes (0x06054b50)
300
+ io << [0].pack(C_UINT2) # number of this disk 2 bytes
301
+ io << [0].pack(C_UINT2) # number of the disk with the
302
+ # start of the central directory 2 bytes
297
303
 
298
304
  if zip64_required # the number of entries will be read from the zip64 part of the central directory
299
- io << [TWO_BYTE_MAX_UINT].pack(C_v) # total number of entries in the
300
- # central directory on this disk 2 bytes
301
- io << [TWO_BYTE_MAX_UINT].pack(C_v) # total number of entries in
302
- # the central directory 2 bytes
305
+ io << [TWO_BYTE_MAX_UINT].pack(C_UINT2) # total number of entries in the
306
+ # central directory on this disk 2 bytes
307
+ io << [TWO_BYTE_MAX_UINT].pack(C_UINT2) # total number of entries in
308
+ # the central directory 2 bytes
303
309
  else
304
- io << [num_files_in_archive].pack(C_v) # total number of entries in the
305
- # central directory on this disk 2 bytes
306
- io << [num_files_in_archive].pack(C_v) # total number of entries in
307
- # the central directory 2 bytes
310
+ io << [num_files_in_archive].pack(C_UINT2) # total number of entries in the
311
+ # central directory on this disk 2 bytes
312
+ io << [num_files_in_archive].pack(C_UINT2) # total number of entries in
313
+ # the central directory 2 bytes
308
314
  end
309
315
 
310
316
  if zip64_required
311
- io << [FOUR_BYTE_MAX_UINT].pack(C_V) # size of the central directory 4 bytes
312
- io << [FOUR_BYTE_MAX_UINT].pack(C_V) # offset of start of central
313
- # directory with respect to
314
- # the starting disk number 4 bytes
317
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # size of the central directory 4 bytes
318
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # offset of start of central
319
+ # directory with respect to
320
+ # the starting disk number 4 bytes
315
321
  else
316
- io << [central_directory_size].pack(C_V) # size of the central directory 4 bytes
317
- io << [start_of_central_directory_location].pack(C_V) # offset of start of central
318
- # directory with respect to
319
- # the starting disk number 4 bytes
322
+ io << [central_directory_size].pack(C_UINT4) # size of the central directory 4 bytes
323
+ io << [start_of_central_directory_location].pack(C_UINT4) # offset of start of central
324
+ # directory with respect to
325
+ # the starting disk number 4 bytes
320
326
  end
321
- io << [comment.bytesize].pack(C_v) # .ZIP file comment length 2 bytes
327
+ io << [comment.bytesize].pack(C_UINT2) # .ZIP file comment length 2 bytes
322
328
  io << comment # .ZIP file comment (variable size)
323
329
  end
324
330
 
@@ -331,10 +337,10 @@ class ZipTricks::ZipWriter
331
337
  # @return [String]
332
338
  def zip_64_extra_for_local_file_header(compressed_size:, uncompressed_size:)
333
339
  data_and_packspecs = [
334
- 0x0001, C_v, # 2 bytes Tag for this "extra" block type
335
- 16, C_v, # 2 bytes Size of this "extra" block. For us it will always be 16 (2x8)
336
- uncompressed_size, C_Qe, # 8 bytes Original uncompressed file size
337
- compressed_size, C_Qe, # 8 bytes Size of compressed data
340
+ 0x0001, C_UINT2, # 2 bytes Tag for this "extra" block type
341
+ 16, C_UINT2, # 2 bytes Size of this "extra" block. For us it will always be 16 (2x8)
342
+ uncompressed_size, C_UINT8, # 8 bytes Original uncompressed file size
343
+ compressed_size, C_UINT8, # 8 bytes Size of compressed data
338
344
  ]
339
345
  pack_array(data_and_packspecs)
340
346
  end
@@ -374,10 +380,10 @@ class ZipTricks::ZipWriter
374
380
  # bits 3-7 reserved for additional timestamps; not set
375
381
  flags = 0b10000000 # Set bit 1 only to indicate only mtime is present
376
382
  data_and_packspecs = [
377
- 0x5455, C_v, # tag for this extra block type ("UT")
378
- (1 + 4), C_v, # the size of this block (1 byte used for the Flag + 1 long used for the timestamp)
379
- flags, C_C, # encode a single byte
380
- mtime.utc.to_i, C_N, # Use a signed long, not the unsigned one used by the rest of the ZIP spec.
383
+ 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)
385
+ 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.
381
387
  ]
382
388
  pack_array(data_and_packspecs)
383
389
  end
@@ -391,12 +397,12 @@ class ZipTricks::ZipWriter
391
397
  # @return [String]
392
398
  def zip_64_extra_for_central_directory_file_header(compressed_size:, uncompressed_size:, local_file_header_location:)
393
399
  data_and_packspecs = [
394
- 0x0001, C_v, # 2 bytes Tag for this "extra" block type
395
- 28, C_v, # 2 bytes Size of this "extra" block. For us it will always be 28
396
- uncompressed_size, C_Qe, # 8 bytes Original uncompressed file size
397
- compressed_size, C_Qe, # 8 bytes Size of compressed data
398
- local_file_header_location, C_Qe, # 8 bytes Offset of local header record
399
- 0, C_V, # 4 bytes Number of the disk on which this file starts
400
+ 0x0001, C_UINT2, # 2 bytes Tag for this "extra" block type
401
+ 28, C_UINT2, # 2 bytes Size of this "extra" block. For us it will always be 28
402
+ uncompressed_size, C_UINT8, # 8 bytes Original uncompressed file size
403
+ compressed_size, C_UINT8, # 8 bytes Size of compressed data
404
+ local_file_header_location, C_UINT8, # 8 bytes Offset of local header record
405
+ 0, C_UINT4, # 4 bytes Number of the disk on which this file starts
400
406
  ]
401
407
  pack_array(data_and_packspecs)
402
408
  end
@@ -406,7 +412,7 @@ class ZipTricks::ZipWriter
406
412
  end
407
413
 
408
414
  def to_binary_dos_date(t)
409
- (t.day) + (t.month << 5) + ((t.year - 1980) << 9)
415
+ t.day + (t.month << 5) + ((t.year - 1980) << 9)
410
416
  end
411
417
 
412
418
  # Unzips a given array of tuples of "numeric value, pack specifier" and then packs all the odd