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.
data/rbi/zip_kit.rbi ADDED
@@ -0,0 +1,2171 @@
1
+ # typed: strong
2
+ module ZipKit
3
+ VERSION = T.let("6.2.0", T.untyped)
4
+
5
+ # A ZIP archive contains a flat list of entries. These entries can implicitly
6
+ # create directories when the archive is expanded. For example, an entry with
7
+ # the filename of "some folder/file.docx" will make the unarchiving application
8
+ # create a directory called "some folder" automatically, and then deposit the
9
+ # file "file.docx" in that directory. These "implicit" directories can be
10
+ # arbitrarily nested, and create a tree structure of directories. That structure
11
+ # however is implicit as the archive contains a flat list.
12
+ #
13
+ # This creates opportunities for conflicts. For example, imagine the following
14
+ # structure:
15
+ #
16
+ # * `something/` - specifies an empty directory with the name "something"
17
+ # * `something` - specifies a file, creates a conflict
18
+ #
19
+ # This can be prevented with filename uniqueness checks. It does get funkier however
20
+ # as the rabbit hole goes down:
21
+ #
22
+ # * `dir/subdir/another_subdir/yet_another_subdir/file.bin` - declares a file and directories
23
+ # * `dir/subdir/another_subdir/yet_another_subdir` - declares a file at one of the levels, creates a conflict
24
+ #
25
+ # The results of this ZIP structure aren't very easy to predict as they depend on the
26
+ # application that opens the archive. For example, BOMArchiveHelper on macOS will expand files
27
+ # as they are declared in the ZIP, but once a conflict occurs it will fail with "error -21". It
28
+ # is not very transparent to the user why unarchiving fails, and it has to - and can reliably - only
29
+ # be prevented when the archive gets created.
30
+ #
31
+ # Unfortunately that conflicts with another "magical" feature of ZipKit which automatically
32
+ # "fixes" duplicate filenames - filenames (paths) which have already been added to the archive.
33
+ # This fix is performed by appending (1), then (2) and so forth to the filename so that the
34
+ # conflict is avoided. This is not possible to apply to directories, because when one of the
35
+ # path components is reused in multiple filenames it means those entities should end up in
36
+ # the same directory (subdirectory) once the archive is opened.
37
+ #
38
+ # The `PathSet` keeps track of entries as they get added using 2 Sets (cheap presence checks),
39
+ # one for directories and one for files. It will raise a `Conflict` exception if there are
40
+ # files clobbering one another, or in case files collide with directories.
41
+ class PathSet
42
+ sig { void }
43
+ def initialize; end
44
+
45
+ # Adds a directory path to the set of known paths, including
46
+ # all the directories that contain it. So, calling
47
+ # add_directory_path("dir/dir2/dir3")
48
+ # will add "dir", "dir/dir2", "dir/dir2/dir3".
49
+ #
50
+ # _@param_ `path` — the path to the directory to add
51
+ sig { params(path: String).void }
52
+ def add_directory_path(path); end
53
+
54
+ # Adds a file path to the set of known paths, including
55
+ # all the directories that contain it. Once a file has been added,
56
+ # it is no longer possible to add a directory having the same path
57
+ # as this would cause conflict.
58
+ #
59
+ # The operation also adds all the containing directories for the file, so
60
+ # add_file_path("dir/dir2/file.doc")
61
+ # will add "dir" and "dir/dir2" as directories, "dir/dir2/dir3".
62
+ #
63
+ # _@param_ `file_path` — the path to the directory to add
64
+ sig { params(file_path: String).void }
65
+ def add_file_path(file_path); end
66
+
67
+ # Tells whether a specific full path is already known to the PathSet.
68
+ # Can be a path for a directory or for a file.
69
+ #
70
+ # _@param_ `path_in_archive` — the path to check for inclusion
71
+ sig { params(path_in_archive: String).returns(T::Boolean) }
72
+ def include?(path_in_archive); end
73
+
74
+ # Clears the contained sets
75
+ sig { void }
76
+ def clear; end
77
+
78
+ # sord omit - no YARD type given for "path_in_archive", using untyped
79
+ # Adds the directory or file path to the path set
80
+ sig { params(path_in_archive: T.untyped).void }
81
+ def add_directory_or_file_path(path_in_archive); end
82
+
83
+ # sord omit - no YARD type given for "path", using untyped
84
+ # sord omit - no YARD return type given, using untyped
85
+ sig { params(path: T.untyped).returns(T.untyped) }
86
+ def non_empty_path_components(path); end
87
+
88
+ # sord omit - no YARD type given for "path", using untyped
89
+ # sord omit - no YARD return type given, using untyped
90
+ sig { params(path: T.untyped).returns(T.untyped) }
91
+ def path_and_ancestors(path); end
92
+
93
+ class Conflict < StandardError
94
+ end
95
+
96
+ class FileClobbersDirectory < ZipKit::PathSet::Conflict
97
+ end
98
+
99
+ class DirectoryClobbersFile < ZipKit::PathSet::Conflict
100
+ end
101
+ end
102
+
103
+ # Is used to write streamed ZIP archives into the provided IO-ish object.
104
+ # The output IO is never going to be rewound or seeked, so the output
105
+ # of this object can be coupled directly to, say, a Rack output. The
106
+ # output can also be a String, Array or anything that responds to `<<`.
107
+ #
108
+ # Allows for splicing raw files (for "stored" entries without compression)
109
+ # and splicing of deflated files (for "deflated" storage mode).
110
+ #
111
+ # For stored entries, you need to know the CRC32 (as a uint) and the filesize upfront,
112
+ # before the writing of the entry body starts.
113
+ #
114
+ # Any object that responds to `<<` can be used as the Streamer target - you can use
115
+ # a String, an Array, a Socket or a File, at your leisure.
116
+ #
117
+ # ## Using the Streamer with runtime compression
118
+ #
119
+ # You can use the Streamer with data descriptors (the CRC32 and the sizes will be
120
+ # written after the file data). This allows non-rewinding on-the-fly compression.
121
+ # The streamer will pick the optimum compression method ("stored" or "deflated")
122
+ # depending on the nature of the byte stream you send into it (by using a small buffer).
123
+ # If you are compressing large files, the Deflater object that the Streamer controls
124
+ # will be regularly flushed to prevent memory inflation.
125
+ #
126
+ # ZipKit::Streamer.open(file_socket_or_string) do |zip|
127
+ # zip.write_file('mov.mp4') do |sink|
128
+ # File.open('mov.mp4', 'rb'){|source| IO.copy_stream(source, sink) }
129
+ # end
130
+ # zip.write_file('long-novel.txt') do |sink|
131
+ # File.open('novel.txt', 'rb'){|source| IO.copy_stream(source, sink) }
132
+ # end
133
+ # end
134
+ #
135
+ # The central directory will be written automatically at the end of the block.
136
+ #
137
+ # ## Using the Streamer with entries of known size and having a known CRC32 checksum
138
+ #
139
+ # Streamer allows "IO splicing" - in this mode it will only control the metadata output,
140
+ # but you can write the data to the socket/file outside of the Streamer. For example, when
141
+ # using the sendfile gem:
142
+ #
143
+ # ZipKit::Streamer.open(socket) do | zip |
144
+ # zip.add_stored_entry(filename: "myfile1.bin", size: 9090821, crc32: 12485)
145
+ # socket.sendfile(tempfile1)
146
+ # zip.simulate_write(tempfile1.size)
147
+ #
148
+ # zip.add_stored_entry(filename: "myfile2.bin", size: 458678, crc32: 89568)
149
+ # socket.sendfile(tempfile2)
150
+ # zip.simulate_write(tempfile2.size)
151
+ # end
152
+ #
153
+ # Note that you need to use `simulate_write` in this case. This needs to happen since Streamer
154
+ # writes absolute offsets into the ZIP (local file header offsets and the like),
155
+ # and it relies on the output object to tell it how many bytes have been written
156
+ # so far. When using `sendfile` the Ruby write methods get bypassed entirely, and the
157
+ # offsets in the IO will not be updated - which will result in an invalid ZIP.
158
+ #
159
+ #
160
+ # ## On-the-fly deflate -using the Streamer with async/suspended writes and data descriptors
161
+ #
162
+ # If you are unable to use the block versions of `write_deflated_file` and `write_stored_file`
163
+ # there is an option to use a separate writer object. It gets returned from `write_deflated_file`
164
+ # and `write_stored_file` if you do not provide them with a block, and will accept data writes.
165
+ # Do note that you _must_ call `#close` on that object yourself:
166
+ #
167
+ # ZipKit::Streamer.open(socket) do | zip |
168
+ # w = zip.write_stored_file('mov.mp4')
169
+ # IO.copy_stream(source_io, w)
170
+ # w.close
171
+ # end
172
+ #
173
+ # The central directory will be written automatically at the end of the `open` block. If you need
174
+ # to manage the Streamer manually, or defer the central directory write until appropriate, use
175
+ # the constructor instead and call `Streamer#close`:
176
+ #
177
+ # zip = ZipKit::Streamer.new(out_io)
178
+ # .....
179
+ # zip.close
180
+ #
181
+ # Calling {Streamer#close} **will not** call `#close` on the underlying IO object.
182
+ class Streamer
183
+ include ZipKit::WriteShovel
184
+ STORED = T.let(0, T.untyped)
185
+ DEFLATED = T.let(8, T.untyped)
186
+ EntryBodySizeMismatch = T.let(Class.new(StandardError), T.untyped)
187
+ InvalidOutput = T.let(Class.new(ArgumentError), T.untyped)
188
+ Overflow = T.let(Class.new(StandardError), T.untyped)
189
+ UnknownMode = T.let(Class.new(StandardError), T.untyped)
190
+ OffsetOutOfSync = T.let(Class.new(StandardError), T.untyped)
191
+
192
+ # sord omit - no YARD return type given, using untyped
193
+ # Creates a new Streamer on top of the given IO-ish object and yields it. Once the given block
194
+ # returns, the Streamer will have it's `close` method called, which will write out the central
195
+ # directory of the archive to the output.
196
+ #
197
+ # _@param_ `stream` — the destination IO for the ZIP (should respond to `tell` and `<<`)
198
+ #
199
+ # _@param_ `kwargs_for_new` — keyword arguments for #initialize
200
+ sig { params(stream: IO, kwargs_for_new: T::Hash[T.untyped, T.untyped]).returns(T.untyped) }
201
+ def self.open(stream, **kwargs_for_new); end
202
+
203
+ # sord duck - #<< looks like a duck type, replacing with untyped
204
+ # Creates a new Streamer on top of the given IO-ish object.
205
+ #
206
+ # _@param_ `writable` — the destination IO for the ZIP. Anything that responds to `<<` can be used.
207
+ #
208
+ # _@param_ `writer` — the object to be used as the writer. Defaults to an instance of ZipKit::ZipWriter, normally you won't need to override it
209
+ #
210
+ # _@param_ `auto_rename_duplicate_filenames` — whether duplicate filenames, when encountered, should be suffixed with (1), (2) etc. Default value is `false` - if dupliate names are used an exception will be raised
211
+ sig { params(writable: T.untyped, writer: ZipKit::ZipWriter, auto_rename_duplicate_filenames: T::Boolean).void }
212
+ def initialize(writable, writer: create_writer, auto_rename_duplicate_filenames: false); end
213
+
214
+ # Writes a part of a zip entry body (actual binary data of the entry) into the output stream.
215
+ #
216
+ # _@param_ `binary_data` — a String in binary encoding
217
+ #
218
+ # _@return_ — self
219
+ sig { params(binary_data: String).returns(T.untyped) }
220
+ def <<(binary_data); end
221
+
222
+ # Advances the internal IO pointer to keep the offsets of the ZIP file in
223
+ # check. Use this if you are going to use accelerated writes to the socket
224
+ # (like the `sendfile()` call) after writing the headers, or if you
225
+ # just need to figure out the size of the archive.
226
+ #
227
+ # _@param_ `num_bytes` — how many bytes are going to be written bypassing the Streamer
228
+ #
229
+ # _@return_ — position in the output stream / ZIP archive
230
+ sig { params(num_bytes: Integer).returns(Integer) }
231
+ def simulate_write(num_bytes); end
232
+
233
+ # Writes out the local header for an entry (file in the ZIP) that is using
234
+ # the deflated storage model (is compressed). Once this method is called,
235
+ # the `<<` method has to be called to write the actual contents of the body.
236
+ #
237
+ # Note that the deflated body that is going to be written into the output
238
+ # has to be _precompressed_ (pre-deflated) before writing it into the
239
+ # Streamer, because otherwise it is impossible to know it's size upfront.
240
+ #
241
+ # _@param_ `filename` — the name of the file in the entry
242
+ #
243
+ # _@param_ `modification_time` — the modification time of the file in the archive
244
+ #
245
+ # _@param_ `compressed_size` — the size of the compressed entry that is going to be written into the archive
246
+ #
247
+ # _@param_ `uncompressed_size` — the size of the entry when uncompressed, in bytes
248
+ #
249
+ # _@param_ `crc32` — the CRC32 checksum of the entry when uncompressed
250
+ #
251
+ # _@param_ `use_data_descriptor` — whether the entry body will be followed by a data descriptor
252
+ #
253
+ # _@param_ `unix_permissions` — which UNIX permissions to set, normally the default should be used
254
+ #
255
+ # _@return_ — the offset the output IO is at after writing the entry header
256
+ sig do
257
+ params(
258
+ filename: String,
259
+ modification_time: Time,
260
+ compressed_size: Integer,
261
+ uncompressed_size: Integer,
262
+ crc32: Integer,
263
+ unix_permissions: T.nilable(Integer),
264
+ use_data_descriptor: T::Boolean
265
+ ).returns(Integer)
266
+ end
267
+ def add_deflated_entry(filename:, modification_time: Time.now.utc, compressed_size: 0, uncompressed_size: 0, crc32: 0, unix_permissions: nil, use_data_descriptor: false); end
268
+
269
+ # Writes out the local header for an entry (file in the ZIP) that is using
270
+ # the stored storage model (is stored as-is).
271
+ # Once this method is called, the `<<` method has to be called one or more
272
+ # times to write the actual contents of the body.
273
+ #
274
+ # _@param_ `filename` — the name of the file in the entry
275
+ #
276
+ # _@param_ `modification_time` — the modification time of the file in the archive
277
+ #
278
+ # _@param_ `size` — the size of the file when uncompressed, in bytes
279
+ #
280
+ # _@param_ `crc32` — the CRC32 checksum of the entry when uncompressed
281
+ #
282
+ # _@param_ `use_data_descriptor` — whether the entry body will be followed by a data descriptor. When in use
283
+ #
284
+ # _@param_ `unix_permissions` — which UNIX permissions to set, normally the default should be used
285
+ #
286
+ # _@return_ — the offset the output IO is at after writing the entry header
287
+ sig do
288
+ params(
289
+ filename: String,
290
+ modification_time: Time,
291
+ size: Integer,
292
+ crc32: Integer,
293
+ unix_permissions: T.nilable(Integer),
294
+ use_data_descriptor: T::Boolean
295
+ ).returns(Integer)
296
+ end
297
+ def add_stored_entry(filename:, modification_time: Time.now.utc, size: 0, crc32: 0, unix_permissions: nil, use_data_descriptor: false); end
298
+
299
+ # Adds an empty directory to the archive with a size of 0 and permissions of 755.
300
+ #
301
+ # _@param_ `dirname` — the name of the directory in the archive
302
+ #
303
+ # _@param_ `modification_time` — the modification time of the directory in the archive
304
+ #
305
+ # _@param_ `unix_permissions` — which UNIX permissions to set, normally the default should be used
306
+ #
307
+ # _@return_ — the offset the output IO is at after writing the entry header
308
+ sig { params(dirname: String, modification_time: Time, unix_permissions: T.nilable(Integer)).returns(Integer) }
309
+ def add_empty_directory(dirname:, modification_time: Time.now.utc, unix_permissions: nil); end
310
+
311
+ # Opens the stream for a file stored in the archive, and yields a writer
312
+ # for that file to the block.
313
+ # The writer will buffer a small amount of data and see whether compression is
314
+ # effective for the data being output. If compression turns out to work well -
315
+ # for instance, if the output is mostly text - it is going to create a deflated
316
+ # file inside the zip. If the compression benefits are negligible, it will
317
+ # create a stored file inside the zip. It will delegate either to `write_deflated_file`
318
+ # or to `write_stored_file`.
319
+ #
320
+ # Using a block, the write will be terminated with a data descriptor outright.
321
+ #
322
+ # zip.write_file("foo.txt") do |sink|
323
+ # IO.copy_stream(source_file, sink)
324
+ # end
325
+ #
326
+ # If deferred writes are desired (for example - to integrate with an API that
327
+ # does not support blocks, or to work with non-blocking environments) the method
328
+ # has to be called without a block. In that case it returns the sink instead,
329
+ # permitting to write to it in a deferred fashion. When `close` is called on
330
+ # the sink, any remanining compression output will be flushed and the data
331
+ # descriptor is going to be written.
332
+ #
333
+ # Note that even though it does not have to happen within the same call stack,
334
+ # call sequencing still must be observed. It is therefore not possible to do
335
+ # this:
336
+ #
337
+ # writer_for_file1 = zip.write_file("somefile.jpg")
338
+ # writer_for_file2 = zip.write_file("another.tif")
339
+ # writer_for_file1 << data
340
+ # writer_for_file2 << data
341
+ #
342
+ # because it is likely to result in an invalid ZIP file structure later on.
343
+ # So using this facility in async scenarios is certainly possible, but care
344
+ # and attention is recommended.
345
+ #
346
+ # _@param_ `filename` — the name of the file in the archive
347
+ #
348
+ # _@param_ `modification_time` — the modification time of the file in the archive
349
+ #
350
+ # _@param_ `unix_permissions` — which UNIX permissions to set, normally the default should be used
351
+ #
352
+ # _@return_ — without a block - the Writable sink which has to be closed manually
353
+ sig do
354
+ params(
355
+ filename: String,
356
+ modification_time: Time,
357
+ unix_permissions: T.nilable(Integer),
358
+ blk: T.proc.params(sink: ZipKit::Streamer::Writable).void
359
+ ).returns(ZipKit::Streamer::Writable)
360
+ end
361
+ def write_file(filename, modification_time: Time.now.utc, unix_permissions: nil, &blk); end
362
+
363
+ # Opens the stream for a stored file in the archive, and yields a writer
364
+ # for that file to the block.
365
+ # Once the write completes, a data descriptor will be written with the
366
+ # actual compressed/uncompressed sizes and the CRC32 checksum.
367
+ #
368
+ # Using a block, the write will be terminated with a data descriptor outright.
369
+ #
370
+ # zip.write_stored_file("foo.txt") do |sink|
371
+ # IO.copy_stream(source_file, sink)
372
+ # end
373
+ #
374
+ # If deferred writes are desired (for example - to integrate with an API that
375
+ # does not support blocks, or to work with non-blocking environments) the method
376
+ # has to be called without a block. In that case it returns the sink instead,
377
+ # permitting to write to it in a deferred fashion. When `close` is called on
378
+ # the sink, any remanining compression output will be flushed and the data
379
+ # descriptor is going to be written.
380
+ #
381
+ # Note that even though it does not have to happen within the same call stack,
382
+ # call sequencing still must be observed. It is therefore not possible to do
383
+ # this:
384
+ #
385
+ # writer_for_file1 = zip.write_stored_file("somefile.jpg")
386
+ # writer_for_file2 = zip.write_stored_file("another.tif")
387
+ # writer_for_file1 << data
388
+ # writer_for_file2 << data
389
+ #
390
+ # because it is likely to result in an invalid ZIP file structure later on.
391
+ # So using this facility in async scenarios is certainly possible, but care
392
+ # and attention is recommended.
393
+ #
394
+ # If an exception is raised inside the block that is passed to the method, a `rollback!` call
395
+ # will be performed automatically and the entry just written will be omitted from the ZIP
396
+ # central directory. This can be useful if you want to rescue the exception and reattempt
397
+ # adding the ZIP file. Note that you will need to call `write_deflated_file` again to start a
398
+ # new file - you can't keep writing to the one that failed.
399
+ #
400
+ # _@param_ `filename` — the name of the file in the archive
401
+ #
402
+ # _@param_ `modification_time` — the modification time of the file in the archive
403
+ #
404
+ # _@param_ `unix_permissions` — which UNIX permissions to set, normally the default should be used
405
+ #
406
+ # _@return_ — without a block - the Writable sink which has to be closed manually
407
+ sig do
408
+ params(
409
+ filename: String,
410
+ modification_time: Time,
411
+ unix_permissions: T.nilable(Integer),
412
+ blk: T.proc.params(sink: ZipKit::Streamer::Writable).void
413
+ ).returns(ZipKit::Streamer::Writable)
414
+ end
415
+ def write_stored_file(filename, modification_time: Time.now.utc, unix_permissions: nil, &blk); end
416
+
417
+ # Opens the stream for a deflated file in the archive, and yields a writer
418
+ # for that file to the block. Once the write completes, a data descriptor
419
+ # will be written with the actual compressed/uncompressed sizes and the
420
+ # CRC32 checksum.
421
+ #
422
+ # Using a block, the write will be terminated with a data descriptor outright.
423
+ #
424
+ # zip.write_stored_file("foo.txt") do |sink|
425
+ # IO.copy_stream(source_file, sink)
426
+ # end
427
+ #
428
+ # If deferred writes are desired (for example - to integrate with an API that
429
+ # does not support blocks, or to work with non-blocking environments) the method
430
+ # has to be called without a block. In that case it returns the sink instead,
431
+ # permitting to write to it in a deferred fashion. When `close` is called on
432
+ # the sink, any remanining compression output will be flushed and the data
433
+ # descriptor is going to be written.
434
+ #
435
+ # Note that even though it does not have to happen within the same call stack,
436
+ # call sequencing still must be observed. It is therefore not possible to do
437
+ # this:
438
+ #
439
+ # writer_for_file1 = zip.write_deflated_file("somefile.jpg")
440
+ # writer_for_file2 = zip.write_deflated_file("another.tif")
441
+ # writer_for_file1 << data
442
+ # writer_for_file2 << data
443
+ # writer_for_file1.close
444
+ # writer_for_file2.close
445
+ #
446
+ # because it is likely to result in an invalid ZIP file structure later on.
447
+ # So using this facility in async scenarios is certainly possible, but care
448
+ # and attention is recommended.
449
+ #
450
+ # If an exception is raised inside the block that is passed to the method, a `rollback!` call
451
+ # will be performed automatically and the entry just written will be omitted from the ZIP
452
+ # central directory. This can be useful if you want to rescue the exception and reattempt
453
+ # adding the ZIP file. Note that you will need to call `write_deflated_file` again to start a
454
+ # new file - you can't keep writing to the one that failed.
455
+ #
456
+ # _@param_ `filename` — the name of the file in the archive
457
+ #
458
+ # _@param_ `modification_time` — the modification time of the file in the archive
459
+ #
460
+ # _@param_ `unix_permissions` — which UNIX permissions to set, normally the default should be used
461
+ #
462
+ # _@return_ — without a block - the Writable sink which has to be closed manually
463
+ sig do
464
+ params(
465
+ filename: String,
466
+ modification_time: Time,
467
+ unix_permissions: T.nilable(Integer),
468
+ blk: T.proc.params(sink: ZipKit::Streamer::Writable).void
469
+ ).returns(ZipKit::Streamer::Writable)
470
+ end
471
+ def write_deflated_file(filename, modification_time: Time.now.utc, unix_permissions: nil, &blk); end
472
+
473
+ # Closes the archive. Writes the central directory, and switches the writer into
474
+ # a state where it can no longer be written to.
475
+ #
476
+ # Once this method is called, the `Streamer` should be discarded (the ZIP archive is complete).
477
+ #
478
+ # _@return_ — the offset the output IO is at after closing the archive
479
+ sig { returns(Integer) }
480
+ def close; end
481
+
482
+ # Sets up the ZipWriter with wrappers if necessary. The method is called once, when the Streamer
483
+ # gets instantiated - the Writer then gets reused. This method is primarily there so that you
484
+ # can override it.
485
+ #
486
+ # _@return_ — the writer to perform writes with
487
+ sig { returns(ZipKit::ZipWriter) }
488
+ def create_writer; end
489
+
490
+ # Updates the last entry written with the CRC32 checksum and compressed/uncompressed
491
+ # sizes. For stored entries, `compressed_size` and `uncompressed_size` are the same.
492
+ # After updating the entry will immediately write the data descriptor bytes
493
+ # to the output.
494
+ #
495
+ # _@param_ `crc32` — the CRC32 checksum of the entry when uncompressed
496
+ #
497
+ # _@param_ `compressed_size` — the size of the compressed segment within the ZIP
498
+ #
499
+ # _@param_ `uncompressed_size` — the size of the entry once uncompressed
500
+ #
501
+ # _@return_ — the offset the output IO is at after writing the data descriptor
502
+ sig { params(crc32: Integer, compressed_size: Integer, uncompressed_size: Integer).returns(Integer) }
503
+ def update_last_entry_and_write_data_descriptor(crc32:, compressed_size:, uncompressed_size:); end
504
+
505
+ # Removes the buffered local entry for the last file written. This can be used when rescuing from exceptions
506
+ # when you want to skip the file that failed writing into the ZIP from getting written out into the
507
+ # ZIP central directory. This is useful when, for example, you encounter errors retrieving the file
508
+ # that you want to place inside the ZIP from a remote storage location and some network exception
509
+ # gets raised. `write_deflated_file` and `write_stored_file` will rollback for you automatically.
510
+ # Of course it is not possible to remove the failed entry from the ZIP file entirely, as the data
511
+ # is likely already on the wire. However, excluding the entry from the central directory of the ZIP
512
+ # file will allow better-behaved ZIP unarchivers to extract the entries which did store correctly,
513
+ # provided they read the ZIP from the central directory and not straight-ahead.
514
+ #
515
+ # _@return_ — position in the output stream / ZIP archive
516
+ #
517
+ # ```ruby
518
+ # zip.add_stored_entry(filename: "data.bin", size: 4.megabytes, crc32: the_crc)
519
+ # while chunk = remote.read(65*2048)
520
+ # zip << chunk
521
+ # rescue Timeout::Error
522
+ # zip.rollback!
523
+ # # and proceed to the next file
524
+ # end
525
+ # ```
526
+ sig { returns(Integer) }
527
+ def rollback!; end
528
+
529
+ # sord omit - no YARD type given for "writable", using untyped
530
+ # sord omit - no YARD return type given, using untyped
531
+ sig { params(writable: T.untyped, block_to_pass_writable_to: T.untyped).returns(T.untyped) }
532
+ def yield_or_return_writable(writable, &block_to_pass_writable_to); end
533
+
534
+ # sord omit - no YARD return type given, using untyped
535
+ sig { returns(T.untyped) }
536
+ def verify_offsets!; end
537
+
538
+ # sord omit - no YARD type given for "filename:", using untyped
539
+ # sord omit - no YARD type given for "modification_time:", using untyped
540
+ # sord omit - no YARD type given for "crc32:", using untyped
541
+ # sord omit - no YARD type given for "storage_mode:", using untyped
542
+ # sord omit - no YARD type given for "compressed_size:", using untyped
543
+ # sord omit - no YARD type given for "uncompressed_size:", using untyped
544
+ # sord omit - no YARD type given for "use_data_descriptor:", using untyped
545
+ # sord omit - no YARD type given for "unix_permissions:", using untyped
546
+ # sord omit - no YARD return type given, using untyped
547
+ sig do
548
+ params(
549
+ filename: T.untyped,
550
+ modification_time: T.untyped,
551
+ crc32: T.untyped,
552
+ storage_mode: T.untyped,
553
+ compressed_size: T.untyped,
554
+ uncompressed_size: T.untyped,
555
+ use_data_descriptor: T.untyped,
556
+ unix_permissions: T.untyped
557
+ ).returns(T.untyped)
558
+ end
559
+ def add_file_and_write_local_header(filename:, modification_time:, crc32:, storage_mode:, compressed_size:, uncompressed_size:, use_data_descriptor:, unix_permissions:); end
560
+
561
+ # sord omit - no YARD type given for "filename", using untyped
562
+ # sord omit - no YARD return type given, using untyped
563
+ sig { params(filename: T.untyped).returns(T.untyped) }
564
+ def remove_backslash(filename); end
565
+
566
+ # sord infer - argument name in single @param inferred as "bytes"
567
+ # Writes the given data to the output stream. Allows the object to be used as
568
+ # a target for `IO.copy_stream(from, to)`
569
+ #
570
+ # _@param_ `d` — the binary string to write (part of the uncompressed file)
571
+ #
572
+ # _@return_ — the number of bytes written
573
+ sig { params(bytes: String).returns(Fixnum) }
574
+ def write(bytes); end
575
+
576
+ # Is used internally by Streamer to keep track of entries in the archive during writing.
577
+ # Normally you will not have to use this class directly
578
+ class Entry < Struct
579
+ sig { void }
580
+ def initialize; end
581
+
582
+ # sord omit - no YARD return type given, using untyped
583
+ sig { returns(T.untyped) }
584
+ def total_bytes_used; end
585
+
586
+ # sord omit - no YARD return type given, using untyped
587
+ # Set the general purpose flags for the entry. We care about is the EFS
588
+ # bit (bit 11) which should be set if the filename is UTF8. If it is, we need to set the
589
+ # bit so that the unarchiving application knows that the filename in the archive is UTF-8
590
+ # encoded, and not some DOS default. For ASCII entries it does not matter.
591
+ # Additionally, we care about bit 3 which toggles the use of the postfix data descriptor.
592
+ sig { returns(T.untyped) }
593
+ def gp_flags; end
594
+
595
+ sig { returns(T::Boolean) }
596
+ def filler?; end
597
+
598
+ # Returns the value of attribute filename
599
+ sig { returns(Object) }
600
+ attr_accessor :filename
601
+
602
+ # Returns the value of attribute crc32
603
+ sig { returns(Object) }
604
+ attr_accessor :crc32
605
+
606
+ # Returns the value of attribute compressed_size
607
+ sig { returns(Object) }
608
+ attr_accessor :compressed_size
609
+
610
+ # Returns the value of attribute uncompressed_size
611
+ sig { returns(Object) }
612
+ attr_accessor :uncompressed_size
613
+
614
+ # Returns the value of attribute storage_mode
615
+ sig { returns(Object) }
616
+ attr_accessor :storage_mode
617
+
618
+ # Returns the value of attribute mtime
619
+ sig { returns(Object) }
620
+ attr_accessor :mtime
621
+
622
+ # Returns the value of attribute use_data_descriptor
623
+ sig { returns(Object) }
624
+ attr_accessor :use_data_descriptor
625
+
626
+ # Returns the value of attribute local_header_offset
627
+ sig { returns(Object) }
628
+ attr_accessor :local_header_offset
629
+
630
+ # Returns the value of attribute bytes_used_for_local_header
631
+ sig { returns(Object) }
632
+ attr_accessor :bytes_used_for_local_header
633
+
634
+ # Returns the value of attribute bytes_used_for_data_descriptor
635
+ sig { returns(Object) }
636
+ attr_accessor :bytes_used_for_data_descriptor
637
+
638
+ # Returns the value of attribute unix_permissions
639
+ sig { returns(Object) }
640
+ attr_accessor :unix_permissions
641
+ end
642
+
643
+ # Is used internally by Streamer to keep track of entries in the archive during writing.
644
+ # Normally you will not have to use this class directly
645
+ class Filler < Struct
646
+ sig { returns(T::Boolean) }
647
+ def filler?; end
648
+
649
+ # Returns the value of attribute total_bytes_used
650
+ sig { returns(Object) }
651
+ attr_accessor :total_bytes_used
652
+ end
653
+
654
+ # Gets yielded from the writing methods of the Streamer
655
+ # and accepts the data being written into the ZIP for deflate
656
+ # or stored modes. Can be used as a destination for `IO.copy_stream`
657
+ #
658
+ # IO.copy_stream(File.open('source.bin', 'rb), writable)
659
+ class Writable
660
+ include ZipKit::WriteShovel
661
+
662
+ # sord omit - no YARD type given for "streamer", using untyped
663
+ # sord omit - no YARD type given for "writer", using untyped
664
+ # Initializes a new Writable with the object it delegates the writes to.
665
+ # Normally you would not need to use this method directly
666
+ sig { params(streamer: T.untyped, writer: T.untyped).void }
667
+ def initialize(streamer, writer); end
668
+
669
+ # Writes the given data to the output stream
670
+ #
671
+ # _@param_ `d` — the binary string to write (part of the uncompressed file)
672
+ sig { params(d: String).returns(T.self_type) }
673
+ def <<(d); end
674
+
675
+ # sord omit - no YARD return type given, using untyped
676
+ # Flushes the writer and recovers the CRC32/size values. It then calls
677
+ # `update_last_entry_and_write_data_descriptor` on the given Streamer.
678
+ sig { returns(T.untyped) }
679
+ def close; end
680
+
681
+ # sord infer - argument name in single @param inferred as "bytes"
682
+ # Writes the given data to the output stream. Allows the object to be used as
683
+ # a target for `IO.copy_stream(from, to)`
684
+ #
685
+ # _@param_ `d` — the binary string to write (part of the uncompressed file)
686
+ #
687
+ # _@return_ — the number of bytes written
688
+ sig { params(bytes: String).returns(Fixnum) }
689
+ def write(bytes); end
690
+ end
691
+
692
+ # Will be used to pick whether to store a file in the `stored` or
693
+ # `deflated` mode, by compressing the first N bytes of the file and
694
+ # comparing the stored and deflated data sizes. If deflate produces
695
+ # a sizable compression gain for this data, it will create a deflated
696
+ # file inside the ZIP archive. If the file doesn't compress well, it
697
+ # will use the "stored" mode for the entry. About 128KB of the
698
+ # file will be buffered to pick the appropriate storage mode. The
699
+ # Heuristic will call either `write_stored_file` or `write_deflated_file`
700
+ # on the Streamer passed into it once it knows which compression
701
+ # method should be applied
702
+ class Heuristic < ZipKit::Streamer::Writable
703
+ BYTES_WRITTEN_THRESHOLD = T.let(128 * 1024, T.untyped)
704
+ MINIMUM_VIABLE_COMPRESSION = T.let(0.75, T.untyped)
705
+
706
+ # sord omit - no YARD type given for "streamer", using untyped
707
+ # sord omit - no YARD type given for "filename", using untyped
708
+ # sord omit - no YARD type given for "**write_file_options", using untyped
709
+ sig { params(streamer: T.untyped, filename: T.untyped, write_file_options: T.untyped).void }
710
+ def initialize(streamer, filename, **write_file_options); end
711
+
712
+ # sord infer - argument name in single @param inferred as "bytes"
713
+ sig { params(bytes: String).returns(T.self_type) }
714
+ def <<(bytes); end
715
+
716
+ # sord omit - no YARD return type given, using untyped
717
+ sig { returns(T.untyped) }
718
+ def close; end
719
+
720
+ # sord omit - no YARD return type given, using untyped
721
+ sig { returns(T.untyped) }
722
+ def decide; end
723
+ end
724
+
725
+ # Sends writes to the given `io`, and also registers all the data passing
726
+ # through it in a CRC32 checksum calculator. Is made to be completely
727
+ # interchangeable with the DeflatedWriter in terms of interface.
728
+ class StoredWriter
729
+ include ZipKit::WriteShovel
730
+ CRC32_BUFFER_SIZE = T.let(64 * 1024, T.untyped)
731
+
732
+ # sord omit - no YARD type given for "io", using untyped
733
+ sig { params(io: T.untyped).void }
734
+ def initialize(io); end
735
+
736
+ # Writes the given data to the contained IO object.
737
+ #
738
+ # _@param_ `data` — data to be written
739
+ #
740
+ # _@return_ — self
741
+ sig { params(data: String).returns(T.untyped) }
742
+ def <<(data); end
743
+
744
+ # Returns the amount of data written and the CRC32 checksum. The return value
745
+ # can be directly used as the argument to {Streamer#update_last_entry_and_write_data_descriptor}
746
+ #
747
+ # _@return_ — a hash of `{crc32, compressed_size, uncompressed_size}`
748
+ sig { returns(T::Hash[T.untyped, T.untyped]) }
749
+ def finish; end
750
+
751
+ # sord infer - argument name in single @param inferred as "bytes"
752
+ # Writes the given data to the output stream. Allows the object to be used as
753
+ # a target for `IO.copy_stream(from, to)`
754
+ #
755
+ # _@param_ `d` — the binary string to write (part of the uncompressed file)
756
+ #
757
+ # _@return_ — the number of bytes written
758
+ sig { params(bytes: String).returns(Fixnum) }
759
+ def write(bytes); end
760
+ end
761
+
762
+ # Sends writes to the given `io` compressed using a `Zlib::Deflate`. Also
763
+ # registers data passing through it in a CRC32 checksum calculator. Is made to be completely
764
+ # interchangeable with the StoredWriter in terms of interface.
765
+ class DeflatedWriter
766
+ include ZipKit::WriteShovel
767
+ CRC32_BUFFER_SIZE = T.let(64 * 1024, T.untyped)
768
+
769
+ # sord omit - no YARD type given for "io", using untyped
770
+ sig { params(io: T.untyped).void }
771
+ def initialize(io); end
772
+
773
+ # Writes the given data into the deflater, and flushes the deflater
774
+ # after having written more than FLUSH_EVERY_N_BYTES bytes of data
775
+ #
776
+ # _@param_ `data` — data to be written
777
+ #
778
+ # _@return_ — self
779
+ sig { params(data: String).returns(T.untyped) }
780
+ def <<(data); end
781
+
782
+ # Returns the amount of data received for writing, the amount of
783
+ # compressed data written and the CRC32 checksum. The return value
784
+ # can be directly used as the argument to {Streamer#update_last_entry_and_write_data_descriptor}
785
+ #
786
+ # _@return_ — a hash of `{crc32, compressed_size, uncompressed_size}`
787
+ sig { returns(T::Hash[T.untyped, T.untyped]) }
788
+ def finish; end
789
+
790
+ # sord infer - argument name in single @param inferred as "bytes"
791
+ # Writes the given data to the output stream. Allows the object to be used as
792
+ # a target for `IO.copy_stream(from, to)`
793
+ #
794
+ # _@param_ `d` — the binary string to write (part of the uncompressed file)
795
+ #
796
+ # _@return_ — the number of bytes written
797
+ sig { params(bytes: String).returns(Fixnum) }
798
+ def write(bytes); end
799
+ end
800
+ end
801
+
802
+ # An object that fakes just-enough of an IO to be dangerous
803
+ # - or, more precisely, to be useful as a source for the FileReader
804
+ # central directory parser. Effectively we substitute an IO object
805
+ # for an object that fetches parts of the remote file over HTTP using `Range:`
806
+ # headers. The `RemoteIO` acts as an adapter between an object that performs the
807
+ # actual fetches over HTTP and an object that expects a handful of IO methods to be
808
+ # available.
809
+ class RemoteIO
810
+ # sord warn - URI wasn't able to be resolved to a constant in this project
811
+ # _@param_ `url` — the HTTP/HTTPS URL of the object to be retrieved
812
+ sig { params(url: T.any(String, URI)).void }
813
+ def initialize(url); end
814
+
815
+ # sord omit - no YARD return type given, using untyped
816
+ # Emulates IO#seek
817
+ #
818
+ # _@param_ `offset` — absolute offset in the remote resource to seek to
819
+ #
820
+ # _@param_ `mode` — The seek mode (only SEEK_SET is supported)
821
+ sig { params(offset: Integer, mode: Integer).returns(T.untyped) }
822
+ def seek(offset, mode = IO::SEEK_SET); end
823
+
824
+ # Emulates IO#size.
825
+ #
826
+ # _@return_ — the size of the remote resource
827
+ sig { returns(Integer) }
828
+ def size; end
829
+
830
+ # Emulates IO#read, but requires the number of bytes to read
831
+ # The read will be limited to the
832
+ # size of the remote resource relative to the current offset in the IO,
833
+ # so if you are at offset 0 in the IO of size 10, doing a `read(20)`
834
+ # will only return you 10 bytes of result, and not raise any exceptions.
835
+ #
836
+ # _@param_ `n_bytes` — how many bytes to read, or `nil` to read all the way to the end
837
+ #
838
+ # _@return_ — the read bytes
839
+ sig { params(n_bytes: T.nilable(Fixnum)).returns(String) }
840
+ def read(n_bytes = nil); end
841
+
842
+ # Returns the current pointer position within the IO
843
+ sig { returns(Fixnum) }
844
+ def tell; end
845
+
846
+ # Only used internally when reading the remote ZIP.
847
+ #
848
+ # _@param_ `range` — the HTTP range of data to fetch from remote
849
+ #
850
+ # _@return_ — the response body of the ranged request
851
+ sig { params(range: T::Range[T.untyped]).returns(String) }
852
+ def request_range(range); end
853
+
854
+ # For working with S3 it is a better idea to perform a GET request for one byte, since doing a HEAD
855
+ # request needs a different permission - and standard GET presigned URLs are not allowed to perform it
856
+ #
857
+ # _@return_ — the size of the remote resource, parsed either from Content-Length or Content-Range header
858
+ sig { returns(Integer) }
859
+ def request_object_size; end
860
+
861
+ # sord omit - no YARD type given for "a", using untyped
862
+ # sord omit - no YARD type given for "b", using untyped
863
+ # sord omit - no YARD type given for "c", using untyped
864
+ # sord omit - no YARD return type given, using untyped
865
+ sig { params(a: T.untyped, b: T.untyped, c: T.untyped).returns(T.untyped) }
866
+ def clamp(a, b, c); end
867
+ end
868
+
869
+ # A low-level ZIP file data writer. You can use it to write out various headers and central directory elements
870
+ # separately. The class handles the actual encoding of the data according to the ZIP format APPNOTE document.
871
+ #
872
+ # The primary reason the writer is a separate object is because it is kept stateless. That is, all the data that
873
+ # is needed for writing a piece of the ZIP (say, the EOCD record, or a data descriptor) can be written
874
+ # without depending on data available elsewhere. This makes the writer very easy to test, since each of
875
+ # it's methods outputs something that only depends on the method's arguments. For example, we use this
876
+ # to test writing Zip64 files which, when tested in a streaming fashion, would need tricky IO stubs
877
+ # to wind IO objects back and forth by large offsets. Instead, we can just write out the EOCD record
878
+ # with given offsets as arguments.
879
+ #
880
+ # Since some methods need a lot of data about the entity being written, everything is passed via
881
+ # keyword arguments - this way it is much less likely that you can make a mistake writing something.
882
+ #
883
+ # Another reason for having a separate Writer is that most ZIP libraries attach the methods for
884
+ # writing out the file headers to some sort of Entry object, which represents a file within the ZIP.
885
+ # However, when you are diagnosing issues with the ZIP files you produce, you actually want to have
886
+ # absolute _most_ of the code responsible for writing the actual encoded bytes available to you on
887
+ # one screen. Altering or checking that code then becomes much, much easier. The methods doing the
888
+ # writing are also intentionally left very verbose - so that you can follow what is happening at
889
+ # all times.
890
+ #
891
+ # All methods of the writer accept anything that responds to `<<` as `io` argument - you can use
892
+ # that to output to String objects, or to output to Arrays that you can later join together.
893
+ class ZipWriter
894
+ FOUR_BYTE_MAX_UINT = T.let(0xFFFFFFFF, T.untyped)
895
+ TWO_BYTE_MAX_UINT = T.let(0xFFFF, T.untyped)
896
+ ZIP_KIT_COMMENT = T.let("Written using ZipKit %<version>s" % {version: ZipKit::VERSION}, T.untyped)
897
+ VERSION_MADE_BY = T.let(52, T.untyped)
898
+ VERSION_NEEDED_TO_EXTRACT = T.let(20, T.untyped)
899
+ VERSION_NEEDED_TO_EXTRACT_ZIP64 = T.let(45, T.untyped)
900
+ DEFAULT_FILE_UNIX_PERMISSIONS = T.let(0o644, T.untyped)
901
+ DEFAULT_DIRECTORY_UNIX_PERMISSIONS = T.let(0o755, T.untyped)
902
+ FILE_TYPE_FILE = T.let(0o10, T.untyped)
903
+ FILE_TYPE_DIRECTORY = T.let(0o04, T.untyped)
904
+ MADE_BY_SIGNATURE = T.let(begin
905
+ # A combination of the VERSION_MADE_BY low byte and the OS type high byte
906
+ os_type = 3 # UNIX
907
+ [VERSION_MADE_BY, os_type].pack("CC")
908
+ end, T.untyped)
909
+ C_UINT4 = T.let("V", T.untyped)
910
+ C_UINT2 = T.let("v", T.untyped)
911
+ C_UINT8 = T.let("Q<", T.untyped)
912
+ C_CHAR = T.let("C", T.untyped)
913
+ C_INT4 = T.let("l<", T.untyped)
914
+
915
+ # sord duck - #<< looks like a duck type, replacing with untyped
916
+ # Writes the local file header, that precedes the actual file _data_.
917
+ #
918
+ # _@param_ `io` — the buffer to write the local file header to
919
+ #
920
+ # _@param_ `filename` — the name of the file in the archive
921
+ #
922
+ # _@param_ `compressed_size` — The size of the compressed (or stored) data - how much space it uses in the ZIP
923
+ #
924
+ # _@param_ `uncompressed_size` — The size of the file once extracted
925
+ #
926
+ # _@param_ `crc32` — The CRC32 checksum of the file
927
+ #
928
+ # _@param_ `mtime` — the modification time to be recorded in the ZIP
929
+ #
930
+ # _@param_ `gp_flags` — bit-packed general purpose flags
931
+ #
932
+ # _@param_ `storage_mode` — 8 for deflated, 0 for stored...
933
+ sig do
934
+ params(
935
+ io: T.untyped,
936
+ filename: String,
937
+ compressed_size: Fixnum,
938
+ uncompressed_size: Fixnum,
939
+ crc32: Fixnum,
940
+ gp_flags: Fixnum,
941
+ mtime: Time,
942
+ storage_mode: Fixnum
943
+ ).void
944
+ end
945
+ def write_local_file_header(io:, filename:, compressed_size:, uncompressed_size:, crc32:, gp_flags:, mtime:, storage_mode:); end
946
+
947
+ # sord duck - #<< looks like a duck type, replacing with untyped
948
+ # sord omit - no YARD type given for "local_file_header_location:", using untyped
949
+ # sord omit - no YARD type given for "storage_mode:", using untyped
950
+ # Writes the file header for the central directory, for a particular file in the archive. When writing out this data,
951
+ # ensure that the CRC32 and both sizes (compressed/uncompressed) are correct for the entry in question.
952
+ #
953
+ # _@param_ `io` — the buffer to write the local file header to
954
+ #
955
+ # _@param_ `filename` — the name of the file in the archive
956
+ #
957
+ # _@param_ `compressed_size` — The size of the compressed (or stored) data - how much space it uses in the ZIP
958
+ #
959
+ # _@param_ `uncompressed_size` — The size of the file once extracted
960
+ #
961
+ # _@param_ `crc32` — The CRC32 checksum of the file
962
+ #
963
+ # _@param_ `mtime` — the modification time to be recorded in the ZIP
964
+ #
965
+ # _@param_ `gp_flags` — bit-packed general purpose flags
966
+ #
967
+ # _@param_ `unix_permissions` — the permissions for the file, or nil for the default to be used
968
+ sig do
969
+ params(
970
+ io: T.untyped,
971
+ local_file_header_location: T.untyped,
972
+ gp_flags: Fixnum,
973
+ storage_mode: T.untyped,
974
+ compressed_size: Fixnum,
975
+ uncompressed_size: Fixnum,
976
+ mtime: Time,
977
+ crc32: Fixnum,
978
+ filename: String,
979
+ unix_permissions: T.nilable(Integer)
980
+ ).void
981
+ end
982
+ def write_central_directory_file_header(io:, local_file_header_location:, gp_flags:, storage_mode:, compressed_size:, uncompressed_size:, mtime:, crc32:, filename:, unix_permissions: nil); end
983
+
984
+ # sord duck - #<< looks like a duck type, replacing with untyped
985
+ # Writes the data descriptor following the file data for a file whose local file header
986
+ # was written with general-purpose flag bit 3 set. If the one of the sizes exceeds the Zip64 threshold,
987
+ # the data descriptor will have the sizes written out as 8-byte values instead of 4-byte values.
988
+ #
989
+ # _@param_ `io` — the buffer to write the local file header to
990
+ #
991
+ # _@param_ `crc32` — The CRC32 checksum of the file
992
+ #
993
+ # _@param_ `compressed_size` — The size of the compressed (or stored) data - how much space it uses in the ZIP
994
+ #
995
+ # _@param_ `uncompressed_size` — The size of the file once extracted
996
+ sig do
997
+ params(
998
+ io: T.untyped,
999
+ compressed_size: Fixnum,
1000
+ uncompressed_size: Fixnum,
1001
+ crc32: Fixnum
1002
+ ).void
1003
+ end
1004
+ def write_data_descriptor(io:, compressed_size:, uncompressed_size:, crc32:); end
1005
+
1006
+ # sord duck - #<< looks like a duck type, replacing with untyped
1007
+ # Writes the "end of central directory record" (including the Zip6 salient bits if necessary)
1008
+ #
1009
+ # _@param_ `io` — the buffer to write the central directory to.
1010
+ #
1011
+ # _@param_ `start_of_central_directory_location` — byte offset of the start of central directory form the beginning of ZIP file
1012
+ #
1013
+ # _@param_ `central_directory_size` — the size of the central directory (only file headers) in bytes
1014
+ #
1015
+ # _@param_ `num_files_in_archive` — How many files the archive contains
1016
+ #
1017
+ # _@param_ `comment` — the comment for the archive (defaults to ZIP_KIT_COMMENT)
1018
+ sig do
1019
+ params(
1020
+ io: T.untyped,
1021
+ start_of_central_directory_location: Fixnum,
1022
+ central_directory_size: Fixnum,
1023
+ num_files_in_archive: Fixnum,
1024
+ comment: String
1025
+ ).void
1026
+ end
1027
+ def write_end_of_central_directory(io:, start_of_central_directory_location:, central_directory_size:, num_files_in_archive:, comment: ZIP_KIT_COMMENT); end
1028
+
1029
+ # Writes the Zip64 extra field for the local file header. Will be used by `write_local_file_header` when any sizes given to it warrant that.
1030
+ #
1031
+ # _@param_ `compressed_size` — The size of the compressed (or stored) data - how much space it uses in the ZIP
1032
+ #
1033
+ # _@param_ `uncompressed_size` — The size of the file once extracted
1034
+ sig { params(compressed_size: Fixnum, uncompressed_size: Fixnum).returns(String) }
1035
+ def zip_64_extra_for_local_file_header(compressed_size:, uncompressed_size:); end
1036
+
1037
+ # sord omit - no YARD type given for "mtime", using untyped
1038
+ # sord omit - no YARD return type given, using untyped
1039
+ # Writes the extended timestamp information field for local headers.
1040
+ #
1041
+ # The spec defines 2
1042
+ # different formats - the one for the local file header can also accomodate the
1043
+ # atime and ctime, whereas the one for the central directory can only take
1044
+ # the mtime - and refers the reader to the local header extra to obtain the
1045
+ # remaining times
1046
+ sig { params(mtime: T.untyped).returns(T.untyped) }
1047
+ def timestamp_extra_for_local_file_header(mtime); end
1048
+
1049
+ # Writes the Zip64 extra field for the central directory header.It differs from the extra used in the local file header because it
1050
+ # also contains the location of the local file header in the ZIP as an 8-byte int.
1051
+ #
1052
+ # _@param_ `compressed_size` — The size of the compressed (or stored) data - how much space it uses in the ZIP
1053
+ #
1054
+ # _@param_ `uncompressed_size` — The size of the file once extracted
1055
+ #
1056
+ # _@param_ `local_file_header_location` — Byte offset of the start of the local file header from the beginning of the ZIP archive
1057
+ sig { params(compressed_size: Fixnum, uncompressed_size: Fixnum, local_file_header_location: Fixnum).returns(String) }
1058
+ def zip_64_extra_for_central_directory_file_header(compressed_size:, uncompressed_size:, local_file_header_location:); end
1059
+
1060
+ # sord omit - no YARD type given for "t", using untyped
1061
+ # sord omit - no YARD return type given, using untyped
1062
+ sig { params(t: T.untyped).returns(T.untyped) }
1063
+ def to_binary_dos_time(t); end
1064
+
1065
+ # sord omit - no YARD type given for "t", using untyped
1066
+ # sord omit - no YARD return type given, using untyped
1067
+ sig { params(t: T.untyped).returns(T.untyped) }
1068
+ def to_binary_dos_date(t); end
1069
+
1070
+ # sord omit - no YARD type given for "values_to_packspecs", using untyped
1071
+ # sord omit - no YARD return type given, using untyped
1072
+ # Unzips a given array of tuples of "numeric value, pack specifier" and then packs all the odd
1073
+ # values using specifiers from all the even values. It is harder to explain than to show:
1074
+ #
1075
+ # pack_array([1, 'V', 2, 'v', 148, 'v]) #=> "\x01\x00\x00\x00\x02\x00\x94\x00"
1076
+ #
1077
+ # will do the following two transforms:
1078
+ #
1079
+ # [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".
1080
+ # This might seem like a "clever optimisation" but the issue is that `pack` needs an array allocated per call, and
1081
+ # we output very verbosely - value-by-value. This might be quite a few array allocs. Using something like this
1082
+ # helps us save the array allocs
1083
+ sig { params(values_to_packspecs: T.untyped).returns(T.untyped) }
1084
+ def pack_array(values_to_packspecs); end
1085
+
1086
+ # sord omit - no YARD type given for "unix_permissions_int", using untyped
1087
+ # sord omit - no YARD type given for "file_type_int", using untyped
1088
+ # sord omit - no YARD return type given, using untyped
1089
+ sig { params(unix_permissions_int: T.untyped, file_type_int: T.untyped).returns(T.untyped) }
1090
+ def generate_external_attrs(unix_permissions_int, file_type_int); end
1091
+ end
1092
+
1093
+ # Acts as a converter between callers which send data to the `#<<` method (such as all the ZipKit
1094
+ # writer methods, which push onto anything), and a given block. Every time `#<<` gets called on the BlockWrite,
1095
+ # the block given to the constructor will be called with the same argument. ZipKit uses this object
1096
+ # when integrating with Rack and in the OutputEnumerator. Normally you wouldn't need to use it manually but
1097
+ # you always can. BlockWrite will also ensure the binary string encoding is forced onto any string
1098
+ # that passes through it.
1099
+ #
1100
+ # For example, you can create a Rack response body like so:
1101
+ #
1102
+ # class MyRackResponse
1103
+ # def each
1104
+ # writer = ZipKit::BlockWrite.new {|chunk| yield(chunk) }
1105
+ # writer << "Hello" << "world" << "!"
1106
+ # end
1107
+ # end
1108
+ # [200, {}, MyRackResponse.new]
1109
+ class BlockWrite
1110
+ # Creates a new BlockWrite.
1111
+ #
1112
+ # _@param_ `block` — The block that will be called when this object receives the `<<` message
1113
+ sig { params(block: T.untyped).void }
1114
+ def initialize(&block); end
1115
+
1116
+ # Sends a string through to the block stored in the BlockWrite.
1117
+ #
1118
+ # _@param_ `buf` — the string to write. Note that a zero-length String will not be forwarded to the block, as it has special meaning when used with chunked encoding (it indicates the end of the stream).
1119
+ #
1120
+ # _@return_ — self
1121
+ sig { params(buf: String).returns(T.untyped) }
1122
+ def <<(buf); end
1123
+ end
1124
+
1125
+ # A very barebones ZIP file reader. Is made for maximum interoperability, but at the same
1126
+ # time we attempt to keep it somewhat concise.
1127
+ #
1128
+ # ## REALLY CRAZY IMPORTANT STUFF: SECURITY IMPLICATIONS
1129
+ #
1130
+ # Please **BEWARE** - using this is a security risk if you are reading files that have been
1131
+ # supplied by users. This implementation has _not_ been formally verified for correctness. As
1132
+ # ZIP files contain relative offsets in lots of places it might be possible for a maliciously
1133
+ # crafted ZIP file to put the decode procedure in an endless loop, make it attempt huge reads
1134
+ # from the input file and so on. Additionally, the reader module for deflated data has
1135
+ # no support for ZIP bomb protection. So either limit the `FileReader` usage to the files you
1136
+ # trust, or triple-check all the inputs upfront. Patches to make this reader more secure
1137
+ # are welcome of course.
1138
+ #
1139
+ # ## Usage
1140
+ #
1141
+ # File.open('zipfile.zip', 'rb') do |f|
1142
+ # entries = ZipKit::FileReader.read_zip_structure(io: f)
1143
+ # entries.each do |e|
1144
+ # File.open(e.filename, 'wb') do |extracted_file|
1145
+ # ex = e.extractor_from(f)
1146
+ # extracted_file << ex.extract(1024 * 1024) until ex.eof?
1147
+ # end
1148
+ # end
1149
+ # end
1150
+ #
1151
+ # ## Supported features
1152
+ #
1153
+ # * Deflate and stored storage modes
1154
+ # * Zip64 (extra fields and offsets)
1155
+ # * Data descriptors
1156
+ #
1157
+ # ## Unsupported features
1158
+ #
1159
+ # * Archives split over multiple disks/files
1160
+ # * Any ZIP encryption
1161
+ # * EFS language flag and InfoZIP filename extra field
1162
+ # * CRC32 checksums are _not_ verified
1163
+ #
1164
+ # ## Mode of operation
1165
+ #
1166
+ # By default, `FileReader` _ignores_ the data in local file headers (as it is
1167
+ # often unreliable). It reads the ZIP file "from the tail", finds the
1168
+ # end-of-central-directory signatures, then reads the central directory entries,
1169
+ # reconstitutes the entries with their filenames, attributes and so on, and
1170
+ # sets these entries up with the absolute _offsets_ into the source file/IO object.
1171
+ # These offsets can then be used to extract the actual compressed data of
1172
+ # the files and to expand it.
1173
+ #
1174
+ # ## Recovering damaged or incomplete ZIP files
1175
+ #
1176
+ # If the ZIP file you are trying to read does not contain the central directory
1177
+ # records `read_zip_structure` will not work, since it starts the read process
1178
+ # from the EOCD marker at the end of the central directory and then crawls
1179
+ # "back" in the IO to figure out the rest. You can explicitly apply a fallback
1180
+ # for reading the archive "straight ahead" instead using `read_zip_straight_ahead`
1181
+ # - the method will instead scan your IO from the very start, skipping over
1182
+ # the actual entry data. This is less efficient than central directory parsing since
1183
+ # it involves a much larger number of reads (1 read from the IO per entry in the ZIP).
1184
+ class FileReader
1185
+ ReadError = T.let(Class.new(StandardError), T.untyped)
1186
+ UnsupportedFeature = T.let(Class.new(StandardError), T.untyped)
1187
+ InvalidStructure = T.let(Class.new(ReadError), T.untyped)
1188
+ LocalHeaderPending = T.let(Class.new(StandardError) do
1189
+ def message
1190
+ "The compressed data offset is not available (local header has not been read)"
1191
+ end
1192
+ end, T.untyped)
1193
+ MissingEOCD = T.let(Class.new(StandardError) do
1194
+ def message
1195
+ "Could not find the EOCD signature in the buffer - maybe a malformed ZIP file"
1196
+ end
1197
+ end, T.untyped)
1198
+ C_UINT4 = T.let("V", T.untyped)
1199
+ C_UINT2 = T.let("v", T.untyped)
1200
+ C_UINT8 = T.let("Q<", T.untyped)
1201
+ MAX_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE = T.let(4 + # Offset of the start of central directory
1202
+ 4 + # Size of the central directory
1203
+ 2 + # Number of files in the cdir
1204
+ 4 + # End-of-central-directory signature
1205
+ 2 + # Number of this disk
1206
+ 2 + # Number of disk with the start of cdir
1207
+ 2 + # Number of files in the cdir of this disk
1208
+ 2 + # The comment size
1209
+ 0xFFFF, T.untyped)
1210
+ MAX_LOCAL_HEADER_SIZE = T.let(4 + # signature
1211
+ 2 + # Version needed to extract
1212
+ 2 + # gp flags
1213
+ 2 + # storage mode
1214
+ 2 + # dos time
1215
+ 2 + # dos date
1216
+ 4 + # CRC32
1217
+ 4 + # Comp size
1218
+ 4 + # Uncomp size
1219
+ 2 + # Filename size
1220
+ 2 + # Extra fields size
1221
+ 0xFFFF + # Maximum filename size
1222
+ 0xFFFF, T.untyped)
1223
+ SIZE_OF_USABLE_EOCD_RECORD = T.let(4 + # Signature
1224
+ 2 + # Number of this disk
1225
+ 2 + # Number of the disk with the EOCD record
1226
+ 2 + # Number of entries in the central directory of this disk
1227
+ 2 + # Number of entries in the central directory total
1228
+ 4 + # Size of the central directory
1229
+ 4, T.untyped)
1230
+
1231
+ # sord duck - #tell looks like a duck type, replacing with untyped
1232
+ # sord duck - #seek looks like a duck type, replacing with untyped
1233
+ # sord duck - #read looks like a duck type, replacing with untyped
1234
+ # sord duck - #size looks like a duck type, replacing with untyped
1235
+ # Parse an IO handle to a ZIP archive into an array of Entry objects.
1236
+ #
1237
+ # _@param_ `io` — an IO-ish object
1238
+ #
1239
+ # _@param_ `read_local_headers` — whether the local headers must be read upfront. When reading a locally available ZIP file this option will not have much use since the small reads from the file handle are not going to be that important. However, if you are using remote reads to decipher a ZIP file located on an HTTP server, the operation _must_ perform an HTTP request for _each entry in the ZIP file_ to determine where the actual file data starts. This, for a ZIP archive of 1000 files, will incur 1000 extra HTTP requests - which you might not want to perform upfront, or - at least - not want to perform _at once_. When the option is set to `false`, you will be getting instances of `LazyEntry` instead of `Entry`. Those objects will raise an exception when you attempt to access their compressed data offset in the ZIP (since the reads have not been performed yet). As a rule, this option can be left in it's default setting (`true`) unless you want to _only_ read the central directory, or you need to limit the number of HTTP requests.
1240
+ #
1241
+ # _@return_ — an array of entries within the ZIP being parsed
1242
+ sig { params(io: T.untyped, read_local_headers: T::Boolean).returns(T::Array[ZipEntry]) }
1243
+ def read_zip_structure(io:, read_local_headers: true); end
1244
+
1245
+ # sord duck - #tell looks like a duck type, replacing with untyped
1246
+ # sord duck - #read looks like a duck type, replacing with untyped
1247
+ # sord duck - #seek looks like a duck type, replacing with untyped
1248
+ # sord omit - no YARD return type given, using untyped
1249
+ # Sometimes you might encounter truncated ZIP files, which do not contain
1250
+ # any central directory whatsoever - or where the central directory is
1251
+ # truncated. In that case, employing the technique of reading the ZIP
1252
+ # "from the end" is impossible, and the only recourse is reading each
1253
+ # local file header in sucession. If the entries in such a ZIP use data
1254
+ # descriptors, you would need to scan after the entry until you encounter
1255
+ # the data descriptor signature - and that might be unreliable at best.
1256
+ # Therefore, this reading technique does not support data descriptors.
1257
+ # It can however recover the entries you still can read if these entries
1258
+ # contain all the necessary information about the contained file.
1259
+ #
1260
+ # headers from @return [Array<ZipEntry>] an array of entries that could be
1261
+ # recovered before hitting EOF
1262
+ #
1263
+ # _@param_ `io` — the IO-ish object to read the local file
1264
+ sig { params(io: T.untyped).returns(T.untyped) }
1265
+ def read_zip_straight_ahead(io:); end
1266
+
1267
+ # sord duck - #read looks like a duck type, replacing with untyped
1268
+ # Parse the local header entry and get the offset in the IO at which the
1269
+ # actual compressed data of the file starts within the ZIP.
1270
+ # The method will eager-read the entire local header for the file
1271
+ # (the maximum size the local header may use), starting at the given offset,
1272
+ # and will then compute its size. That size plus the local header offset
1273
+ # given will be the compressed data offset of the entry (read starting at
1274
+ # this offset to get the data).
1275
+ #
1276
+ # the compressed data offset
1277
+ #
1278
+ # _@param_ `io` — an IO-ish object the ZIP file can be read from
1279
+ #
1280
+ # _@return_ — the parsed local header entry and
1281
+ sig { params(io: T.untyped).returns(T::Array[T.any(ZipEntry, Fixnum)]) }
1282
+ def read_local_file_header(io:); end
1283
+
1284
+ # sord duck - #seek looks like a duck type, replacing with untyped
1285
+ # sord duck - #read looks like a duck type, replacing with untyped
1286
+ # sord omit - no YARD return type given, using untyped
1287
+ # Get the offset in the IO at which the actual compressed data of the file
1288
+ # starts within the ZIP. The method will eager-read the entire local header
1289
+ # for the file (the maximum size the local header may use), starting at the
1290
+ # given offset, and will then compute its size. That size plus the local
1291
+ # header offset given will be the compressed data offset of the entry
1292
+ # (read starting at this offset to get the data).
1293
+ #
1294
+ # local file header is supposed to begin @return [Fixnum] absolute offset
1295
+ # (0-based) of where the compressed data begins for this file within the ZIP
1296
+ #
1297
+ # _@param_ `io` — an IO-ish object the ZIP file can be read from
1298
+ #
1299
+ # _@param_ `local_file_header_offset` — absolute offset (0-based) where the
1300
+ sig { params(io: T.untyped, local_file_header_offset: Fixnum).returns(T.untyped) }
1301
+ def get_compressed_data_offset(io:, local_file_header_offset:); end
1302
+
1303
+ # Parse an IO handle to a ZIP archive into an array of Entry objects, reading from the end
1304
+ # of the IO object.
1305
+ #
1306
+ # _@param_ `options` — any options the instance method of the same name accepts
1307
+ #
1308
+ # _@return_ — an array of entries within the ZIP being parsed
1309
+ #
1310
+ # _@see_ `#read_zip_structure`
1311
+ sig { params(options: T::Hash[T.untyped, T.untyped]).returns(T::Array[ZipEntry]) }
1312
+ def self.read_zip_structure(**options); end
1313
+
1314
+ # Parse an IO handle to a ZIP archive into an array of Entry objects, reading from the start of
1315
+ # the file and parsing local file headers one-by-one
1316
+ #
1317
+ # _@param_ `options` — any options the instance method of the same name accepts
1318
+ #
1319
+ # _@return_ — an array of entries within the ZIP being parsed
1320
+ #
1321
+ # _@see_ `#read_zip_straight_ahead`
1322
+ sig { params(options: T::Hash[T.untyped, T.untyped]).returns(T::Array[ZipEntry]) }
1323
+ def self.read_zip_straight_ahead(**options); end
1324
+
1325
+ # sord omit - no YARD type given for "entries", using untyped
1326
+ # sord omit - no YARD type given for "io", using untyped
1327
+ # sord omit - no YARD return type given, using untyped
1328
+ sig { params(entries: T.untyped, io: T.untyped).returns(T.untyped) }
1329
+ def read_local_headers(entries, io); end
1330
+
1331
+ # sord omit - no YARD type given for "io", using untyped
1332
+ # sord omit - no YARD return type given, using untyped
1333
+ sig { params(io: T.untyped).returns(T.untyped) }
1334
+ def skip_ahead_2(io); end
1335
+
1336
+ # sord omit - no YARD type given for "io", using untyped
1337
+ # sord omit - no YARD return type given, using untyped
1338
+ sig { params(io: T.untyped).returns(T.untyped) }
1339
+ def skip_ahead_4(io); end
1340
+
1341
+ # sord omit - no YARD type given for "io", using untyped
1342
+ # sord omit - no YARD return type given, using untyped
1343
+ sig { params(io: T.untyped).returns(T.untyped) }
1344
+ def skip_ahead_8(io); end
1345
+
1346
+ # sord omit - no YARD type given for "io", using untyped
1347
+ # sord omit - no YARD type given for "absolute_pos", using untyped
1348
+ # sord omit - no YARD return type given, using untyped
1349
+ sig { params(io: T.untyped, absolute_pos: T.untyped).returns(T.untyped) }
1350
+ def seek(io, absolute_pos); end
1351
+
1352
+ # sord omit - no YARD type given for "io", using untyped
1353
+ # sord omit - no YARD type given for "signature_magic_number", using untyped
1354
+ # sord omit - no YARD return type given, using untyped
1355
+ sig { params(io: T.untyped, signature_magic_number: T.untyped).returns(T.untyped) }
1356
+ def assert_signature(io, signature_magic_number); end
1357
+
1358
+ # sord omit - no YARD type given for "io", using untyped
1359
+ # sord omit - no YARD type given for "n", using untyped
1360
+ # sord omit - no YARD return type given, using untyped
1361
+ sig { params(io: T.untyped, n: T.untyped).returns(T.untyped) }
1362
+ def skip_ahead_n(io, n); end
1363
+
1364
+ # sord omit - no YARD type given for "io", using untyped
1365
+ # sord omit - no YARD type given for "n_bytes", using untyped
1366
+ # sord omit - no YARD return type given, using untyped
1367
+ sig { params(io: T.untyped, n_bytes: T.untyped).returns(T.untyped) }
1368
+ def read_n(io, n_bytes); end
1369
+
1370
+ # sord omit - no YARD type given for "io", using untyped
1371
+ # sord omit - no YARD return type given, using untyped
1372
+ sig { params(io: T.untyped).returns(T.untyped) }
1373
+ def read_2b(io); end
1374
+
1375
+ # sord omit - no YARD type given for "io", using untyped
1376
+ # sord omit - no YARD return type given, using untyped
1377
+ sig { params(io: T.untyped).returns(T.untyped) }
1378
+ def read_4b(io); end
1379
+
1380
+ # sord omit - no YARD type given for "io", using untyped
1381
+ # sord omit - no YARD return type given, using untyped
1382
+ sig { params(io: T.untyped).returns(T.untyped) }
1383
+ def read_8b(io); end
1384
+
1385
+ # sord omit - no YARD type given for "io", using untyped
1386
+ # sord omit - no YARD return type given, using untyped
1387
+ sig { params(io: T.untyped).returns(T.untyped) }
1388
+ def read_cdir_entry(io); end
1389
+
1390
+ # sord omit - no YARD type given for "file_io", using untyped
1391
+ # sord omit - no YARD type given for "zip_file_size", using untyped
1392
+ # sord omit - no YARD return type given, using untyped
1393
+ sig { params(file_io: T.untyped, zip_file_size: T.untyped).returns(T.untyped) }
1394
+ def get_eocd_offset(file_io, zip_file_size); end
1395
+
1396
+ # sord omit - no YARD type given for "of_substring", using untyped
1397
+ # sord omit - no YARD type given for "in_string", using untyped
1398
+ # sord omit - no YARD return type given, using untyped
1399
+ sig { params(of_substring: T.untyped, in_string: T.untyped).returns(T.untyped) }
1400
+ def all_indices_of_substr_in_str(of_substring, in_string); end
1401
+
1402
+ # sord omit - no YARD type given for "in_str", using untyped
1403
+ # sord omit - no YARD return type given, using untyped
1404
+ # We have to scan the maximum possible number
1405
+ # of bytes that the EOCD can theoretically occupy including the comment after it,
1406
+ # and we have to find a combination of:
1407
+ # [EOCD signature, <some ZIP medatata>, comment byte size, comment of size]
1408
+ # at the end. To do so, we first find all indices of the signature in the trailer
1409
+ # string, and then check whether the bytestring starting at the signature and
1410
+ # ending at the end of string satisfies that given pattern.
1411
+ sig { params(in_str: T.untyped).returns(T.untyped) }
1412
+ def locate_eocd_signature(in_str); end
1413
+
1414
+ # sord omit - no YARD type given for "file_io", using untyped
1415
+ # sord omit - no YARD type given for "eocd_offset", using untyped
1416
+ # sord omit - no YARD return type given, using untyped
1417
+ # Find the Zip64 EOCD locator segment offset. Do this by seeking backwards from the
1418
+ # EOCD record in the archive by fixed offsets
1419
+ # get_zip64_eocd_location is too high. [15.17/15]
1420
+ sig { params(file_io: T.untyped, eocd_offset: T.untyped).returns(T.untyped) }
1421
+ def get_zip64_eocd_location(file_io, eocd_offset); end
1422
+
1423
+ # sord omit - no YARD type given for "io", using untyped
1424
+ # sord omit - no YARD type given for "zip64_end_of_cdir_location", using untyped
1425
+ # sord omit - no YARD return type given, using untyped
1426
+ # num_files_and_central_directory_offset_zip64 is too high. [21.12/15]
1427
+ sig { params(io: T.untyped, zip64_end_of_cdir_location: T.untyped).returns(T.untyped) }
1428
+ def num_files_and_central_directory_offset_zip64(io, zip64_end_of_cdir_location); end
1429
+
1430
+ # sord omit - no YARD type given for "file_io", using untyped
1431
+ # sord omit - no YARD type given for "eocd_offset", using untyped
1432
+ # sord omit - no YARD return type given, using untyped
1433
+ # Start of the central directory offset
1434
+ sig { params(file_io: T.untyped, eocd_offset: T.untyped).returns(T.untyped) }
1435
+ def num_files_and_central_directory_offset(file_io, eocd_offset); end
1436
+
1437
+ # sord omit - no YARD return type given, using untyped
1438
+ # Is provided as a stub to be overridden in a subclass if you need it. Will report
1439
+ # during various stages of reading. The log message is contained in the return value
1440
+ # of `yield` in the method (the log messages are lazy-evaluated).
1441
+ sig { returns(T.untyped) }
1442
+ def log; end
1443
+
1444
+ # sord omit - no YARD type given for "extra_fields_str", using untyped
1445
+ # sord omit - no YARD return type given, using untyped
1446
+ sig { params(extra_fields_str: T.untyped).returns(T.untyped) }
1447
+ def parse_out_extra_fields(extra_fields_str); end
1448
+
1449
+ # Rubocop: convention: Missing top-level class documentation comment.
1450
+ class StoredReader
1451
+ # sord omit - no YARD type given for "from_io", using untyped
1452
+ # sord omit - no YARD type given for "compressed_data_size", using untyped
1453
+ sig { params(from_io: T.untyped, compressed_data_size: T.untyped).void }
1454
+ def initialize(from_io, compressed_data_size); end
1455
+
1456
+ # sord omit - no YARD type given for "n_bytes", using untyped
1457
+ # sord omit - no YARD return type given, using untyped
1458
+ sig { params(n_bytes: T.untyped).returns(T.untyped) }
1459
+ def extract(n_bytes = nil); end
1460
+
1461
+ sig { returns(T::Boolean) }
1462
+ def eof?; end
1463
+ end
1464
+
1465
+ # Rubocop: convention: Missing top-level class documentation comment.
1466
+ class InflatingReader
1467
+ # sord omit - no YARD type given for "from_io", using untyped
1468
+ # sord omit - no YARD type given for "compressed_data_size", using untyped
1469
+ sig { params(from_io: T.untyped, compressed_data_size: T.untyped).void }
1470
+ def initialize(from_io, compressed_data_size); end
1471
+
1472
+ # sord omit - no YARD type given for "n_bytes", using untyped
1473
+ # sord omit - no YARD return type given, using untyped
1474
+ sig { params(n_bytes: T.untyped).returns(T.untyped) }
1475
+ def extract(n_bytes = nil); end
1476
+
1477
+ sig { returns(T::Boolean) }
1478
+ def eof?; end
1479
+ end
1480
+
1481
+ # Represents a file within the ZIP archive being read. This is different from
1482
+ # the Entry object used in Streamer for ZIP writing, since during writing more
1483
+ # data can be kept in memory for immediate use.
1484
+ class ZipEntry
1485
+ # sord omit - no YARD type given for "from_io", using untyped
1486
+ # Returns a reader for the actual compressed data of the entry.
1487
+ #
1488
+ # reader = entry.extractor_from(source_file)
1489
+ # outfile << reader.extract(512 * 1024) until reader.eof?
1490
+ #
1491
+ # _@return_ — the reader for the data
1492
+ sig { params(from_io: T.untyped).returns(T.any(StoredReader, InflatingReader)) }
1493
+ def extractor_from(from_io); end
1494
+
1495
+ # _@return_ — at what offset you should start reading
1496
+ # for the compressed data in your original IO object
1497
+ sig { returns(Fixnum) }
1498
+ def compressed_data_offset; end
1499
+
1500
+ # Tells whether the compressed data offset is already known for this entry
1501
+ sig { returns(T::Boolean) }
1502
+ def known_offset?; end
1503
+
1504
+ # Tells whether the entry uses a data descriptor (this is defined
1505
+ # by bit 3 in the GP flags).
1506
+ sig { returns(T::Boolean) }
1507
+ def uses_data_descriptor?; end
1508
+
1509
+ # sord infer - inferred type of parameter "offset" as Fixnum using getter's return type
1510
+ # sord omit - no YARD return type given, using untyped
1511
+ # Sets the offset at which the compressed data for this file starts in the ZIP.
1512
+ # By default, the value will be set by the Reader for you. If you use delayed
1513
+ # reading, you need to set it by using the `get_compressed_data_offset` on the Reader:
1514
+ #
1515
+ # entry.compressed_data_offset = reader.get_compressed_data_offset(io: file,
1516
+ # local_file_header_offset: entry.local_header_offset)
1517
+ sig { params(offset: Fixnum).returns(T.untyped) }
1518
+ def compressed_data_offset=(offset); end
1519
+
1520
+ # _@return_ — bit-packed version signature of the program that made the archive
1521
+ sig { returns(Fixnum) }
1522
+ attr_accessor :made_by
1523
+
1524
+ # _@return_ — ZIP version support needed to extract this file
1525
+ sig { returns(Fixnum) }
1526
+ attr_accessor :version_needed_to_extract
1527
+
1528
+ # _@return_ — bit-packed general purpose flags
1529
+ sig { returns(Fixnum) }
1530
+ attr_accessor :gp_flags
1531
+
1532
+ # _@return_ — Storage mode (0 for stored, 8 for deflate)
1533
+ sig { returns(Fixnum) }
1534
+ attr_accessor :storage_mode
1535
+
1536
+ # _@return_ — the bit-packed DOS time
1537
+ sig { returns(Fixnum) }
1538
+ attr_accessor :dos_time
1539
+
1540
+ # _@return_ — the bit-packed DOS date
1541
+ sig { returns(Fixnum) }
1542
+ attr_accessor :dos_date
1543
+
1544
+ # _@return_ — the CRC32 checksum of this file
1545
+ sig { returns(Fixnum) }
1546
+ attr_accessor :crc32
1547
+
1548
+ # _@return_ — size of compressed file data in the ZIP
1549
+ sig { returns(Fixnum) }
1550
+ attr_accessor :compressed_size
1551
+
1552
+ # _@return_ — size of the file once uncompressed
1553
+ sig { returns(Fixnum) }
1554
+ attr_accessor :uncompressed_size
1555
+
1556
+ # _@return_ — the filename
1557
+ sig { returns(String) }
1558
+ attr_accessor :filename
1559
+
1560
+ # _@return_ — disk number where this file starts
1561
+ sig { returns(Fixnum) }
1562
+ attr_accessor :disk_number_start
1563
+
1564
+ # _@return_ — internal attributes of the file
1565
+ sig { returns(Fixnum) }
1566
+ attr_accessor :internal_attrs
1567
+
1568
+ # _@return_ — external attributes of the file
1569
+ sig { returns(Fixnum) }
1570
+ attr_accessor :external_attrs
1571
+
1572
+ # _@return_ — at what offset the local file header starts
1573
+ # in your original IO object
1574
+ sig { returns(Fixnum) }
1575
+ attr_accessor :local_file_header_offset
1576
+
1577
+ # _@return_ — the file comment
1578
+ sig { returns(String) }
1579
+ attr_accessor :comment
1580
+ end
1581
+ end
1582
+
1583
+ # Used when you need to supply a destination IO for some
1584
+ # write operations, but want to discard the data (like when
1585
+ # estimating the size of a ZIP)
1586
+ module NullWriter
1587
+ # _@param_ `_` — the data to write
1588
+ sig { params(_: String).returns(T.self_type) }
1589
+ def self.<<(_); end
1590
+ end
1591
+
1592
+ # Alows reading the central directory of a remote ZIP file without
1593
+ # downloading the entire file. The central directory provides the
1594
+ # offsets at which the actual file contents is located. You can then
1595
+ # use the `Range:` HTTP headers to download those entries separately.
1596
+ #
1597
+ # Please read the security warning in `FileReader` _VERY CAREFULLY_
1598
+ # before you use this module.
1599
+ module RemoteUncap
1600
+ # {ZipKit::FileReader} when reading
1601
+ # files within the remote archive
1602
+ #
1603
+ # _@param_ `uri` — the HTTP(S) URL to read the ZIP footer from
1604
+ #
1605
+ # _@param_ `reader_class` — which class to use for reading
1606
+ #
1607
+ # _@param_ `options_for_zip_reader` — any additional options to give to
1608
+ #
1609
+ # _@return_ — metadata about the
1610
+ sig { params(uri: String, reader_class: Class, options_for_zip_reader: T::Hash[T.untyped, T.untyped]).returns(T::Array[ZipKit::FileReader::ZipEntry]) }
1611
+ def self.files_within_zip_at(uri, reader_class: ZipKit::FileReader, **options_for_zip_reader); end
1612
+ end
1613
+
1614
+ # A simple stateful class for keeping track of a CRC32 value through multiple writes
1615
+ class StreamCRC32
1616
+ include ZipKit::WriteShovel
1617
+ STRINGS_HAVE_CAPACITY_SUPPORT = T.let(begin
1618
+ String.new("", capacity: 1)
1619
+ true
1620
+ rescue ArgumentError
1621
+ false
1622
+ end, T.untyped)
1623
+ CRC_BUF_SIZE = T.let(1024 * 512, T.untyped)
1624
+
1625
+ # Compute a CRC32 value from an IO object. The object should respond to `read` and `eof?`
1626
+ #
1627
+ # _@param_ `io` — the IO to read the data from
1628
+ #
1629
+ # _@return_ — the computed CRC32 value
1630
+ sig { params(io: IO).returns(Fixnum) }
1631
+ def self.from_io(io); end
1632
+
1633
+ # Creates a new streaming CRC32 calculator
1634
+ sig { void }
1635
+ def initialize; end
1636
+
1637
+ # Append data to the CRC32. Updates the contained CRC32 value in place.
1638
+ #
1639
+ # _@param_ `blob` — the string to compute the CRC32 from
1640
+ sig { params(blob: String).returns(T.self_type) }
1641
+ def <<(blob); end
1642
+
1643
+ # Returns the CRC32 value computed so far
1644
+ #
1645
+ # _@return_ — the updated CRC32 value for all the blobs so far
1646
+ sig { returns(Fixnum) }
1647
+ def to_i; end
1648
+
1649
+ # Appends a known CRC32 value to the current one, and combines the
1650
+ # contained CRC32 value in-place.
1651
+ #
1652
+ # _@param_ `crc32` — the CRC32 value to append
1653
+ #
1654
+ # _@param_ `blob_size` — the size of the daata the `crc32` is computed from
1655
+ #
1656
+ # _@return_ — the updated CRC32 value for all the blobs so far
1657
+ sig { params(crc32: Fixnum, blob_size: Fixnum).returns(Fixnum) }
1658
+ def append(crc32, blob_size); end
1659
+
1660
+ # sord infer - argument name in single @param inferred as "bytes"
1661
+ # Writes the given data to the output stream. Allows the object to be used as
1662
+ # a target for `IO.copy_stream(from, to)`
1663
+ #
1664
+ # _@param_ `d` — the binary string to write (part of the uncompressed file)
1665
+ #
1666
+ # _@return_ — the number of bytes written
1667
+ sig { params(bytes: String).returns(Fixnum) }
1668
+ def write(bytes); end
1669
+ end
1670
+
1671
+ # Some operations (such as CRC32) benefit when they are performed
1672
+ # on larger chunks of data. In certain use cases, it is possible that
1673
+ # the consumer of ZipKit is going to be writing small chunks
1674
+ # in rapid succession, so CRC32 is going to have to perform a lot of
1675
+ # CRC32 combine operations - and this adds up. Since the CRC32 value
1676
+ # is usually not needed until the complete output has completed
1677
+ # we can buffer at least some amount of data before computing CRC32 over it.
1678
+ # We also use this buffer for output via Rack, where some amount of buffering
1679
+ # helps reduce the number of syscalls made by the webserver. ZipKit performs
1680
+ # lots of very small writes, and some degree of speedup (about 20%) can be achieved
1681
+ # with a buffer of a few KB.
1682
+ #
1683
+ # Note that there is no guarantee that the write buffer is going to flush at or above
1684
+ # the given `buffer_size`, because for writes which exceed the buffer size it will
1685
+ # first `flush` and then write through the oversized chunk, without buffering it. This
1686
+ # helps conserve memory. Also note that the buffer will *not* duplicate strings for you
1687
+ # and *will* yield the same buffer String over and over, so if you are storing it in an
1688
+ # Array you might need to duplicate it.
1689
+ #
1690
+ # Note also that the WriteBuffer assumes that the object it `<<`-writes into is going
1691
+ # to **consume** in some way the string that it passes in. After the `<<` method returns,
1692
+ # the WriteBuffer will be cleared, and it passes the same String reference on every call
1693
+ # to `<<`. Therefore, if you need to retain the output of the WriteBuffer in, say, an Array,
1694
+ # you might need to `.dup` the `String` it gives you.
1695
+ class WriteBuffer
1696
+ # sord duck - #<< looks like a duck type, replacing with untyped
1697
+ # Creates a new WriteBuffer bypassing into a given writable object
1698
+ #
1699
+ # _@param_ `writable` — An object that responds to `#<<` with a String as argument
1700
+ #
1701
+ # _@param_ `buffer_size` — How many bytes to buffer
1702
+ sig { params(writable: T.untyped, buffer_size: Integer).void }
1703
+ def initialize(writable, buffer_size); end
1704
+
1705
+ # Appends the given data to the write buffer, and flushes the buffer into the
1706
+ # writable if the buffer size exceeds the `buffer_size` given at initialization
1707
+ #
1708
+ # _@param_ `data` — data to be written
1709
+ #
1710
+ # _@return_ — self
1711
+ sig { params(data: String).returns(T.untyped) }
1712
+ def <<(data); end
1713
+
1714
+ # Explicitly flushes the buffer if it contains anything
1715
+ #
1716
+ # _@return_ — self
1717
+ sig { returns(T.untyped) }
1718
+ def flush; end
1719
+ end
1720
+
1721
+ # A lot of objects in ZipKit accept bytes that may be sent
1722
+ # to the `<<` operator (the "shovel" operator). This is in the tradition
1723
+ # of late Jim Weirich and his Builder gem. In [this presentation](https://youtu.be/1BVFlvRPZVM?t=2403)
1724
+ # he justifies this design very eloquently. In ZipKit we follow this example.
1725
+ # However, there is a number of methods in Ruby - including the standard library -
1726
+ # which expect your object to implement the `write` method instead. Since the `write`
1727
+ # method can be expressed in terms of the `<<` method, why not allow all ZipKit
1728
+ # "IO-ish" things to also respond to `write`? This is what this module does.
1729
+ # Jim would be proud. We miss you, Jim.
1730
+ module WriteShovel
1731
+ # sord infer - argument name in single @param inferred as "bytes"
1732
+ # Writes the given data to the output stream. Allows the object to be used as
1733
+ # a target for `IO.copy_stream(from, to)`
1734
+ #
1735
+ # _@param_ `d` — the binary string to write (part of the uncompressed file)
1736
+ #
1737
+ # _@return_ — the number of bytes written
1738
+ sig { params(bytes: String).returns(Fixnum) }
1739
+ def write(bytes); end
1740
+ end
1741
+
1742
+ # Permits Deflate compression in independent blocks. The workflow is as follows:
1743
+ #
1744
+ # * Run every block to compress through deflate_chunk, remove the header,
1745
+ # footer and adler32 from the result
1746
+ # * Write out the compressed block bodies (the ones deflate_chunk returns)
1747
+ # to your output, in sequence
1748
+ # * Write out the footer (\03\00)
1749
+ #
1750
+ # The resulting stream is guaranteed to be handled properly by all zip
1751
+ # unarchiving tools, including the BOMArchiveHelper/ArchiveUtility on OSX.
1752
+ #
1753
+ # You could also build a compressor for Rubyzip using this module quite easily,
1754
+ # even though this is outside the scope of the library.
1755
+ #
1756
+ # When you deflate the chunks separately, you need to write the end marker
1757
+ # yourself (using `write_terminator`).
1758
+ # If you just want to deflate a large IO's contents, use
1759
+ # `deflate_in_blocks_and_terminate` to have the end marker written out for you.
1760
+ #
1761
+ # Basic usage to compress a file in parts:
1762
+ #
1763
+ # source_file = File.open('12_gigs.bin', 'rb')
1764
+ # compressed = Tempfile.new
1765
+ # # Will not compress everything in memory, but do it per chunk to spare
1766
+ # memory. `compressed`
1767
+ # # will be written to at the end of each chunk.
1768
+ # ZipKit::BlockDeflate.deflate_in_blocks_and_terminate(source_file,
1769
+ # compressed)
1770
+ #
1771
+ # You can also do the same to parts that you will later concatenate together
1772
+ # elsewhere, in that case you need to skip the end marker:
1773
+ #
1774
+ # compressed = Tempfile.new
1775
+ # ZipKit::BlockDeflate.deflate_in_blocks(File.open('part1.bin', 'rb),
1776
+ # compressed)
1777
+ # ZipKit::BlockDeflate.deflate_in_blocks(File.open('part2.bin', 'rb),
1778
+ # compressed)
1779
+ # ZipKit::BlockDeflate.deflate_in_blocks(File.open('partN.bin', 'rb),
1780
+ # compressed)
1781
+ # ZipKit::BlockDeflate.write_terminator(compressed)
1782
+ #
1783
+ # You can also elect to just compress strings in memory (to splice them later):
1784
+ #
1785
+ # compressed_string = ZipKit::BlockDeflate.deflate_chunk(big_string)
1786
+ class BlockDeflate
1787
+ DEFAULT_BLOCKSIZE = T.let(1_024 * 1024 * 5, T.untyped)
1788
+ END_MARKER = T.let([3, 0].pack("C*"), T.untyped)
1789
+ VALID_COMPRESSIONS = T.let((Zlib::DEFAULT_COMPRESSION..Zlib::BEST_COMPRESSION).to_a.freeze, T.untyped)
1790
+
1791
+ # Write the end marker (\x3\x0) to the given IO.
1792
+ #
1793
+ # `output_io` can also be a {ZipKit::Streamer} to expedite ops.
1794
+ #
1795
+ # _@param_ `output_io` — the stream to write to (should respond to `:<<`)
1796
+ #
1797
+ # _@return_ — number of bytes written to `output_io`
1798
+ sig { params(output_io: IO).returns(Fixnum) }
1799
+ def self.write_terminator(output_io); end
1800
+
1801
+ # Compress a given binary string and flush the deflate stream at byte boundary.
1802
+ # The returned string can be spliced into another deflate stream.
1803
+ #
1804
+ # _@param_ `bytes` — Bytes to compress
1805
+ #
1806
+ # _@param_ `level` — Zlib compression level (defaults to `Zlib::DEFAULT_COMPRESSION`)
1807
+ #
1808
+ # _@return_ — compressed bytes
1809
+ sig { params(bytes: String, level: Fixnum).returns(String) }
1810
+ def self.deflate_chunk(bytes, level: Zlib::DEFAULT_COMPRESSION); end
1811
+
1812
+ # Compress the contents of input_io into output_io, in blocks
1813
+ # of block_size. Aligns the parts so that they can be concatenated later.
1814
+ # Writes deflate end marker (\x3\x0) into `output_io` as the final step, so
1815
+ # the contents of `output_io` can be spliced verbatim into a ZIP archive.
1816
+ #
1817
+ # Once the write completes, no more parts for concatenation should be written to
1818
+ # the same stream.
1819
+ #
1820
+ # `output_io` can also be a {ZipKit::Streamer} to expedite ops.
1821
+ #
1822
+ # _@param_ `input_io` — the stream to read from (should respond to `:read`)
1823
+ #
1824
+ # _@param_ `output_io` — the stream to write to (should respond to `:<<`)
1825
+ #
1826
+ # _@param_ `level` — Zlib compression level (defaults to `Zlib::DEFAULT_COMPRESSION`)
1827
+ #
1828
+ # _@param_ `block_size` — The block size to use (defaults to `DEFAULT_BLOCKSIZE`)
1829
+ #
1830
+ # _@return_ — number of bytes written to `output_io`
1831
+ sig do
1832
+ params(
1833
+ input_io: IO,
1834
+ output_io: IO,
1835
+ level: Fixnum,
1836
+ block_size: Fixnum
1837
+ ).returns(Fixnum)
1838
+ end
1839
+ def self.deflate_in_blocks_and_terminate(input_io, output_io, level: Zlib::DEFAULT_COMPRESSION, block_size: DEFAULT_BLOCKSIZE); end
1840
+
1841
+ # Compress the contents of input_io into output_io, in blocks
1842
+ # of block_size. Align the parts so that they can be concatenated later.
1843
+ # Will not write the deflate end marker (\x3\x0) so more parts can be written
1844
+ # later and succesfully read back in provided the end marker wll be written.
1845
+ #
1846
+ # `output_io` can also be a {ZipKit::Streamer} to expedite ops.
1847
+ #
1848
+ # _@param_ `input_io` — the stream to read from (should respond to `:read`)
1849
+ #
1850
+ # _@param_ `output_io` — the stream to write to (should respond to `:<<`)
1851
+ #
1852
+ # _@param_ `level` — Zlib compression level (defaults to `Zlib::DEFAULT_COMPRESSION`)
1853
+ #
1854
+ # _@param_ `block_size` — The block size to use (defaults to `DEFAULT_BLOCKSIZE`)
1855
+ #
1856
+ # _@return_ — number of bytes written to `output_io`
1857
+ sig do
1858
+ params(
1859
+ input_io: IO,
1860
+ output_io: IO,
1861
+ level: Fixnum,
1862
+ block_size: Fixnum
1863
+ ).returns(Fixnum)
1864
+ end
1865
+ def self.deflate_in_blocks(input_io, output_io, level: Zlib::DEFAULT_COMPRESSION, block_size: DEFAULT_BLOCKSIZE); end
1866
+ end
1867
+
1868
+ # Helps to estimate archive sizes
1869
+ class SizeEstimator
1870
+ # Creates a new estimator with a Streamer object. Normally you should use
1871
+ # `estimate` instead an not use this method directly.
1872
+ #
1873
+ # _@param_ `streamer`
1874
+ sig { params(streamer: ZipKit::Streamer).void }
1875
+ def initialize(streamer); end
1876
+
1877
+ # Performs the estimate using fake archiving. It needs to know the sizes of the
1878
+ # entries upfront. Usage:
1879
+ #
1880
+ # expected_zip_size = SizeEstimator.estimate do | estimator |
1881
+ # estimator.add_stored_entry(filename: "file.doc", size: 898291)
1882
+ # estimator.add_deflated_entry(filename: "family.tif",
1883
+ # uncompressed_size: 89281911, compressed_size: 121908)
1884
+ # end
1885
+ #
1886
+ # _@param_ `kwargs_for_streamer_new` — Any options to pass to Streamer, see {Streamer#initialize}
1887
+ #
1888
+ # _@return_ — the size of the resulting archive, in bytes
1889
+ sig { params(kwargs_for_streamer_new: T.untyped, blk: T.proc.params(the: SizeEstimator).void).returns(Integer) }
1890
+ def self.estimate(**kwargs_for_streamer_new, &blk); end
1891
+
1892
+ # Add a fake entry to the archive, to see how big it is going to be in the end.
1893
+ #
1894
+ # data descriptor to specify size
1895
+ #
1896
+ # _@param_ `filename` — the name of the file (filenames are variable-width in the ZIP)
1897
+ #
1898
+ # _@param_ `size` — size of the uncompressed entry
1899
+ #
1900
+ # _@param_ `use_data_descriptor` — whether the entry uses a postfix
1901
+ #
1902
+ # _@return_ — self
1903
+ sig { params(filename: String, size: Fixnum, use_data_descriptor: T::Boolean).returns(T.untyped) }
1904
+ def add_stored_entry(filename:, size:, use_data_descriptor: false); end
1905
+
1906
+ # Add a fake entry to the archive, to see how big it is going to be in the end.
1907
+ #
1908
+ # _@param_ `filename` — the name of the file (filenames are variable-width in the ZIP)
1909
+ #
1910
+ # _@param_ `uncompressed_size` — size of the uncompressed entry
1911
+ #
1912
+ # _@param_ `compressed_size` — size of the compressed entry
1913
+ #
1914
+ # _@param_ `use_data_descriptor` — whether the entry uses a postfix data descriptor to specify size
1915
+ #
1916
+ # _@return_ — self
1917
+ sig do
1918
+ params(
1919
+ filename: String,
1920
+ uncompressed_size: Fixnum,
1921
+ compressed_size: Fixnum,
1922
+ use_data_descriptor: T::Boolean
1923
+ ).returns(T.untyped)
1924
+ end
1925
+ def add_deflated_entry(filename:, uncompressed_size:, compressed_size:, use_data_descriptor: false); end
1926
+
1927
+ # Add an empty directory to the archive.
1928
+ #
1929
+ # _@param_ `dirname` — the name of the directory
1930
+ #
1931
+ # _@return_ — self
1932
+ sig { params(dirname: String).returns(T.untyped) }
1933
+ def add_empty_directory_entry(dirname:); end
1934
+ end
1935
+
1936
+ # A tiny wrapper over any object that supports :<<.
1937
+ # Adds :tell and :advance_position_by. This is needed for write destinations
1938
+ # which do not respond to `#pos` or `#tell`. A lot of ZIP archive format parts
1939
+ # include "offsets in archive" - a byte offset from the start of file. Keeping
1940
+ # track of this value is what this object will do. It also allows "advancing"
1941
+ # this value if data gets written using a bypass (such as `IO#sendfile`)
1942
+ class WriteAndTell
1943
+ include ZipKit::WriteShovel
1944
+
1945
+ # sord omit - no YARD type given for "io", using untyped
1946
+ sig { params(io: T.untyped).void }
1947
+ def initialize(io); end
1948
+
1949
+ # sord omit - no YARD type given for "bytes", using untyped
1950
+ # sord omit - no YARD return type given, using untyped
1951
+ sig { params(bytes: T.untyped).returns(T.untyped) }
1952
+ def <<(bytes); end
1953
+
1954
+ # sord omit - no YARD type given for "num_bytes", using untyped
1955
+ # sord omit - no YARD return type given, using untyped
1956
+ sig { params(num_bytes: T.untyped).returns(T.untyped) }
1957
+ def advance_position_by(num_bytes); end
1958
+
1959
+ # sord omit - no YARD return type given, using untyped
1960
+ sig { returns(T.untyped) }
1961
+ def tell; end
1962
+
1963
+ # sord infer - argument name in single @param inferred as "bytes"
1964
+ # Writes the given data to the output stream. Allows the object to be used as
1965
+ # a target for `IO.copy_stream(from, to)`
1966
+ #
1967
+ # _@param_ `d` — the binary string to write (part of the uncompressed file)
1968
+ #
1969
+ # _@return_ — the number of bytes written
1970
+ sig { params(bytes: String).returns(Fixnum) }
1971
+ def write(bytes); end
1972
+ end
1973
+
1974
+ # Should be included into a Rails controller for easy ZIP output from any action.
1975
+ module RailsStreaming
1976
+ # Opens a {ZipKit::Streamer} and yields it to the caller. The output of the streamer
1977
+ # gets automatically forwarded to the Rails response stream. When the output completes,
1978
+ # the Rails response stream is going to be closed automatically.
1979
+ #
1980
+ # _@param_ `filename` — name of the file for the Content-Disposition header
1981
+ #
1982
+ # _@param_ `type` — the content type (MIME type) of the archive being output
1983
+ #
1984
+ # _@param_ `use_chunked_transfer_encoding` — whether to forcibly encode output as chunked. Normally you should not need this.
1985
+ #
1986
+ # _@param_ `zip_streamer_options` — options that will be passed to the Streamer. See {ZipKit::Streamer#initialize} for the full list of options.
1987
+ #
1988
+ # _@return_ — The output enumerator assigned to the response body
1989
+ sig do
1990
+ params(
1991
+ filename: String,
1992
+ type: String,
1993
+ use_chunked_transfer_encoding: T::Boolean,
1994
+ zip_streamer_options: T::Hash[T.untyped, T.untyped],
1995
+ zip_streaming_blk: T.proc.params(the: ZipKit::Streamer).void
1996
+ ).returns(ZipKit::OutputEnumerator)
1997
+ end
1998
+ def zip_kit_stream(filename: "download.zip", type: "application/zip", use_chunked_transfer_encoding: false, **zip_streamer_options, &zip_streaming_blk); end
1999
+ end
2000
+
2001
+ # The output enumerator makes it possible to "pull" from a ZipKit streamer
2002
+ # object instead of having it "push" writes to you. It will "stash" the block which
2003
+ # writes the ZIP archive through the streamer, and when you call `each` on the Enumerator
2004
+ # it will yield you the bytes the block writes. Since it is an enumerator you can
2005
+ # use `next` to take chunks written by the ZipKit streamer one by one. It can be very
2006
+ # convenient when you need to segment your ZIP output into bigger chunks for, say,
2007
+ # uploading them to a cloud storage provider such as S3.
2008
+ #
2009
+ # Another use of the `OutputEnumerator` is as a Rack response body - since a Rack
2010
+ # response body object must support `#each` yielding successive binary strings.
2011
+ # Which is exactly what `OutputEnumerator` does.
2012
+ #
2013
+ # The enumerator can provide you some more conveinences for HTTP output - correct streaming
2014
+ # headers and a body with chunked transfer encoding.
2015
+ #
2016
+ # iterable_zip_body = ZipKit::OutputEnumerator.new do | streamer |
2017
+ # streamer.write_file('big.csv') do |sink|
2018
+ # CSV(sink) do |csv_writer|
2019
+ # csv_writer << Person.column_names
2020
+ # Person.all.find_each do |person|
2021
+ # csv_writer << person.attributes.values
2022
+ # end
2023
+ # end
2024
+ # end
2025
+ # end
2026
+ #
2027
+ # You can grab the headers one usually needs for streaming from `#streaming_http_headers`:
2028
+ #
2029
+ # [200, iterable_zip_body.streaming_http_headers, iterable_zip_body]
2030
+ #
2031
+ # to bypass things like `Rack::ETag` and the nginx buffering.
2032
+ class OutputEnumerator
2033
+ DEFAULT_WRITE_BUFFER_SIZE = T.let(64 * 1024, T.untyped)
2034
+
2035
+ # Creates a new OutputEnumerator enumerator. The enumerator can be read from using `each`,
2036
+ # and the creation of the ZIP is in lockstep with the caller calling `each` on the returned
2037
+ # output enumerator object. This can be used when the calling program wants to stream the
2038
+ # output of the ZIP archive and throttle that output, or split it into chunks, or use it
2039
+ # as a generator.
2040
+ #
2041
+ # For example:
2042
+ #
2043
+ # # The block given to {output_enum} won't be executed immediately - rather it
2044
+ # # will only start to execute when the caller starts to read from the output
2045
+ # # by calling `each`
2046
+ # body = ::ZipKit::OutputEnumerator.new(writer: CustomWriter) do |streamer|
2047
+ # streamer.add_stored_entry(filename: 'large.tif', size: 1289894, crc32: 198210)
2048
+ # streamer << large_file.read(1024*1024) until large_file.eof?
2049
+ # ...
2050
+ # end
2051
+ #
2052
+ # body.each do |bin_string|
2053
+ # # Send the output somewhere, buffer it in a file etc.
2054
+ # # The block passed into `initialize` will only start executing once `#each`
2055
+ # # is called
2056
+ # ...
2057
+ # end
2058
+ #
2059
+ # _@param_ `kwargs_for_new` — keyword arguments for {Streamer.new}
2060
+ #
2061
+ # _@param_ `streamer_options` — options for Streamer, see {ZipKit::Streamer.new}
2062
+ #
2063
+ # _@param_ `write_buffer_size` — By default all ZipKit writes are unbuffered. For output to sockets it is beneficial to bulkify those writes so that they are roughly sized to a socket buffer chunk. This object will bulkify writes for you in this way (so `each` will yield not on every call to `<<` from the Streamer but at block size boundaries or greater). Set it to 0 for unbuffered writes.
2064
+ #
2065
+ # _@param_ `blk` — a block that will receive the Streamer object when executing. The block will not be executed immediately but only once `each` is called on the OutputEnumerator
2066
+ #
2067
+ # _@return_ — the enumerator you can read bytestrings of the ZIP from by calling `each`
2068
+ sig { params(write_buffer_size: Integer, streamer_options: T::Hash[T.untyped, T.untyped], blk: T.untyped).void }
2069
+ def initialize(write_buffer_size: DEFAULT_WRITE_BUFFER_SIZE, **streamer_options, &blk); end
2070
+
2071
+ # sord omit - no YARD return type given, using untyped
2072
+ # Executes the block given to the constructor with a {ZipKit::Streamer}
2073
+ # and passes each written chunk to the block given to the method. This allows one
2074
+ # to "take" output of the ZIP piecewise. If called without a block will return an Enumerator
2075
+ # that you can pull data from using `next`.
2076
+ #
2077
+ # **NOTE** Because the `WriteBuffer` inside this object can reuse the buffer, it is important
2078
+ # that the `String` that is yielded **either** gets consumed eagerly (written byte-by-byte somewhere, or `#dup`-ed)
2079
+ # since the write buffer will clear it after your block returns. If you expand this Enumerator
2080
+ # eagerly into an Array you might notice that a lot of the segments of your ZIP output are
2081
+ # empty - this means that you need to duplicate them.
2082
+ sig { returns(T.untyped) }
2083
+ def each; end
2084
+
2085
+ # Returns a Hash of HTTP response headers you are likely to need to have your response stream correctly.
2086
+ sig { returns(T::Hash[T.untyped, T.untyped]) }
2087
+ def streaming_http_headers; end
2088
+
2089
+ # Returns a tuple of `headers, body` - headers are a `Hash` and the body is
2090
+ # an object that can be used as a Rack response body. This method used to accept arguments
2091
+ # but will now just ignore them.
2092
+ sig { returns(T::Array[T.untyped]) }
2093
+ def to_headers_and_rack_response_body; end
2094
+ end
2095
+
2096
+ # A body wrapper that emits chunked responses, creating valid
2097
+ # Transfer-Encoding::Chunked HTTP response body. This is copied from Rack::Chunked::Body,
2098
+ # because Rack is not going to include that class after version 3.x
2099
+ # Rails has a substitute class for this inside ActionController::Streaming,
2100
+ # but that module is a private constant in the Rails codebase, and is thus
2101
+ # considered "private" from the Rails standpoint. It is not that much code to
2102
+ # carry, so we copy it into our code.
2103
+ class RackChunkedBody
2104
+ TERM = T.let("\r\n", T.untyped)
2105
+ TAIL = T.let("0#{TERM}", T.untyped)
2106
+
2107
+ # sord duck - #each looks like a duck type, replacing with untyped
2108
+ # _@param_ `body` — the enumerable that yields bytes, usually a `OutputEnumerator`
2109
+ sig { params(body: T.untyped).void }
2110
+ def initialize(body); end
2111
+
2112
+ # sord omit - no YARD return type given, using untyped
2113
+ # For each string yielded by the response body, yield
2114
+ # the element in chunked encoding - and finish off with a terminator
2115
+ sig { returns(T.untyped) }
2116
+ def each; end
2117
+ end
2118
+
2119
+ module UniquifyFilename
2120
+ # sord duck - #include? looks like a duck type, replacing with untyped
2121
+ # Makes a given filename unique by appending a (n) suffix
2122
+ # between just before the filename extension. So "file.txt" gets
2123
+ # transformed into "file (1).txt". The transformation is applied
2124
+ # repeatedly as long as the generated filename is present
2125
+ # in `while_included_in` object
2126
+ #
2127
+ # _@param_ `path` — the path to make unique
2128
+ #
2129
+ # _@param_ `while_included_in` — an object that stores the list of already used paths
2130
+ #
2131
+ # _@return_ — the path as is, or with the suffix required to make it unique
2132
+ sig { params(path: String, while_included_in: T.untyped).returns(String) }
2133
+ def self.call(path, while_included_in); end
2134
+ end
2135
+
2136
+ # Contains a file handle which can be closed once the response finishes sending.
2137
+ # It supports `to_path` so that `Rack::Sendfile` can intercept it.
2138
+ # This class is deprecated and is going to be removed in zip_kit 7.x
2139
+ # @api deprecated
2140
+ class RackTempfileBody
2141
+ TEMPFILE_NAME_PREFIX = T.let("zip-tricks-tf-body-", T.untyped)
2142
+
2143
+ # sord omit - no YARD type given for "env", using untyped
2144
+ # sord duck - #each looks like a duck type, replacing with untyped
2145
+ # _@param_ `body` — the enumerable that yields bytes, usually a `OutputEnumerator`. The `body` will be read in full immediately and closed.
2146
+ sig { params(env: T.untyped, body: T.untyped).void }
2147
+ def initialize(env, body); end
2148
+
2149
+ # Returns the size of the contained `Tempfile` so that a correct
2150
+ # Content-Length header can be set
2151
+ sig { returns(Integer) }
2152
+ def size; end
2153
+
2154
+ # Returns the path to the `Tempfile`, so that Rack::Sendfile can send this response
2155
+ # using the downstream webserver
2156
+ sig { returns(String) }
2157
+ def to_path; end
2158
+
2159
+ # Stream the file's contents if `Rack::Sendfile` isn't present.
2160
+ sig { void }
2161
+ def each; end
2162
+
2163
+ # sord omit - no YARD return type given, using untyped
2164
+ sig { returns(T.untyped) }
2165
+ def flush; end
2166
+
2167
+ # sord omit - no YARD type given for :tempfile, using untyped
2168
+ sig { returns(T.untyped) }
2169
+ attr_reader :tempfile
2170
+ end
2171
+ end