ziperu 0.1.1-arm-linux-musl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +24 -0
- data/LICENSE-rubyzip.txt +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +276 -0
- data/lib/rubyzip.rb +4 -0
- data/lib/zip/central_directory.rb +5 -0
- data/lib/zip/compressor.rb +5 -0
- data/lib/zip/constants.rb +5 -0
- data/lib/zip/crypto/aes_encryption.rb +5 -0
- data/lib/zip/crypto/decrypted_io.rb +5 -0
- data/lib/zip/crypto/encryption.rb +5 -0
- data/lib/zip/crypto/null_encryption.rb +5 -0
- data/lib/zip/crypto/traditional_encryption.rb +5 -0
- data/lib/zip/crypto.rb +5 -0
- data/lib/zip/decompressor.rb +5 -0
- data/lib/zip/deflater.rb +5 -0
- data/lib/zip/dirtyable.rb +5 -0
- data/lib/zip/dos_time.rb +5 -0
- data/lib/zip/entry.rb +5 -0
- data/lib/zip/entry_set.rb +5 -0
- data/lib/zip/errors.rb +5 -0
- data/lib/zip/extra_field/aes.rb +5 -0
- data/lib/zip/extra_field/generic.rb +5 -0
- data/lib/zip/extra_field/ntfs.rb +5 -0
- data/lib/zip/extra_field/old_unix.rb +5 -0
- data/lib/zip/extra_field/universal_time.rb +5 -0
- data/lib/zip/extra_field/unix.rb +5 -0
- data/lib/zip/extra_field/unknown.rb +5 -0
- data/lib/zip/extra_field/zip64.rb +5 -0
- data/lib/zip/extra_field.rb +5 -0
- data/lib/zip/file.rb +5 -0
- data/lib/zip/file_split.rb +5 -0
- data/lib/zip/filesystem/dir.rb +5 -0
- data/lib/zip/filesystem/directory_iterator.rb +5 -0
- data/lib/zip/filesystem/file.rb +5 -0
- data/lib/zip/filesystem/file_stat.rb +5 -0
- data/lib/zip/filesystem/zip_file_name_mapper.rb +5 -0
- data/lib/zip/filesystem.rb +5 -0
- data/lib/zip/inflater.rb +5 -0
- data/lib/zip/input_stream.rb +5 -0
- data/lib/zip/ioextras/abstract_input_stream.rb +5 -0
- data/lib/zip/ioextras/abstract_output_stream.rb +5 -0
- data/lib/zip/ioextras/fake_io.rb +5 -0
- data/lib/zip/ioextras.rb +5 -0
- data/lib/zip/null_compressor.rb +5 -0
- data/lib/zip/null_decompressor.rb +5 -0
- data/lib/zip/null_input_stream.rb +5 -0
- data/lib/zip/output_stream.rb +5 -0
- data/lib/zip/pass_thru_compressor.rb +5 -0
- data/lib/zip/pass_thru_decompressor.rb +5 -0
- data/lib/zip/streamable_directory.rb +5 -0
- data/lib/zip/streamable_stream.rb +5 -0
- data/lib/zip/version.rb +5 -0
- data/lib/zip.rb +19 -0
- data/lib/ziperu/3.1/ziperu.so +0 -0
- data/lib/ziperu/3.2/ziperu.so +0 -0
- data/lib/ziperu/3.3/ziperu.so +0 -0
- data/lib/ziperu/3.4/ziperu.so +0 -0
- data/lib/ziperu/4.0/ziperu.so +0 -0
- data/lib/ziperu/central_directory.rb +136 -0
- data/lib/ziperu/compressor.rb +14 -0
- data/lib/ziperu/constants.rb +148 -0
- data/lib/ziperu/crypto.rb +72 -0
- data/lib/ziperu/decompressor.rb +35 -0
- data/lib/ziperu/deflater.rb +41 -0
- data/lib/ziperu/dirtyable.rb +48 -0
- data/lib/ziperu/dos_time.rb +87 -0
- data/lib/ziperu/entry.rb +760 -0
- data/lib/ziperu/entry_set.rb +147 -0
- data/lib/ziperu/errors.rb +154 -0
- data/lib/ziperu/extra_field/aes.rb +52 -0
- data/lib/ziperu/extra_field/generic.rb +45 -0
- data/lib/ziperu/extra_field/ntfs.rb +98 -0
- data/lib/ziperu/extra_field/old_unix.rb +52 -0
- data/lib/ziperu/extra_field/universal_time.rb +81 -0
- data/lib/ziperu/extra_field/unix.rb +45 -0
- data/lib/ziperu/extra_field/unknown.rb +37 -0
- data/lib/ziperu/extra_field/zip64.rb +86 -0
- data/lib/ziperu/extra_field.rb +109 -0
- data/lib/ziperu/file.rb +614 -0
- data/lib/ziperu/file_split.rb +93 -0
- data/lib/ziperu/filesystem/dir.rb +88 -0
- data/lib/ziperu/filesystem/directory_iterator.rb +50 -0
- data/lib/ziperu/filesystem/file.rb +265 -0
- data/lib/ziperu/filesystem/file_stat.rb +112 -0
- data/lib/ziperu/filesystem/zip_file_name_mapper.rb +83 -0
- data/lib/ziperu/filesystem.rb +76 -0
- data/lib/ziperu/inflater.rb +60 -0
- data/lib/ziperu/input_stream.rb +296 -0
- data/lib/ziperu/ioextras/abstract_input_stream.rb +137 -0
- data/lib/ziperu/ioextras/abstract_output_stream.rb +57 -0
- data/lib/ziperu/ioextras/fake_io.rb +41 -0
- data/lib/ziperu/ioextras.rb +42 -0
- data/lib/ziperu/null_compressor.rb +20 -0
- data/lib/ziperu/null_decompressor.rb +25 -0
- data/lib/ziperu/null_input_stream.rb +55 -0
- data/lib/ziperu/output_stream.rb +223 -0
- data/lib/ziperu/pass_thru_compressor.rb +29 -0
- data/lib/ziperu/pass_thru_decompressor.rb +37 -0
- data/lib/ziperu/streamable_directory.rb +15 -0
- data/lib/ziperu/streamable_stream.rb +59 -0
- data/lib/ziperu/version.rb +7 -0
- data/lib/ziperu.rb +116 -0
- metadata +156 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4227ebcba259148f28d45baf22c1fd0d190a5e4ee7f278d6d2303161b026ee2b
|
|
4
|
+
data.tar.gz: beeb11e678df912f016e9de932541bc3e6d27ac89f0441daafe8c24923a43bb3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 793d68f2085a89c2d4d0992618c814638214c58ab060ef387b133fa7a2378f9d3b1174c4fa565b4664554abd3405a65f5f7edfcdb8961f4abd78a11c58012e09
|
|
7
|
+
data.tar.gz: '0947e46565a8925a4f5374839a33b186d7f443b5b2ce8a4fdcfdab70ef8f8689c32a6457d8e0c4730b84b2d9cf6e64a137282a1f675a309784ed4abe226fa6e9'
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
First release, as far as rubygems.org is concerned: 0.1.0 was tagged but
|
|
6
|
+
its precompiled gems were built for Ruby 3.3 and later only, so it was
|
|
7
|
+
never published. The native gems now cover every Ruby the gemspec
|
|
8
|
+
promises (3.1 through 4.0).
|
|
9
|
+
|
|
10
|
+
## 0.1.0
|
|
11
|
+
|
|
12
|
+
Not published.
|
|
13
|
+
|
|
14
|
+
- rubyzip 3.x API (`Ziperu::File`, `Entry`, `InputStream`, `OutputStream`,
|
|
15
|
+
`FileSystem`) on a Rust core; `require "zip"` aliases `Zip = Ziperu`.
|
|
16
|
+
- Reads Stored, Deflate, Deflate64, bzip2, zstd, LZMA, XZ, PPMd and the
|
|
17
|
+
PKZIP 1.x methods; writes Stored, Deflate, bzip2 and zstd; AES and
|
|
18
|
+
ZipCrypto encryption both ways.
|
|
19
|
+
- Precompiled gems for Linux, macOS and Windows; no toolchain needed.
|
|
20
|
+
- Beyond rubyzip: archive limits (`Ziperu.max_entries`,
|
|
21
|
+
`Ziperu.max_total_size`), `File#verify`, symlink entries with opt-in
|
|
22
|
+
extraction, progress callbacks, native `extract_all`, streaming reads
|
|
23
|
+
and writes through any IO, UTF-8 names when the archive flags them.
|
|
24
|
+
- See the README for the deliberate differences from rubyzip.
|
data/LICENSE-rubyzip.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2002-2026, The Rubyzip Developers
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maor Caplan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# ziperu
|
|
2
|
+
|
|
3
|
+
A drop-in replacement for [rubyzip](https://github.com/rubyzip/rubyzip) with a
|
|
4
|
+
Rust core. Same `Ziperu::File` / `Ziperu::Entry` / `Ziperu::InputStream` /
|
|
5
|
+
`Ziperu::OutputStream` API, backed by the [`zip`](https://github.com/zip-rs/zip2)
|
|
6
|
+
crate through [Magnus](https://github.com/matsadler/magnus).
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
gem "ziperu" # in the Gemfile, in place of gem "rubyzip"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Precompiled gems are published for Linux, macOS and Windows, so no Rust
|
|
13
|
+
toolchain is needed to install. Changes are listed in
|
|
14
|
+
[CHANGELOG.md](CHANGELOG.md).
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
require "ziperu" # or require "zip" to get the Zip alias
|
|
18
|
+
|
|
19
|
+
Ziperu::File.open("archive.zip") do |zf|
|
|
20
|
+
zf.each { |entry| puts "#{entry.name} (#{entry.size} bytes)" }
|
|
21
|
+
puts zf.read("README.txt")
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Status: early.** `Ziperu::File` (read, `extract`/`extract_all`, `add`,
|
|
26
|
+
`add_recursive`, `get_output_stream`, `remove`, `rename`, `replace`,
|
|
27
|
+
`mkdir`, `commit`, `write_buffer`/`open_buffer`), `Ziperu::InputStream` and
|
|
28
|
+
`Ziperu::OutputStream` (with AES and ZipCrypto) and `Ziperu::FileSystem`
|
|
29
|
+
work; see [COMPAT.md](COMPAT.md) for what rubyzip's own test suite says.
|
|
30
|
+
Tracks the rubyzip 3.x API (`Ziperu::RUBYZIP_COMPAT_VERSION`).
|
|
31
|
+
|
|
32
|
+
### Migrating from rubyzip
|
|
33
|
+
|
|
34
|
+
The library lives in the `Ziperu` namespace. `require "zip"` loads it and
|
|
35
|
+
sets `Zip = Ziperu`, so existing code keeps working with a one-line change
|
|
36
|
+
to the Gemfile:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
gem "ziperu" # instead of gem "rubyzip"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If rubyzip has to stay installed during the migration (another gem depends
|
|
43
|
+
on it, say), the two cannot share the `Zip` name: `require "zip"` raises a
|
|
44
|
+
`LoadError` naming the file that already defined `Zip`. Use `require "ziperu"`
|
|
45
|
+
and `Ziperu::` for the code you have switched, and leave `Zip::` to rubyzip
|
|
46
|
+
until it goes. Exception classes follow the namespace, so rescue
|
|
47
|
+
`Ziperu::Error` (or `Zip::Error`, which is the same class once aliased).
|
|
48
|
+
|
|
49
|
+
### Deliberate differences from rubyzip
|
|
50
|
+
|
|
51
|
+
- `Ziperu::InputStream` locates entries through the central directory instead
|
|
52
|
+
of walking local headers, so archives written in streaming mode (general
|
|
53
|
+
purpose bit 3, e.g. by macOS Archive Utility) read normally instead of
|
|
54
|
+
raising `Ziperu::StreamingError`.
|
|
55
|
+
- Every read path stops at the entry's declared size, so a header that lies
|
|
56
|
+
can't inflate into a zip bomb; a `Ziperu::EntrySizeError` is raised instead.
|
|
57
|
+
(rubyzip only checks this in `extract`, not in `InputStream`.)
|
|
58
|
+
`Ziperu.validate_entry_sizes = false` restores rubyzip's behaviour of
|
|
59
|
+
extracting the full data with a warning.
|
|
60
|
+
- ZipCrypto ("traditional") encryption is written by ziperu itself (the
|
|
61
|
+
crate keeps that broken cipher read-only), with the same header layout
|
|
62
|
+
rubyzip produces: general purpose bits 0 and 3 set, the check byte from
|
|
63
|
+
the DOS time. Only Stored and Deflate entries can be encrypted this way,
|
|
64
|
+
and a committed `Ziperu::File` never encrypts. `AESEncrypter` is the
|
|
65
|
+
better choice wherever the consumer can open AES.
|
|
66
|
+
- Archives compressed with bzip2, Deflate64, zstd, LZMA, XZ, PPMd or the
|
|
67
|
+
PKZIP 1.x methods (Shrink, Reduce, Implode) read normally; rubyzip raises
|
|
68
|
+
`Ziperu::CompressionMethodError` for anything but Stored and Deflate.
|
|
69
|
+
Entries can also be *written* with bzip2 or zstd
|
|
70
|
+
(`put_next_entry(name, "", ExtraField.new, Ziperu::Entry::ZSTD, 3)`), which
|
|
71
|
+
rubyzip, Windows Explorer and Java cannot read, so those are opt-in.
|
|
72
|
+
- AES entries of 20 bytes or more are written as AE-1 (with a real CRC in the
|
|
73
|
+
header) rather than rubyzip's always-AE-2 with a zero CRC; both are read
|
|
74
|
+
by every AES-aware tool, and AE-1 adds an integrity check.
|
|
75
|
+
- Entry names and comments come back as UTF-8 strings when the entry's
|
|
76
|
+
language-encoding flag is set (which is how most tools, and ziperu
|
|
77
|
+
itself, write non-ASCII names); rubyzip returns the raw bytes as binary
|
|
78
|
+
strings. Entries without the flag, or whose flagged name is not valid
|
|
79
|
+
UTF-8, stay binary as in rubyzip, and `Ziperu.force_entry_names_encoding`
|
|
80
|
+
overrides both. A lookup (`find_entry`, `get_entry`, `read`) accepts the
|
|
81
|
+
name in any encoding with the same bytes, so neither `.b` nor
|
|
82
|
+
`force_encoding` is needed to find an entry.
|
|
83
|
+
- Writing two entries with the same name to one `OutputStream` raises
|
|
84
|
+
`Ziperu::Error`; rubyzip writes both and the last one wins.
|
|
85
|
+
- A CRC mismatch while reading raises `Ziperu::DecompressionError`;
|
|
86
|
+
rubyzip hands back the bytes.
|
|
87
|
+
- Every written file entry carries Unix permissions in its external
|
|
88
|
+
attributes (0644 by default), and such permissions are read back whatever
|
|
89
|
+
the entry's host system byte says; rubyzip leaves `unix_perms` nil for
|
|
90
|
+
entries written on Windows. Setuid, setgid and sticky bits are not
|
|
91
|
+
written, and a symlink added from disk is written as a regular file
|
|
92
|
+
holding the link target.
|
|
93
|
+
- Editing an existing entry's comment, time or extra fields and committing
|
|
94
|
+
rewrites that entry's data under a fresh header; a rename or a chmod on
|
|
95
|
+
its own keeps the byte-for-byte copy. So does an untouched entry, unless
|
|
96
|
+
it carries an extended-timestamp, NTFS or Info-ZIP unix extra field, which
|
|
97
|
+
the native copy would not carry over intact. An encrypted entry is always
|
|
98
|
+
copied as is: its name and permissions can change, and any other edit
|
|
99
|
+
raises `Ziperu::Error` at commit rather than vanishing, which is what
|
|
100
|
+
rubyzip does for compression methods it cannot write.
|
|
101
|
+
- Several records with the same name in one archive (Python's `zipfile`
|
|
102
|
+
writes such archives, with a warning): `Ziperu::File` lists every record
|
|
103
|
+
and each reads its own data, where rubyzip keeps only the last. A lookup
|
|
104
|
+
by name (`find_entry`, `read`, `extract_all`) finds the last, as in
|
|
105
|
+
rubyzip, and so does a commit, which writes one record per name; a
|
|
106
|
+
shadowed record given a new name before the commit is written afresh.
|
|
107
|
+
`Ziperu::InputStream` yields every record, as rubyzip's does.
|
|
108
|
+
- `Ziperu::InputStream` over something that is not a zip archive at all
|
|
109
|
+
returns `nil` from the first `get_next_entry`, as rubyzip does; a corrupt
|
|
110
|
+
archive raises `Ziperu::Error` from `Ziperu::File.open` in both.
|
|
111
|
+
- `Zip::VERSION` is ziperu's own version, not rubyzip's. Code that gates on
|
|
112
|
+
it (`Gem::Version.new(Zip::VERSION) >= "3"`) should check
|
|
113
|
+
`Ziperu::RUBYZIP_COMPAT_VERSION` instead.
|
|
114
|
+
- An open `Ziperu::File` holds its file open until `close`. On Windows a
|
|
115
|
+
commit by *another* `Ziperu::File` on the same path therefore moves the
|
|
116
|
+
old archive aside before putting the new one in place, and removes it
|
|
117
|
+
once nothing holds it; the first instance keeps reading the old bytes,
|
|
118
|
+
as it would on Unix. rubyzip closes the file between operations.
|
|
119
|
+
- Using one `InputStream`, `OutputStream` or `Zip::File` from two threads
|
|
120
|
+
at the same time raises `ThreadError` ("in use by another thread")
|
|
121
|
+
rather than corrupting state; rubyzip's objects are not thread-safe
|
|
122
|
+
either, they just fail less loudly.
|
|
123
|
+
- Class names are `Ziperu::...` even when loaded through the `Zip` alias:
|
|
124
|
+
`Zip::Entry.name` is `"Ziperu::Entry"`, and so are `inspect` output and
|
|
125
|
+
exception messages. Comparisons against the string `"Zip::Entry"` need
|
|
126
|
+
updating; comparisons against the constant do not.
|
|
127
|
+
- Extraction never creates symlinks unless asked to (see below; rubyzip 3.x
|
|
128
|
+
never does) and refuses paths that resolve outside the destination
|
|
129
|
+
directory, checking the resolved absolute path with a trailing separator,
|
|
130
|
+
the fix for CVE-2026-85396.
|
|
131
|
+
|
|
132
|
+
### Beyond rubyzip
|
|
133
|
+
|
|
134
|
+
Everything here is additive: code that does not use it behaves as above.
|
|
135
|
+
|
|
136
|
+
- **Archive limits.** `Ziperu.max_entries` and `Ziperu.max_total_size`
|
|
137
|
+
(declared uncompressed bytes) make `Ziperu::File.open` and
|
|
138
|
+
`Ziperu::InputStream.open` raise `Ziperu::LimitError` before any entry is
|
|
139
|
+
read. With `validate_entry_sizes` on (the default) no entry inflates past
|
|
140
|
+
its declared size, so the total is a real bound on what an extraction can
|
|
141
|
+
write.
|
|
142
|
+
- **Integrity check.** `Ziperu::File#verify` inflates every entry to nowhere
|
|
143
|
+
so the CRC of each is checked, releasing the GVL meanwhile, and returns a
|
|
144
|
+
Hash of failing entries to the exception each raised (empty when sound).
|
|
145
|
+
Encrypted entries are checked when a `decrypter:` is given.
|
|
146
|
+
- **Symlink entries.** `add` of a symlink stores a link entry (target as
|
|
147
|
+
content, `S_IFLNK` in the attributes) instead of a copy of the target.
|
|
148
|
+
`extract_all(dir, symlinks: :create)` and `extract(..., symlinks: :create)`
|
|
149
|
+
create such links, after the links' targets have been checked to resolve
|
|
150
|
+
inside the destination; links are created after everything else so no
|
|
151
|
+
file is written through one. The default stays `:skip`, with rubyzip's
|
|
152
|
+
warning. `add_recursive` still skips links on disk.
|
|
153
|
+
- **Progress.** `extract_all(dir, progress: ->(entry, done, total) {})` and
|
|
154
|
+
`add_recursive(dir, progress: ->(entry, done, nil) {})` report after each
|
|
155
|
+
entry; `extract_all` keeps its native fast path and reports per batch.
|
|
156
|
+
- **More compression methods.** bzip2 and zstd on write; LZMA, XZ, PPMd,
|
|
157
|
+
Deflate64 and the PKZIP 1.x methods on read (see above).
|
|
158
|
+
- **Streaming reads and writes through any IO.** `open_buffer(io)` reads an
|
|
159
|
+
IO lazily and `write_buffer(io)` writes entry by entry, so neither holds
|
|
160
|
+
the archive in memory.
|
|
161
|
+
|
|
162
|
+
## Why
|
|
163
|
+
|
|
164
|
+
rubyzip is pure Ruby and does per-entry work in Ruby objects. ziperu parses
|
|
165
|
+
the central directory once in Rust and hands Ruby a flat metadata dump, reads
|
|
166
|
+
entries in large chunks, copies unchanged entries byte-for-byte on `commit`,
|
|
167
|
+
and releases the GVL while inflating from a file or String buffer and while
|
|
168
|
+
deflating, so other Ruby threads keep running. A StringIO is read from its
|
|
169
|
+
string directly; an archive opened from any other IO object
|
|
170
|
+
(`open_buffer(io)`, `InputStream.open(io)`) is read through it lazily,
|
|
171
|
+
under the GVL; a pipe is read to its end first. The intent is
|
|
172
|
+
precompiled gems for every platform rubyzip runs on, zero runtime dependencies,
|
|
173
|
+
and stricter defaults on path traversal and declared-size checks.
|
|
174
|
+
|
|
175
|
+
## Development
|
|
176
|
+
|
|
177
|
+
Needs Ruby >= 3.1 and Rust >= 1.88.
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
bundle install
|
|
181
|
+
bundle exec rake compile test
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Windows
|
|
185
|
+
|
|
186
|
+
RubyInstaller is MinGW-based, so the extension builds for the GNU Rust
|
|
187
|
+
target and bindgen needs clang's resource headers. Once:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
winget install RubyInstallerTeam.RubyWithDevKit.3.4
|
|
191
|
+
rustup target add x86_64-pc-windows-gnu
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
then in each shell:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
. .\bin\windows-env.ps1
|
|
198
|
+
bundle exec rake compile test
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### rubyzip compatibility
|
|
202
|
+
|
|
203
|
+
`rake compat` downloads rubyzip's source into `tmp/`, generates its fixtures
|
|
204
|
+
with the real rubyzip, then runs rubyzip's own test suite with
|
|
205
|
+
`require "zip"` resolving to this gem, so it also proves the `Zip` alias is
|
|
206
|
+
complete. The per-file scorecard is written to
|
|
207
|
+
[COMPAT.md](COMPAT.md); per-file logs land in `tmp/compat-logs/`. Use
|
|
208
|
+
`ONLY=input_stream rake compat` to run a subset.
|
|
209
|
+
|
|
210
|
+
### Fuzzing
|
|
211
|
+
|
|
212
|
+
`rake fuzz` runs [fuzz/differential.rb](fuzz/differential.rb): random
|
|
213
|
+
archives go through ziperu and Python's `zipfile` (plus rubyzip, once
|
|
214
|
+
`rake compat:fetch` has run) in both directions, random edits are made
|
|
215
|
+
through the `Zip::File` API and checked by the oracles, and corrupted
|
|
216
|
+
archives must fail with ziperu's own error classes while agreeing with the
|
|
217
|
+
oracle on anything both still accept. CI runs three minutes of it per push
|
|
218
|
+
with a fresh seed; a failure is saved under `tmp/fuzz/failures/` with the
|
|
219
|
+
seed that reproduces it (`SEED=n ITERATIONS=m rake fuzz`).
|
|
220
|
+
|
|
221
|
+
There is deliberately no cargo-fuzz harness: the Rust layer is thin
|
|
222
|
+
wrappers over the `zip` crate, which has its own fuzzing, and the code
|
|
223
|
+
that is ours (entry ownership, the chunked reader, the Ruby IO source) is
|
|
224
|
+
only reachable through the Ruby API, which is what the differential fuzzer
|
|
225
|
+
drives.
|
|
226
|
+
|
|
227
|
+
### Benchmarks
|
|
228
|
+
|
|
229
|
+
`rake bench` runs [bench/bench.rb](bench/bench.rb) with rubyzip and ziperu
|
|
230
|
+
loaded side by side. On one Windows machine (Ruby 3.4, median of 5):
|
|
231
|
+
|
|
232
|
+
| scenario | rubyzip 3.7.0 | ziperu 0.1.0 | speedup |
|
|
233
|
+
|---|---:|---:|---:|
|
|
234
|
+
| open and list 10000 entries | 298 ms | 130 ms | 2.3x |
|
|
235
|
+
| read 1000 x 8 KB deflated entries | 196 ms | 23 ms | 8.4x |
|
|
236
|
+
| InputStream over 1000 entries | 111 ms | 64 ms | 1.7x |
|
|
237
|
+
| inflate one 64 MB entry | 106 ms | 54 ms | 2.0x |
|
|
238
|
+
| extract_all 1000 entries | 984 ms | 459 ms | 2.1x |
|
|
239
|
+
| write 1000 x 8 KB entries (OutputStream) | 89 ms | 39 ms | 2.3x |
|
|
240
|
+
| deflate one 64 MB entry (OutputStream) | 245 ms | 58 ms | 4.3x |
|
|
241
|
+
| open_buffer(String) and read 1000 entries | 69 ms | 15 ms | 4.5x |
|
|
242
|
+
| Zip::File add 100 files and commit | 40 ms | 33 ms | 1.2x |
|
|
243
|
+
|
|
244
|
+
Inflating and deflating are where the Rust core pays off. Listing a large
|
|
245
|
+
central directory is dominated by building Ruby `Entry` objects, which both
|
|
246
|
+
libraries do in Ruby. `extract_all` writes every entry in one native call
|
|
247
|
+
(one open per file, with its mtime set on that handle) and is left bounded
|
|
248
|
+
by file creation itself, which is slow on Windows.
|
|
249
|
+
|
|
250
|
+
### Releasing
|
|
251
|
+
|
|
252
|
+
A `v*` tag runs [release.yml](.github/workflows/release.yml): the source
|
|
253
|
+
gem plus one precompiled gem per platform (built with
|
|
254
|
+
[oxidize-rb/actions/cross-gem](https://github.com/oxidize-rb/actions), each
|
|
255
|
+
holding the extension for every stable Ruby), a smoke test that installs
|
|
256
|
+
the right gem on Linux, macOS and Windows without a Rust toolchain, and a
|
|
257
|
+
draft GitHub release with the gems attached. Pushing to rubygems.org is a
|
|
258
|
+
manual `gem push` from that draft.
|
|
259
|
+
|
|
260
|
+
## Layout
|
|
261
|
+
|
|
262
|
+
- `ext/ziperu/` — the Rust extension (`Ziperu::Native::*`): a few primitive,
|
|
263
|
+
positional-argument methods.
|
|
264
|
+
- `lib/ziperu/` — the rubyzip-shaped API in Ruby: kwargs, blocks, `Enumerable`,
|
|
265
|
+
the error hierarchy, extra-field parsing.
|
|
266
|
+
- `test/` — minitest; `test/data/` holds fixture archives.
|
|
267
|
+
- `fuzz/` — the differential fuzzer and its Python and rubyzip oracles.
|
|
268
|
+
- `bench/` — the rubyzip comparison benchmark.
|
|
269
|
+
|
|
270
|
+
## License
|
|
271
|
+
|
|
272
|
+
MIT, see [LICENSE.txt](LICENSE.txt). The Ruby layer ports a good part of
|
|
273
|
+
rubyzip (the extra-field, filesystem, DOS time, entry-set and split code,
|
|
274
|
+
and the test fixtures under `test/data/rubyzip/`), which is BSD-2-Clause;
|
|
275
|
+
its notice is in [LICENSE-rubyzip.txt](LICENSE-rubyzip.txt) and the ported
|
|
276
|
+
files say so at the top.
|
data/lib/rubyzip.rb
ADDED
data/lib/zip/crypto.rb
ADDED
data/lib/zip/deflater.rb
ADDED
data/lib/zip/dos_time.rb
ADDED
data/lib/zip/entry.rb
ADDED
data/lib/zip/errors.rb
ADDED
data/lib/zip/file.rb
ADDED
data/lib/zip/inflater.rb
ADDED
data/lib/zip/ioextras.rb
ADDED