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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +11 -11
- data/Rakefile +10 -2
- data/lib/zip_kit/file_reader.rb +1 -1
- data/lib/zip_kit/output_enumerator.rb +19 -37
- data/lib/zip_kit/rack_tempfile_body.rb +3 -1
- data/lib/zip_kit/rails_streaming.rb +13 -6
- data/lib/zip_kit/remote_io.rb +2 -0
- data/lib/zip_kit/size_estimator.rb +3 -1
- data/lib/zip_kit/streamer/heuristic.rb +3 -8
- data/lib/zip_kit/streamer.rb +12 -15
- data/lib/zip_kit/version.rb +1 -1
- data/lib/zip_kit/zip_writer.rb +182 -106
- data/rbi/zip_kit.rbi +2171 -0
- data/zip_kit.gemspec +1 -0
- metadata +18 -4
- data/.codeclimate.yml +0 -7
data/lib/zip_kit/zip_writer.rb
CHANGED
@@ -27,7 +27,7 @@
|
|
27
27
|
class ZipKit::ZipWriter
|
28
28
|
FOUR_BYTE_MAX_UINT = 0xFFFFFFFF
|
29
29
|
TWO_BYTE_MAX_UINT = 0xFFFF
|
30
|
-
|
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
|
-
:
|
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
|
-
|
78
|
-
io <<
|
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
|
-
|
85
|
-
io << [
|
86
|
-
|
87
|
-
io << [
|
88
|
-
|
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
|
-
|
92
|
-
io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4)
|
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
|
-
|
95
|
-
io << [
|
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
|
-
|
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
|
-
|
124
|
+
# extra field length 2 bytes
|
125
|
+
io << [extra_fields.size].pack(C_UINT2)
|
113
126
|
|
114
|
-
|
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[
|
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
|
-
|
146
|
-
io <<
|
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)
|
168
|
+
[VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_UINT2)
|
149
169
|
else
|
150
|
-
[VERSION_NEEDED_TO_EXTRACT].pack(C_UINT2)
|
170
|
+
[VERSION_NEEDED_TO_EXTRACT].pack(C_UINT2)
|
151
171
|
end
|
152
172
|
|
153
|
-
|
154
|
-
io << [
|
155
|
-
|
156
|
-
io << [
|
157
|
-
|
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)
|
161
|
-
io << [FOUR_BYTE_MAX_UINT].pack(C_UINT4)
|
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)
|
164
|
-
io << [uncompressed_size].pack(C_UINT4)
|
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
|
-
|
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
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
211
|
-
io <<
|
212
|
-
#
|
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
|
-
|
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 << [
|
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
|
-
|
237
|
-
io << [
|
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
|
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:
|
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)
|
261
|
-
|
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 <<
|
267
|
-
|
268
|
-
|
269
|
-
io <<
|
270
|
-
#
|
271
|
-
io << [
|
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)
|
321
|
+
io << [num_files_in_archive].pack(C_UINT8)
|
322
|
+
# total number of entries in the
|
274
323
|
# central directory 8 bytes
|
275
|
-
io << [
|
276
|
-
#
|
277
|
-
|
278
|
-
|
279
|
-
#
|
280
|
-
|
281
|
-
#
|
282
|
-
|
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 << [
|
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 << [
|
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 << [
|
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
|
-
|
295
|
-
io << [
|
296
|
-
|
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
|
-
|
300
|
-
|
301
|
-
|
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)
|
306
|
-
|
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
|
-
|
312
|
-
|
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)
|
318
|
-
io << [start_of_central_directory_location].pack(C_UINT4)
|
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
|
-
|
323
|
-
io << comment
|
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
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
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
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
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
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
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)
|