zip_kit 6.0.1 → 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,7 +27,7 @@
27
27
  class ZipKit::ZipWriter
28
28
  FOUR_BYTE_MAX_UINT = 0xFFFFFFFF
29
29
  TWO_BYTE_MAX_UINT = 0xFFFF
30
- ZIP_TRICKS_COMMENT = "Written using ZipKit %<version>s" % {version: ZipKit::VERSION}
30
+ ZIP_KIT_COMMENT = "Written using ZipKit %<version>s" % {version: ZipKit::VERSION}
31
31
  VERSION_MADE_BY = 52
32
32
  VERSION_NEEDED_TO_EXTRACT = 20
33
33
  VERSION_NEEDED_TO_EXTRACT_ZIP64 = 45
@@ -58,7 +58,7 @@ class ZipKit::ZipWriter
58
58
  :C_UINT4,
59
59
  :C_UINT2,
60
60
  :C_UINT8,
61
- :ZIP_TRICKS_COMMENT
61
+ :ZIP_KIT_COMMENT
62
62
 
63
63
  # Writes the local file header, that precedes the actual file _data_.
64
64
  #
@@ -74,29 +74,41 @@ class ZipKit::ZipWriter
74
74
  def write_local_file_header(io:, filename:, compressed_size:, uncompressed_size:, crc32:, gp_flags:, mtime:, storage_mode:)
75
75
  requires_zip64 = compressed_size > FOUR_BYTE_MAX_UINT || uncompressed_size > FOUR_BYTE_MAX_UINT
76
76
 
77
- io << [0x04034b50].pack(C_UINT4) # local file header signature 4 bytes (0x04034b50)
78
- io << if requires_zip64 # version needed to extract 2 bytes
77
+ # local file header signature 4 bytes (0x04034b50)
78
+ io << [0x04034b50].pack(C_UINT4)
79
+ # version needed to extract 2 bytes
80
+ io << if requires_zip64
79
81
  [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_UINT2)
80
82
  else
81
83
  [VERSION_NEEDED_TO_EXTRACT].pack(C_UINT2)
82
84
  end
83
85
 
84
- io << [gp_flags].pack(C_UINT2) # general purpose bit flag 2 bytes
85
- io << [storage_mode].pack(C_UINT2) # compression method 2 bytes
86
- io << [to_binary_dos_time(mtime)].pack(C_UINT2) # last mod file time 2 bytes
87
- io << [to_binary_dos_date(mtime)].pack(C_UINT2) # last mod file date 2 bytes
88
- io << [crc32].pack(C_UINT4) # crc-32 4 bytes
86
+ # general purpose bit flag 2 bytes
87
+ io << [gp_flags].pack(C_UINT2)
88
+ # compression method 2 bytes
89
+ io << [storage_mode].pack(C_UINT2)
90
+ # last mod file time 2 bytes
91
+ io << [to_binary_dos_time(mtime)].pack(C_UINT2)
92
+ # last mod file date 2 bytes
93
+ io << [to_binary_dos_date(mtime)].pack(C_UINT2)
94
+ # crc-32 4 bytes
95
+ io << [crc32].pack(C_UINT4)
89
96
 
90
97
  if requires_zip64
91
- io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # compressed size 4 bytes
92
- io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # uncompressed size 4 bytes
98
+ # compressed size 4 bytes
99
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4)
100
+ # uncompressed size 4 bytes
101
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4)
93
102
  else
94
- io << [compressed_size].pack(C_UINT4) # compressed size 4 bytes
95
- io << [uncompressed_size].pack(C_UINT4) # uncompressed size 4 bytes
103
+ # compressed size 4 bytes
104
+ io << [compressed_size].pack(C_UINT4)
105
+ # uncompressed size 4 bytes
106
+ io << [uncompressed_size].pack(C_UINT4)
96
107
  end
97
108
 
98
109
  # Filename should not be longer than 0xFFFF otherwise this wont fit here
99
- io << [filename.bytesize].pack(C_UINT2) # file name length 2 bytes
110
+ # file name length 2 bytes
111
+ io << [filename.bytesize].pack(C_UINT2)
100
112
 
101
113
  extra_fields = StringIO.new
102
114
 
@@ -109,9 +121,13 @@ class ZipKit::ZipWriter
109
121
  end
110
122
  extra_fields << timestamp_extra_for_local_file_header(mtime)
111
123
 
112
- io << [extra_fields.size].pack(C_UINT2) # extra field length 2 bytes
124
+ # extra field length 2 bytes
125
+ io << [extra_fields.size].pack(C_UINT2)
113
126
 
114
- io << filename # file name (variable size)
127
+ # file name (variable size)
128
+ io << filename
129
+
130
+ # Contents of the extra fields (variable size)
115
131
  io << extra_fields.string
116
132
  end
117
133
 
@@ -125,7 +141,7 @@ class ZipKit::ZipWriter
125
141
  # @param crc32[Fixnum] The CRC32 checksum of the file
126
142
  # @param mtime[Time] the modification time to be recorded in the ZIP
127
143
  # @param gp_flags[Fixnum] bit-packed general purpose flags
128
- # @param unix_permissions[Fixnum?] the permissions for the file, or nil for the default to be used
144
+ # @param unix_permissions[Integer] the permissions for the file, or nil for the default to be used
129
145
  # @return [void]
130
146
  def write_central_directory_file_header(io:,
131
147
  local_file_header_location:,
@@ -142,30 +158,42 @@ class ZipKit::ZipWriter
142
158
  add_zip64 = (local_file_header_location > FOUR_BYTE_MAX_UINT) ||
143
159
  (compressed_size > FOUR_BYTE_MAX_UINT) || (uncompressed_size > FOUR_BYTE_MAX_UINT)
144
160
 
145
- io << [0x02014b50].pack(C_UINT4) # central file header signature 4 bytes (0x02014b50)
146
- io << MADE_BY_SIGNATURE # version made by 2 bytes
161
+ # central file header signature 4 bytes (0x02014b50)
162
+ io << [0x02014b50].pack(C_UINT4)
163
+ # version made by 2 bytes
164
+ io << MADE_BY_SIGNATURE
165
+
166
+ # version needed to extract 2 bytes
147
167
  io << if add_zip64
148
- [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_UINT2) # version needed to extract 2 bytes
168
+ [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_UINT2)
149
169
  else
150
- [VERSION_NEEDED_TO_EXTRACT].pack(C_UINT2) # version needed to extract 2 bytes
170
+ [VERSION_NEEDED_TO_EXTRACT].pack(C_UINT2)
151
171
  end
152
172
 
153
- io << [gp_flags].pack(C_UINT2) # general purpose bit flag 2 bytes
154
- io << [storage_mode].pack(C_UINT2) # compression method 2 bytes
155
- io << [to_binary_dos_time(mtime)].pack(C_UINT2) # last mod file time 2 bytes
156
- io << [to_binary_dos_date(mtime)].pack(C_UINT2) # last mod file date 2 bytes
157
- io << [crc32].pack(C_UINT4) # crc-32 4 bytes
158
-
173
+ # general purpose bit flag 2 bytes
174
+ io << [gp_flags].pack(C_UINT2)
175
+ # compression method 2 bytes
176
+ io << [storage_mode].pack(C_UINT2)
177
+ # last mod file time 2 bytes
178
+ io << [to_binary_dos_time(mtime)].pack(C_UINT2)
179
+ # last mod file date 2 bytes
180
+ io << [to_binary_dos_date(mtime)].pack(C_UINT2)
181
+ # crc-32 4 bytes
182
+ io << [crc32].pack(C_UINT4)
183
+
184
+ # compressed size 4 bytes
185
+ # uncompressed size 4 bytes
159
186
  if add_zip64
160
- io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # compressed size 4 bytes
161
- io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # uncompressed size 4 bytes
187
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4)
188
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4)
162
189
  else
163
- io << [compressed_size].pack(C_UINT4) # compressed size 4 bytes
164
- io << [uncompressed_size].pack(C_UINT4) # uncompressed size 4 bytes
190
+ io << [compressed_size].pack(C_UINT4)
191
+ io << [uncompressed_size].pack(C_UINT4)
165
192
  end
166
193
 
167
194
  # Filename should not be longer than 0xFFFF otherwise this wont fit here
168
- io << [filename.bytesize].pack(C_UINT2) # file name length 2 bytes
195
+ # file name length 2 bytes
196
+ io << [filename.bytesize].pack(C_UINT2)
169
197
 
170
198
  extra_fields = StringIO.new
171
199
  if add_zip64
@@ -175,23 +203,25 @@ class ZipKit::ZipWriter
175
203
  end
176
204
  extra_fields << timestamp_extra_for_central_directory_entry(mtime)
177
205
 
178
- io << [extra_fields.size].pack(C_UINT2) # extra field length 2 bytes
179
-
180
- io << [0].pack(C_UINT2) # file comment length 2 bytes
206
+ # extra field length 2 bytes
207
+ io << [extra_fields.size].pack(C_UINT2)
208
+ # file comment length 2 bytes
209
+ io << [0].pack(C_UINT2)
181
210
 
182
211
  # For The Unarchiver < 3.11.1 this field has to be set to the overflow value if zip64 is used
183
212
  # because otherwise it does not properly advance the pointer when reading the Zip64 extra field
184
213
  # https://bitbucket.org/WAHa_06x36/theunarchiver/pull-requests/2/bug-fix-for-zip64-extra-field-parser/diff
185
- io << if add_zip64 # disk number start 2 bytes
214
+ # disk number start 2 bytes
215
+ io << if add_zip64
186
216
  [TWO_BYTE_MAX_UINT].pack(C_UINT2)
187
217
  else
188
218
  [0].pack(C_UINT2)
189
219
  end
190
- io << [0].pack(C_UINT2) # internal file attributes 2 bytes
220
+ # internal file attributes 2 bytes
221
+ io << [0].pack(C_UINT2)
191
222
 
192
223
  # Because the add_empty_directory method will create a directory with a trailing "/",
193
224
  # this check can be used to assign proper permissions to the created directory.
194
- # external file attributes 4 bytes
195
225
  external_attrs = if filename.end_with?("/")
196
226
  unix_permissions ||= DEFAULT_DIRECTORY_UNIX_PERMISSIONS
197
227
  generate_external_attrs(unix_permissions, FILE_TYPE_DIRECTORY)
@@ -199,17 +229,23 @@ class ZipKit::ZipWriter
199
229
  unix_permissions ||= DEFAULT_FILE_UNIX_PERMISSIONS
200
230
  generate_external_attrs(unix_permissions, FILE_TYPE_FILE)
201
231
  end
232
+
233
+ # external file attributes 4 bytes
202
234
  io << [external_attrs].pack(C_UINT4)
203
235
 
204
- io << if add_zip64 # relative offset of local header 4 bytes
236
+ # relative offset of local header 4 bytes
237
+ io << if add_zip64
205
238
  [FOUR_BYTE_MAX_UINT].pack(C_UINT4)
206
239
  else
207
240
  [local_file_header_location].pack(C_UINT4)
208
241
  end
209
242
 
210
- io << filename # file name (variable size)
211
- io << extra_fields.string # extra field (variable size)
212
- # (empty) # file comment (variable size)
243
+ # file name (variable size)
244
+ io << filename
245
+ # extra field (variable size)
246
+ io << extra_fields.string
247
+ # file comment (variable size)
248
+ # (empty)
213
249
  end
214
250
 
215
251
  # Writes the data descriptor following the file data for a file whose local file header
@@ -222,19 +258,25 @@ class ZipKit::ZipWriter
222
258
  # @param uncompressed_size[Fixnum] The size of the file once extracted
223
259
  # @return [void]
224
260
  def write_data_descriptor(io:, compressed_size:, uncompressed_size:, crc32:)
225
- io << [0x08074b50].pack(C_UINT4) # Although not originally assigned a signature, the value
261
+ # Although not originally assigned a signature, the value
226
262
  # 0x08074b50 has commonly been adopted as a signature value
227
263
  # for the data descriptor record.
228
- io << [crc32].pack(C_UINT4) # crc-32 4 bytes
264
+ io << [0x08074b50].pack(C_UINT4)
265
+
266
+ # crc-32 4 bytes
267
+ io << [crc32].pack(C_UINT4)
229
268
 
230
269
  # If one of the sizes is above 0xFFFFFFF use ZIP64 lengths (8 bytes) instead. A good unarchiver
231
270
  # will decide to unpack it as such if it finds the Zip64 extra for the file in the central directory.
232
- # So also use the opportune moment to switch the entry to Zip64 if needed
271
+ # So also use the opportune moment to switch the entry to Zip64 if needed.
272
+ # We switch if either of the sizes requires ZIP64, so that both values are encoded similarly.
233
273
  requires_zip64 = compressed_size > FOUR_BYTE_MAX_UINT || uncompressed_size > FOUR_BYTE_MAX_UINT
234
274
  pack_spec = requires_zip64 ? C_UINT8 : C_UINT4
235
275
 
236
- io << [compressed_size].pack(pack_spec) # compressed size 4 bytes, or 8 bytes for ZIP64
237
- io << [uncompressed_size].pack(pack_spec) # uncompressed size 4 bytes, or 8 bytes for ZIP64
276
+ # compressed size 4 bytes, or 8 bytes for ZIP64
277
+ io << [compressed_size].pack(pack_spec)
278
+ # uncompressed size 4 bytes, or 8 bytes for ZIP64
279
+ io << [uncompressed_size].pack(pack_spec)
238
280
  end
239
281
 
240
282
  # Writes the "end of central directory record" (including the Zip6 salient bits if necessary)
@@ -243,9 +285,9 @@ class ZipKit::ZipWriter
243
285
  # @param start_of_central_directory_location[Fixnum] byte offset of the start of central directory form the beginning of ZIP file
244
286
  # @param central_directory_size[Fixnum] the size of the central directory (only file headers) in bytes
245
287
  # @param num_files_in_archive[Fixnum] How many files the archive contains
246
- # @param comment[String] the comment for the archive (defaults to ZIP_TRICKS_COMMENT)
288
+ # @param comment[String] the comment for the archive (defaults to ZIP_KIT_COMMENT)
247
289
  # @return [void]
248
- def write_end_of_central_directory(io:, start_of_central_directory_location:, central_directory_size:, num_files_in_archive:, comment: ZIP_TRICKS_COMMENT)
290
+ def write_end_of_central_directory(io:, start_of_central_directory_location:, central_directory_size:, num_files_in_archive:, comment: ZIP_KIT_COMMENT)
249
291
  zip64_eocdr_offset = start_of_central_directory_location + central_directory_size
250
292
 
251
293
  zip64_required = central_directory_size > FOUR_BYTE_MAX_UINT ||
@@ -256,71 +298,88 @@ class ZipKit::ZipWriter
256
298
  # Then, if zip64 is used
257
299
  if zip64_required
258
300
  # [zip64 end of central directory record]
259
- # zip64 end of central dir
260
- io << [0x06064b50].pack(C_UINT4) # signature 4 bytes (0x06064b50)
261
- io << [44].pack(C_UINT8) # size of zip64 end of central
301
+ # zip64 end of central dir signature 4 bytes (0x06064b50)
302
+ io << [0x06064b50].pack(C_UINT4)
303
+
304
+ # size of zip64 end of central
262
305
  # directory record 8 bytes
263
306
  # (this is ex. the 12 bytes of the signature and the size value itself).
264
307
  # Without the extensible data sector (which we are not using)
265
308
  # it is always 44 bytes.
266
- io << MADE_BY_SIGNATURE # version made by 2 bytes
267
- io << [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_UINT2) # version needed to extract 2 bytes
268
- io << [0].pack(C_UINT4) # number of this disk 4 bytes
269
- io << [0].pack(C_UINT4) # number of the disk with the
270
- # start of the central directory 4 bytes
271
- io << [num_files_in_archive].pack(C_UINT8) # total number of entries in the
309
+ io << [44].pack(C_UINT8)
310
+
311
+ # version made by 2 bytes
312
+ io << MADE_BY_SIGNATURE
313
+ # version needed to extract 2 bytes
314
+ io << [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_UINT2)
315
+ # number of this disk 4 bytes
316
+ io << [0].pack(C_UINT4)
317
+ # number of the disk with the start of the central directory 4 bytes
318
+ io << [0].pack(C_UINT4)
319
+ # total number of entries in the
272
320
  # central directory on this disk 8 bytes
273
- io << [num_files_in_archive].pack(C_UINT8) # total number of entries in the
321
+ io << [num_files_in_archive].pack(C_UINT8)
322
+ # total number of entries in the
274
323
  # central directory 8 bytes
275
- io << [central_directory_size].pack(C_UINT8) # size of the central directory 8 bytes
276
- # offset of start of central
277
- # directory with respect to
278
- io << [start_of_central_directory_location].pack(C_UINT8) # the starting disk number 8 bytes
279
- # zip64 extensible data sector (variable size), blank for us
280
-
281
- # [zip64 end of central directory locator]
282
- io << [0x07064b50].pack(C_UINT4) # zip64 end of central dir locator
324
+ io << [num_files_in_archive].pack(C_UINT8)
325
+ # size of the central directory 8 bytes
326
+ io << [central_directory_size].pack(C_UINT8)
327
+ # offset of start of central directory with respect to
328
+ # the starting disk number 8 bytes
329
+ io << [start_of_central_directory_location].pack(C_UINT8)
330
+ # zip64 extensible data sector (variable size)
331
+ # (blank for us)
332
+
333
+ # zip64 end of central dir locator
283
334
  # signature 4 bytes (0x07064b50)
284
- io << [0].pack(C_UINT4) # number of the disk with the
285
- # start of the zip64 end of
335
+ io << [0x07064b50].pack(C_UINT4)
336
+ # number of the disk with the start of the zip64 end of
286
337
  # central directory 4 bytes
287
- io << [zip64_eocdr_offset].pack(C_UINT8) # relative offset of the zip64
338
+ io << [0].pack(C_UINT4)
339
+ # relative offset of the zip64
288
340
  # end of central directory record 8 bytes
289
341
  # (note: "relative" is actually "from the start of the file")
290
- io << [1].pack(C_UINT4) # total number of disks 4 bytes
342
+ io << [zip64_eocdr_offset].pack(C_UINT8)
343
+ # total number of disks 4 bytes
344
+ io << [1].pack(C_UINT4)
291
345
  end
292
346
 
293
347
  # Then the end of central directory record:
294
- io << [0x06054b50].pack(C_UINT4) # end of central dir signature 4 bytes (0x06054b50)
295
- io << [0].pack(C_UINT2) # number of this disk 2 bytes
296
- io << [0].pack(C_UINT2) # number of the disk with the
348
+ # end of central dir signature 4 bytes (0x06054b50)
349
+ io << [0x06054b50].pack(C_UINT4)
350
+ # number of this disk 2 bytes
351
+ io << [0].pack(C_UINT2)
352
+ # number of the disk with the
297
353
  # start of the central directory 2 bytes
354
+ io << [0].pack(C_UINT2)
298
355
 
299
- if zip64_required # the number of entries will be read from the zip64 part of the central directory
300
- io << [TWO_BYTE_MAX_UINT].pack(C_UINT2) # total number of entries in the
301
- # central directory on this disk 2 bytes
302
- io << [TWO_BYTE_MAX_UINT].pack(C_UINT2) # total number of entries in
356
+ # total number of entries in the
357
+ # central directory on this disk 2 bytes
358
+ # total number of entries in
303
359
  # the central directory 2 bytes
360
+ if zip64_required # the number of entries will be read from the zip64 part of the central directory
361
+ io << [TWO_BYTE_MAX_UINT].pack(C_UINT2)
362
+ io << [TWO_BYTE_MAX_UINT].pack(C_UINT2)
304
363
  else
305
- io << [num_files_in_archive].pack(C_UINT2) # total number of entries in the
306
- # central directory on this disk 2 bytes
307
- io << [num_files_in_archive].pack(C_UINT2) # total number of entries in
308
- # the central directory 2 bytes
364
+ io << [num_files_in_archive].pack(C_UINT2)
365
+ io << [num_files_in_archive].pack(C_UINT2)
309
366
  end
310
367
 
311
- if zip64_required
312
- io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # size of the central directory 4 bytes
313
- io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4) # offset of start of central
368
+ # size of the central directory 4 bytes
369
+ # offset of start of central
314
370
  # directory with respect to
315
371
  # the starting disk number 4 bytes
372
+ if zip64_required
373
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4)
374
+ io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4)
316
375
  else
317
- io << [central_directory_size].pack(C_UINT4) # size of the central directory 4 bytes
318
- io << [start_of_central_directory_location].pack(C_UINT4) # offset of start of central
319
- # directory with respect to
320
- # the starting disk number 4 bytes
376
+ io << [central_directory_size].pack(C_UINT4)
377
+ io << [start_of_central_directory_location].pack(C_UINT4)
321
378
  end
322
- io << [comment.bytesize].pack(C_UINT2) # .ZIP file comment length 2 bytes
323
- io << comment # .ZIP file comment (variable size)
379
+ # .ZIP file comment length 2 bytes
380
+ io << [comment.bytesize].pack(C_UINT2)
381
+ # .ZIP file comment (variable size)
382
+ io << comment
324
383
  end
325
384
 
326
385
  private
@@ -332,10 +391,14 @@ class ZipKit::ZipWriter
332
391
  # @return [String]
333
392
  def zip_64_extra_for_local_file_header(compressed_size:, uncompressed_size:)
334
393
  data_and_packspecs = [
335
- 0x0001, C_UINT2, # 2 bytes Tag for this "extra" block type
336
- 16, C_UINT2, # 2 bytes Size of this "extra" block. For us it will always be 16 (2x8)
337
- uncompressed_size, C_UINT8, # 8 bytes Original uncompressed file size
338
- compressed_size, C_UINT8 # 8 bytes Size of compressed data
394
+ # 2 bytes Tag for this "extra" block type
395
+ 0x0001, C_UINT2,
396
+ # 2 bytes Size of this "extra" block. For us it will always be 16 (2x8)
397
+ 16, C_UINT2,
398
+ # 8 bytes Original uncompressed file size
399
+ uncompressed_size, C_UINT8,
400
+ # 8 bytes Size of compressed data
401
+ compressed_size, C_UINT8
339
402
  ]
340
403
  pack_array(data_and_packspecs)
341
404
  end
@@ -377,10 +440,14 @@ class ZipKit::ZipWriter
377
440
  # bits 3-7 reserved for additional timestamps; not set
378
441
  flags = 0b00000001 # Set the lowest bit only, to indicate that only mtime is present
379
442
  data_and_packspecs = [
380
- 0x5455, C_UINT2, # tag for this extra block type ("UT")
381
- (1 + 4), C_UINT2, # the size of this block (1 byte used for the Flag + 3 longs used for the timestamp)
382
- flags, C_CHAR, # encode a single byte
383
- mtime.utc.to_i, C_INT4 # Use a signed int, not the unsigned one used by the rest of the ZIP spec.
443
+ # tag for this extra block type ("UT")
444
+ 0x5455, C_UINT2,
445
+ # the size of this block (1 byte used for the Flag + 3 longs used for the timestamp)
446
+ (1 + 4), C_UINT2,
447
+ # encode a single byte
448
+ flags, C_CHAR,
449
+ # Use a signed int, not the unsigned one used by the rest of the ZIP spec.
450
+ mtime.utc.to_i, C_INT4
384
451
  ]
385
452
  # The atime and ctime can be omitted if not present
386
453
  pack_array(data_and_packspecs)
@@ -399,12 +466,18 @@ class ZipKit::ZipWriter
399
466
  # @return [String]
400
467
  def zip_64_extra_for_central_directory_file_header(compressed_size:, uncompressed_size:, local_file_header_location:)
401
468
  data_and_packspecs = [
402
- 0x0001, C_UINT2, # 2 bytes Tag for this "extra" block type
403
- 28, C_UINT2, # 2 bytes Size of this "extra" block. For us it will always be 28
404
- uncompressed_size, C_UINT8, # 8 bytes Original uncompressed file size
405
- compressed_size, C_UINT8, # 8 bytes Size of compressed data
406
- local_file_header_location, C_UINT8, # 8 bytes Offset of local header record
407
- 0, C_UINT4 # 4 bytes Number of the disk on which this file starts
469
+ # 2 bytes Tag for this "extra" block type
470
+ 0x0001, C_UINT2,
471
+ # 2 bytes Size of this "extra" block. For us it will always be 28
472
+ 28, C_UINT2,
473
+ # 8 bytes Original uncompressed file size
474
+ uncompressed_size, C_UINT8,
475
+ # 8 bytes Size of compressed data
476
+ compressed_size, C_UINT8,
477
+ # 8 bytes Offset of local header record
478
+ local_file_header_location, C_UINT8,
479
+ # 4 bytes Number of the disk on which this file starts
480
+ 0, C_UINT4
408
481
  ]
409
482
  pack_array(data_and_packspecs)
410
483
  end
@@ -424,7 +497,10 @@ class ZipKit::ZipWriter
424
497
  #
425
498
  # will do the following two transforms:
426
499
  #
427
- # [1, 'V', 2, 'v', 148, 'v] -> [1,2,148], ['V','v','v'] -> [1,2,148].pack('Vvv') -> "\x01\x00\x00\x00\x02\x00\x94\x00"
500
+ # [1, 'V', 2, 'v', 148, 'v] -> [1,2,148], ['V','v','v'] -> [1,2,148].pack('Vvv') -> "\x01\x00\x00\x00\x02\x00\x94\x00".
501
+ # This might seem like a "clever optimisation" but the issue is that `pack` needs an array allocated per call, and
502
+ # we output very verbosely - value-by-value. This might be quite a few array allocs. Using something like this
503
+ # helps us save the array allocs
428
504
  def pack_array(values_to_packspecs)
429
505
  values, packspecs = values_to_packspecs.partition.each_with_index { |_, i| i.even? }
430
506
  values.pack(packspecs.join)