zip_kit 6.4.1 → 6.4.2
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 +4 -4
- data/CHANGELOG.md +8 -0
- data/CODE_OF_CONDUCT.md +12 -8
- data/CONTRIBUTING.md +56 -120
- data/lib/zip_kit/file_reader.rb +91 -62
- data/lib/zip_kit/output_enumerator.rb +9 -5
- data/lib/zip_kit/rails_streaming.rb +7 -0
- data/lib/zip_kit/version.rb +1 -1
- data/lib/zip_kit/zip_writer.rb +207 -220
- data/rbi/zip_kit.rbi +63 -43
- data/rbi/zip_kit.rbs +53 -30
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c94f6db106a2c4fb28f882cdbaf3411a2fb4142a3691f337fd3adbefc8cf566b
|
|
4
|
+
data.tar.gz: e5eef7ee71cb2a2b7c5a091399308f97ff69e9f64d350540de9fdf908e77da08
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 11236fe274f019b9f4c0a0c90814578ed61f074d911dc2c110289455507e6728c0b3e9bd8e9f820f06bfd3075cb1136d5bd6ea6812b0b3fa22ad9a674904555e
|
|
7
|
+
data.tar.gz: cdf1dfe461549d4df702a7b058e0fce429102edf1a1721df2c7ce769d4dfa67fc04e7d6cd8576427ded83e9529a818c1fa4104b5bab81a883882fb9565517829
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## Unreleased
|
|
2
2
|
|
|
3
|
+
## 6.4.2
|
|
4
|
+
|
|
5
|
+
* Write every ZIP record with a single `Array#pack` and a single `<<` to the destination, instead of one per field. Archives with lots of entries get written about 1.5x faster.
|
|
6
|
+
* Unpack the fixed fields of local and central directory headers in one go in `FileReader`, and read 4KB upfront for a local file header instead of the maximum possible 128KB. `FileReader.read_zip_structure` with local headers gets about 5x faster on archives with many entries, and parsing the central directory alone about 2x.
|
|
7
|
+
* Fix `ZipWriter` converting the given `mtime` to UTC in place. The local file header got the DOS time in local time and the central directory header got it in UTC, and the caller was left with a Time object in UTC.
|
|
8
|
+
* Remove the `C_CHAR` and `C_INT4` constants from `ZipWriter`, which were never meant to be public (the other packspec constants were private already).
|
|
9
|
+
* Add `"cache-control" => "no-transform"` to `OutputEnumerator.streaming_http_headers`, and add `no-transform` to the `Cache-Control` of the response in `zip_kit_stream` (keeping whatever the controller has set there). `Rack::Deflater` only leaves a response alone if it has `no-transform` or a content encoding other than `identity`, so the `content-encoding: identity` we were sending did not stop it - it gzipped the ZIP all the same, and with `sync: false` buffered all of it to do so. If you replace our `cache-control` with your own, keep `no-transform` in it.
|
|
10
|
+
|
|
3
11
|
## 6.4.1
|
|
4
12
|
|
|
5
13
|
* Use lowercase names for the HTTP headers ZipKit produces, since Rack 3 requires them (`Rack::Lint` rejects uppercase header names). `OutputEnumerator.streaming_http_headers` now returns `"content-type"`, `"last-modified"`, `"content-encoding"` and `"x-accel-buffering"`. This is a breaking change if you look those headers up or override them in the returned Hash using the capitalised names: `headers["Content-Type"]` is now `nil`, and `headers["Content-Type"] = "application/epub+zip"` adds a second content type instead of replacing ours. Use the lowercase names instead. `zip_kit_stream` in Rails is not affected: it now merges our headers into the response regardless of case, so headers Rails sets itself (like the content type) still take precedence and do not get sent twice.
|
data/CODE_OF_CONDUCT.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Relationship to CONTRIBUTING
|
|
4
|
+
|
|
5
|
+
This Code of Conduct works together with [CONTRIBUTING.md](CONTRIBUTING.md). Where the two differ, CONTRIBUTING.md takes precedence.
|
|
2
6
|
|
|
3
7
|
## Our Pledge
|
|
4
8
|
|
|
@@ -24,23 +28,23 @@ Examples of unacceptable behavior by participants include:
|
|
|
24
28
|
|
|
25
29
|
## Our Responsibilities
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
The maintainer is responsible for clarifying the standards of acceptable behavior and is expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
The maintainer has the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviours that the maintainer deems inappropriate, threatening, offensive, or harmful.
|
|
30
34
|
|
|
31
35
|
## Scope
|
|
32
36
|
|
|
33
|
-
This Code of Conduct applies
|
|
37
|
+
This Code of Conduct applies within project spaces: the repository, its issues, pull requests and discussions, and any other channels the project runs.
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
Conduct outside project spaces is not considered, either for or against anyone, as described in [What happens elsewhere stays elsewhere](CONTRIBUTING.md#what-happens-elsewhere-stays-elsewhere).
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
## Enforcement
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the maintainer at me@julik.nl. The maintainer will review and investigate all complaints, and will respond in a way the maintainer deems appropriate to the circumstances. The maintainer is obligated to maintain confidentiality with regard to the reporter of an incident. Enforcement decisions are final.
|
|
40
44
|
|
|
41
45
|
## Attribution
|
|
42
46
|
|
|
43
|
-
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
|
47
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, with modifications, available at [http://contributor-covenant.org/version/1/4][version]
|
|
44
48
|
|
|
45
49
|
[homepage]: http://contributor-covenant.org
|
|
46
50
|
[version]: http://contributor-covenant.org/version/1/4/
|
data/CONTRIBUTING.md
CHANGED
|
@@ -1,153 +1,89 @@
|
|
|
1
1
|
# Contributing to zip_kit
|
|
2
2
|
|
|
3
|
-
Please take a moment to review this document
|
|
4
|
-
|
|
3
|
+
Contributions are welcome! Please take a moment to review this document so that
|
|
4
|
+
contributing stays easy and effective for everyone involved.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
the developers managing and developing this open source project. In return,
|
|
8
|
-
they should reciprocate that respect in addressing your issue or assessing
|
|
9
|
-
patches and features.
|
|
6
|
+
I'd especially love to get:
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
* Reports of archives that a specific unarchiver refuses to open or extracts incorrectly
|
|
9
|
+
* Performance work
|
|
10
|
+
* Documentation fixes
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
## What do I need to know to help?
|
|
14
13
|
|
|
15
|
-
If you
|
|
14
|
+
If you would like to learn more about the technologies zip_kit builds on, check out the (non-exhaustive) list below. You can also get in touch with me via an issue or email.
|
|
16
15
|
|
|
17
16
|
- [ruby](https://ruby-doc.org)
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
|
|
22
|
-
# How do I make a contribution?
|
|
23
|
-
|
|
24
|
-
## Using the issue tracker
|
|
25
|
-
|
|
26
|
-
The issue tracker is the preferred channel for [bug reports](#bug-reports),
|
|
27
|
-
[feature requests](#feature-requests) and [submitting pull
|
|
28
|
-
requests](#pull-requests), but please respect the following restrictions:
|
|
29
|
-
|
|
30
|
-
* Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others. Adhere to the principles set out in the [Code of Conduct](https://github.com/WeTransfer/zip_kit/blob/main/CODE_OF_CONDUCT.md).
|
|
31
|
-
|
|
32
|
-
## Bug reports
|
|
33
|
-
|
|
34
|
-
A bug is a _demonstrable problem_ that is caused by code in the repository.
|
|
35
|
-
|
|
36
|
-
Good bug reports are extremely helpful-thank you!
|
|
17
|
+
- [ZIP files](https://en.wikipedia.org/wiki/Zip_(file_format)) and the [APPNOTE.TXT](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) specification
|
|
18
|
+
- [rubyzip](https://github.com/rubyzip/rubyzip), as we like to test "our implementation against theirs"
|
|
19
|
+
- [rspec](https://rspec.info/) for testing
|
|
37
20
|
|
|
38
|
-
|
|
21
|
+
## Issues and pull requests
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
|
|
23
|
+
* Opening an issue first is often the fastest way to get something in. I can shape it into something that fits the library. Please do this before any significant PR (new features, refactoring), so you don't spend time on something that won't be merged.
|
|
24
|
+
* Search the issues first, and check whether the problem still happens on the latest `main`.
|
|
25
|
+
* The most useful bug report is a small script that produces the broken archive (or the archive itself), plus the name, version and OS of the tool that fails to open it.
|
|
26
|
+
* I may close a PR and implement the idea differently. That's not a rejection of the idea, and you'll be credited (`Co-authored-by`).
|
|
27
|
+
* Feel free to propose opinionated changes, but be prepared that your opinion won't be shared. It is a give-and-take.
|
|
28
|
+
* Keep PRs focused, without unrelated commits.
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
latest `main` branch in the repository.
|
|
30
|
+
## Code changes
|
|
45
31
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
A good bug report shouldn't leave others needing to chase you up for more
|
|
50
|
-
information. Please try to be as detailed as possible in your report. What is
|
|
51
|
-
your environment? What steps will reproduce the issue? What tool(s) or OS will
|
|
52
|
-
experience the problem? What would you expect to be the outcome? All these
|
|
53
|
-
details will help people to fix any potential bugs.
|
|
54
|
-
|
|
55
|
-
Example:
|
|
56
|
-
|
|
57
|
-
> Short and descriptive example bug report title
|
|
58
|
-
>
|
|
59
|
-
> A summary of the issue and the OS environment in which it occurs. If
|
|
60
|
-
> suitable, include the steps required to reproduce the bug.
|
|
61
|
-
>
|
|
62
|
-
> 1. This is the first step
|
|
63
|
-
> 2. This is the second step
|
|
64
|
-
> 3. Further steps, etc.
|
|
65
|
-
>
|
|
66
|
-
> `<url>` - a link to the reduced test case, if possible. Feel free to use a [Gist](https://gist.github.com).
|
|
67
|
-
>
|
|
68
|
-
> Any other information you want to share that is relevant to the issue being
|
|
69
|
-
> reported. This might include the lines of code that you have identified as
|
|
70
|
-
> causing the bug, and potential solutions (and your opinions on their
|
|
71
|
-
> merits).
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
## Feature requests
|
|
75
|
-
|
|
76
|
-
Feature requests are welcome. But take a moment to find out whether your idea
|
|
77
|
-
fits with the scope and aims of the project. It's up to *you* to make a strong
|
|
78
|
-
case to convince the project's developers of the merits of this feature. Please
|
|
79
|
-
provide as much detail and context as possible.
|
|
32
|
+
Please adhere to the coding conventions used throughout the project (indentation,
|
|
33
|
+
accurate and terse comments, etc.) and any other requirements (such as test coverage).
|
|
80
34
|
|
|
35
|
+
Run the `rake` default task before submitting. It runs the test suite (`bundle exec rspec`),
|
|
36
|
+
checks formatting with [standard](https://github.com/standardrb/standard) (`bundle exec rake format`
|
|
37
|
+
fixes most of it), and regenerates the type signatures in `rbi/`. Commit the regenerated signatures.
|
|
81
38
|
|
|
82
|
-
|
|
39
|
+
zip_kit supports Ruby 2.6 and up, and CI runs the tests and the linter on 2.6. Don't use syntax or
|
|
40
|
+
stdlib features newer than that. CI also runs the suite with frozen string literals enforced, so don't
|
|
41
|
+
mutate string literals.
|
|
83
42
|
|
|
84
|
-
|
|
85
|
-
help. They should remain focused in scope and avoid containing unrelated
|
|
86
|
-
commits.
|
|
43
|
+
## Basic expectations
|
|
87
44
|
|
|
88
|
-
|
|
89
|
-
implementing features, refactoring code, porting to a different language),
|
|
90
|
-
otherwise you risk spending a lot of time working on something that the
|
|
91
|
-
project's developers might not want to merge into the project.
|
|
45
|
+
This project is something created with love, and I - as an author - expect you, as a contributor, to return the favor. The ground rules:
|
|
92
46
|
|
|
93
|
-
|
|
94
|
-
|
|
47
|
+
* Do your best to be kind.
|
|
48
|
+
* Be polite and direct. No abrasiveness, no passive-aggression.
|
|
49
|
+
* [Low-context](https://www.ebsco.com/research-starters/communication-and-mass-media/high-context-and-low-context-cultures) communication is preferred and permitted
|
|
50
|
+
* No ad-hominems, also in code comments
|
|
51
|
+
* No sexualized or aggressive language
|
|
95
52
|
|
|
96
|
-
|
|
97
|
-
suite can be run with `bundle exec rspec`. You are also encouraged to use the
|
|
98
|
-
script in the `testing` directory to create test files that you can then verify
|
|
99
|
-
with various zip/unzip utilities. Further instructions are [here](https://github.com/WeTransfer/zip_kit/blob/main/testing/README_TESTING.md).
|
|
53
|
+
These rules sit alongside the [Code of Conduct](CODE_OF_CONDUCT.md), which applies to everyone taking part in the project.
|
|
100
54
|
|
|
101
|
-
|
|
102
|
-
project:
|
|
55
|
+
As a maintainer, I reserve the right to deny any contribution, discussion post, comment or pull request I find inappropriate or that is not aligned with the Code of Conduct.
|
|
103
56
|
|
|
104
|
-
|
|
105
|
-
and configure the remotes:
|
|
57
|
+
Similarly, I reserve the right to not accept any contribution that doesn't fit the goals or spirit of the project (or my own), without explaining why it has been refused.
|
|
106
58
|
|
|
107
|
-
|
|
108
|
-
# Clone your fork of the repo into the current directory
|
|
109
|
-
git clone git@github.com:julik/zip_kit.git
|
|
110
|
-
# Navigate to the newly cloned directory
|
|
111
|
-
cd zip_kit
|
|
112
|
-
# Assign the original repo to a remote called "upstream"
|
|
113
|
-
git remote add upstream git@github.com:julik/zip_kit.git
|
|
114
|
-
```
|
|
59
|
+
## Decisions are final
|
|
115
60
|
|
|
116
|
-
|
|
61
|
+
You're welcome to make your case once, clearly. After that, please don't reopen closed issues, start meta-threads about the decision, or argue about the wording of this document. This is a list of expectations, not a contract to be lawyered. If something isn't covered, the maintainer's judgment applies.
|
|
117
62
|
|
|
118
|
-
|
|
119
|
-
git checkout <dev-branch>
|
|
120
|
-
git pull upstream <dev-branch>
|
|
121
|
-
```
|
|
63
|
+
This applies to Code of Conduct enforcement too. Reporting a violation is always welcome, privately, at the address given in the [Code of Conduct](CODE_OF_CONDUCT.md#enforcement), but the outcome is final.
|
|
122
64
|
|
|
123
|
-
|
|
124
|
-
contain your feature, change, or fix:
|
|
65
|
+
## What happens elsewhere stays elsewhere
|
|
125
66
|
|
|
126
|
-
|
|
127
|
-
git checkout -b <topic-branch-name>
|
|
128
|
-
```
|
|
67
|
+
Contributions are judged on what they are, not on who sent them. What anyone says or does outside this project (on social media, at their job, in other repos, in politics) is not a topic here and will not be considered, either for or against them. Please don't bring it in: no call-outs, no "are you aware this person…" threads, no requests to ban someone over things that didn't happen here.
|
|
129
68
|
|
|
130
|
-
|
|
131
|
-
conciseness. Check out [this post](https://chris.beams.io/posts/git-commit/) or
|
|
132
|
-
[this other post](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) for some tips re: writing good commit messages.
|
|
69
|
+
The [Code of Conduct](CODE_OF_CONDUCT.md#scope) applies within project spaces only, and defers to this document where the two differ.
|
|
133
70
|
|
|
134
|
-
|
|
71
|
+
## AI-generated issues and code
|
|
135
72
|
|
|
136
|
-
|
|
137
|
-
git pull [--rebase] upstream <dev-branch>
|
|
138
|
-
```
|
|
73
|
+
This project uses LLMs to produce code and artifacts, and it's fine if you do the same when contributing. LLM use in this project is settled: threads about whether it should be used (utility, harm, societal implications etc.) will be closed.
|
|
139
74
|
|
|
140
|
-
|
|
75
|
+
If you require LLM-untainted code for your needs, feel free to use zip_kit prior to and including commit 2f123ae41cffe7255f1ff0824af44420d5dbfaa7 - but I am not going to maintain an "LLM-free" branch of zip_kit just for your needs.
|
|
76
|
+
|
|
77
|
+
Please disclose LLM use in every issue and PR: say which model or tool you used and what for (e.g. "Claude wrote the fix, I wrote the test"). This is for transparency, not for judgment. Disclosed LLM use is treated the same as no LLM use.
|
|
141
78
|
|
|
142
|
-
|
|
143
|
-
git push origin <topic-branch-name>
|
|
144
|
-
```
|
|
79
|
+
However, the following is on you:
|
|
145
80
|
|
|
146
|
-
|
|
81
|
+
* Make your LLM output _terse._ Nobody likes reading slop, even my agents. Make it get to the point.
|
|
82
|
+
* Make sure what you submit actually ran. Don't file issues about behavior or APIs your LLM imagined.
|
|
83
|
+
* zip_kit has no runtime dependencies. Do not add any (or have your LLM do it) unless absolutely necessary
|
|
84
|
+
* If you file a PR, make at least one pass with "Make this at least 50% less code"
|
|
85
|
+
* Code generated by your LLM will be considered as written by you, with the same requirements
|
|
147
86
|
|
|
148
|
-
|
|
149
|
-
with a clear title and description.
|
|
87
|
+
## License
|
|
150
88
|
|
|
151
|
-
|
|
152
|
-
license your work under the same license as that used by the project, which you
|
|
153
|
-
can see by clicking [here](LICENSE.txt).
|
|
89
|
+
By submitting a patch, you agree to allow the project owner to license your work under the same license as that used by the project, which you can see [here](LICENSE.txt).
|
data/lib/zip_kit/file_reader.rb
CHANGED
|
@@ -291,9 +291,8 @@ class ZipKit::FileReader
|
|
|
291
291
|
|
|
292
292
|
# Parse the local header entry and get the offset in the IO at which the
|
|
293
293
|
# actual compressed data of the file starts within the ZIP.
|
|
294
|
-
# The method will
|
|
295
|
-
#
|
|
296
|
-
# and will then compute its size. That size plus the local header offset
|
|
294
|
+
# The method will read the local header for the file, starting at the
|
|
295
|
+
# given offset, and will then compute its size. That size plus the local header offset
|
|
297
296
|
# given will be the compressed data offset of the entry (read starting at
|
|
298
297
|
# this offset to get the data).
|
|
299
298
|
#
|
|
@@ -303,28 +302,43 @@ class ZipKit::FileReader
|
|
|
303
302
|
def read_local_file_header(io:)
|
|
304
303
|
local_file_header_offset = io.tell
|
|
305
304
|
|
|
306
|
-
# Reading in bulk is cheaper - grab
|
|
307
|
-
#
|
|
308
|
-
|
|
309
|
-
|
|
305
|
+
# Reading in bulk is cheaper - grab enough for most local headers, including
|
|
306
|
+
# some headroom for the filename and extra fields. Reading the maximum possible
|
|
307
|
+
# size (about 128KB) for every entry costs more than the parsing itself.
|
|
308
|
+
header = io.read(LOCAL_HEADER_READ_SIZE)
|
|
309
|
+
if header.nil? || header.bytesize < SIZE_OF_LOCAL_HEADER_FIXED_FIELDS
|
|
310
|
+
raise ReadError, "Expected to read at least #{SIZE_OF_LOCAL_HEADER_FIXED_FIELDS} bytes of the local file header, but the IO was at the end"
|
|
311
|
+
end
|
|
310
312
|
|
|
311
|
-
|
|
313
|
+
signature,
|
|
314
|
+
version_needed_to_extract,
|
|
315
|
+
gp_flags,
|
|
316
|
+
storage_mode,
|
|
317
|
+
dos_time,
|
|
318
|
+
dos_date,
|
|
319
|
+
crc32,
|
|
320
|
+
compressed_size,
|
|
321
|
+
uncompressed_size,
|
|
322
|
+
filename_size,
|
|
323
|
+
extra_size = header.unpack(LOCAL_HEADER_FIXED_FIELDS_UNPACKSPEC)
|
|
324
|
+
assert_signature_value(signature, 0x04034b50)
|
|
325
|
+
|
|
326
|
+
# ...and if the filename and extras did not fit, read the rest
|
|
327
|
+
size_of_header = SIZE_OF_LOCAL_HEADER_FIXED_FIELDS + filename_size + extra_size
|
|
328
|
+
header << read_n(io, size_of_header - header.bytesize) if header.bytesize < size_of_header
|
|
312
329
|
|
|
313
|
-
assert_signature(io_starting_at_local_header, 0x04034b50)
|
|
314
330
|
e = ZipEntry.new
|
|
315
|
-
e.version_needed_to_extract =
|
|
316
|
-
e.gp_flags =
|
|
317
|
-
e.storage_mode =
|
|
318
|
-
e.dos_time =
|
|
319
|
-
e.dos_date =
|
|
320
|
-
e.crc32 =
|
|
321
|
-
e.compressed_size =
|
|
322
|
-
e.uncompressed_size =
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
e.filename = read_n(io_starting_at_local_header, filename_size)
|
|
327
|
-
extra_fields_str = read_n(io_starting_at_local_header, extra_size)
|
|
331
|
+
e.version_needed_to_extract = version_needed_to_extract
|
|
332
|
+
e.gp_flags = gp_flags
|
|
333
|
+
e.storage_mode = storage_mode
|
|
334
|
+
e.dos_time = dos_time
|
|
335
|
+
e.dos_date = dos_date
|
|
336
|
+
e.crc32 = crc32
|
|
337
|
+
e.compressed_size = compressed_size
|
|
338
|
+
e.uncompressed_size = uncompressed_size
|
|
339
|
+
|
|
340
|
+
e.filename = header.byteslice(SIZE_OF_LOCAL_HEADER_FIXED_FIELDS, filename_size)
|
|
341
|
+
extra_fields_str = header.byteslice(SIZE_OF_LOCAL_HEADER_FIXED_FIELDS + filename_size, extra_size)
|
|
328
342
|
|
|
329
343
|
# Parse out the extra fields
|
|
330
344
|
extra_table = parse_out_extra_fields(extra_fields_str)
|
|
@@ -351,16 +365,14 @@ class ZipKit::FileReader
|
|
|
351
365
|
e.compressed_size = read_8b(zip64_extra) if e.compressed_size == 0xFFFFFFFF
|
|
352
366
|
end
|
|
353
367
|
|
|
354
|
-
|
|
355
|
-
e.compressed_data_offset = offset
|
|
368
|
+
e.compressed_data_offset = local_file_header_offset + size_of_header
|
|
356
369
|
|
|
357
370
|
e
|
|
358
371
|
end
|
|
359
372
|
|
|
360
373
|
# Get the offset in the IO at which the actual compressed data of the file
|
|
361
|
-
# starts within the ZIP. The method will
|
|
362
|
-
#
|
|
363
|
-
# given offset, and will then compute its size. That size plus the local
|
|
374
|
+
# starts within the ZIP. The method will read the local header for the file,
|
|
375
|
+
# starting at the given offset, and will then compute its size. That size plus the local
|
|
364
376
|
# header offset given will be the compressed data offset of the entry
|
|
365
377
|
# (read starting at this offset to get the data).
|
|
366
378
|
#
|
|
@@ -431,7 +443,10 @@ class ZipKit::FileReader
|
|
|
431
443
|
end
|
|
432
444
|
|
|
433
445
|
def assert_signature(io, signature_magic_number)
|
|
434
|
-
|
|
446
|
+
assert_signature_value(read_4b(io), signature_magic_number)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def assert_signature_value(readback, signature_magic_number)
|
|
435
450
|
if readback != signature_magic_number
|
|
436
451
|
expected = "0x0" + signature_magic_number.to_s(16)
|
|
437
452
|
actual = "0x0" + readback.to_s(16)
|
|
@@ -475,24 +490,39 @@ class ZipKit::FileReader
|
|
|
475
490
|
|
|
476
491
|
def read_cdir_entry(io)
|
|
477
492
|
# read_cdir_entry is too high. [45.66/15]
|
|
478
|
-
|
|
493
|
+
signature,
|
|
494
|
+
made_by,
|
|
495
|
+
version_needed_to_extract,
|
|
496
|
+
gp_flags,
|
|
497
|
+
storage_mode,
|
|
498
|
+
dos_time,
|
|
499
|
+
dos_date,
|
|
500
|
+
crc32,
|
|
501
|
+
compressed_size,
|
|
502
|
+
uncompressed_size,
|
|
503
|
+
filename_size,
|
|
504
|
+
extra_size,
|
|
505
|
+
comment_len,
|
|
506
|
+
disk_number_start,
|
|
507
|
+
internal_attrs,
|
|
508
|
+
external_attrs,
|
|
509
|
+
local_file_header_offset = read_n(io, SIZE_OF_CDIR_ENTRY_FIXED_FIELDS).unpack(CDIR_ENTRY_FIXED_FIELDS_UNPACKSPEC)
|
|
510
|
+
assert_signature_value(signature, 0x02014b50)
|
|
511
|
+
|
|
479
512
|
ZipEntry.new.tap do |e|
|
|
480
|
-
e.made_by =
|
|
481
|
-
e.version_needed_to_extract =
|
|
482
|
-
e.gp_flags =
|
|
483
|
-
e.storage_mode =
|
|
484
|
-
e.dos_time =
|
|
485
|
-
e.dos_date =
|
|
486
|
-
e.crc32 =
|
|
487
|
-
e.compressed_size =
|
|
488
|
-
e.uncompressed_size =
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
e.
|
|
493
|
-
e.internal_attrs = read_2b(io)
|
|
494
|
-
e.external_attrs = read_4b(io)
|
|
495
|
-
e.local_file_header_offset = read_4b(io)
|
|
513
|
+
e.made_by = made_by
|
|
514
|
+
e.version_needed_to_extract = version_needed_to_extract
|
|
515
|
+
e.gp_flags = gp_flags
|
|
516
|
+
e.storage_mode = storage_mode
|
|
517
|
+
e.dos_time = dos_time
|
|
518
|
+
e.dos_date = dos_date
|
|
519
|
+
e.crc32 = crc32
|
|
520
|
+
e.compressed_size = compressed_size
|
|
521
|
+
e.uncompressed_size = uncompressed_size
|
|
522
|
+
e.disk_number_start = disk_number_start
|
|
523
|
+
e.internal_attrs = internal_attrs
|
|
524
|
+
e.external_attrs = external_attrs
|
|
525
|
+
e.local_file_header_offset = local_file_header_offset
|
|
496
526
|
e.filename = read_n(io, filename_size)
|
|
497
527
|
|
|
498
528
|
# Extra fields
|
|
@@ -660,6 +690,16 @@ class ZipKit::FileReader
|
|
|
660
690
|
C_UINT2 = "v"
|
|
661
691
|
C_UINT8 = "Q<"
|
|
662
692
|
|
|
693
|
+
# The fixed-size fields get unpacked in one go, instead of one `read` and `unpack` per field.
|
|
694
|
+
# Signature, version needed, gp flags, storage mode, DOS time and date, CRC32, both sizes and
|
|
695
|
+
# the filename and extra field lengths
|
|
696
|
+
LOCAL_HEADER_FIXED_FIELDS_UNPACKSPEC = "VvvvvvVVVvv"
|
|
697
|
+
SIZE_OF_LOCAL_HEADER_FIXED_FIELDS = 30
|
|
698
|
+
# Same as the local header, but with "made by" after the signature and the comment length,
|
|
699
|
+
# disk number, internal and external attributes and the local header offset at the end
|
|
700
|
+
CDIR_ENTRY_FIXED_FIELDS_UNPACKSPEC = "VvvvvvvVVVvvvvvVV"
|
|
701
|
+
SIZE_OF_CDIR_ENTRY_FIXED_FIELDS = 46
|
|
702
|
+
|
|
663
703
|
# To prevent too many tiny reads, read the maximum possible size of end of
|
|
664
704
|
# central directory record upfront (all the fixed fields + at most 0xFFFF
|
|
665
705
|
# bytes of the archive comment)
|
|
@@ -673,22 +713,9 @@ class ZipKit::FileReader
|
|
|
673
713
|
2 + # The comment size
|
|
674
714
|
0xFFFF # Maximum comment size
|
|
675
715
|
|
|
676
|
-
#
|
|
677
|
-
#
|
|
678
|
-
|
|
679
|
-
MAX_LOCAL_HEADER_SIZE = 4 + # signature
|
|
680
|
-
2 + # Version needed to extract
|
|
681
|
-
2 + # gp flags
|
|
682
|
-
2 + # storage mode
|
|
683
|
-
2 + # dos time
|
|
684
|
-
2 + # dos date
|
|
685
|
-
4 + # CRC32
|
|
686
|
-
4 + # Comp size
|
|
687
|
-
4 + # Uncomp size
|
|
688
|
-
2 + # Filename size
|
|
689
|
-
2 + # Extra fields size
|
|
690
|
-
0xFFFF + # Maximum filename size
|
|
691
|
-
0xFFFF # Maximum extra fields size
|
|
716
|
+
# How much to read upfront for a local file header. Filenames and extra fields rarely
|
|
717
|
+
# take more than a few hundred bytes, the rest gets read separately if needed
|
|
718
|
+
LOCAL_HEADER_READ_SIZE = 4 * 1024
|
|
692
719
|
|
|
693
720
|
SIZE_OF_USABLE_EOCD_RECORD = 4 + # Signature
|
|
694
721
|
2 + # Number of this disk
|
|
@@ -716,7 +743,9 @@ class ZipKit::FileReader
|
|
|
716
743
|
end
|
|
717
744
|
|
|
718
745
|
private_constant :C_UINT4, :C_UINT2, :C_UINT8, :MAX_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE,
|
|
719
|
-
:
|
|
746
|
+
:LOCAL_HEADER_FIXED_FIELDS_UNPACKSPEC, :SIZE_OF_LOCAL_HEADER_FIXED_FIELDS,
|
|
747
|
+
:CDIR_ENTRY_FIXED_FIELDS_UNPACKSPEC, :SIZE_OF_CDIR_ENTRY_FIXED_FIELDS,
|
|
748
|
+
:LOCAL_HEADER_READ_SIZE, :SIZE_OF_USABLE_EOCD_RECORD
|
|
720
749
|
|
|
721
750
|
# Is provided as a stub to be overridden in a subclass if you need it. Will report
|
|
722
751
|
# during various stages of reading. The log message is contained in the return value
|
|
@@ -116,9 +116,12 @@ class ZipKit::OutputEnumerator
|
|
|
116
116
|
#
|
|
117
117
|
# * `last-modified` will be set to "now" so that the response is considered "fresh" by `Rack::ETag`. This is done so that `Rack::ETag` won't try to
|
|
118
118
|
# calculate a lax ETag value and thus won't start buffering your response out of nowhere
|
|
119
|
-
# * `
|
|
120
|
-
#
|
|
121
|
-
#
|
|
119
|
+
# * `cache-control` will be set to `no-transform`. This is so that `Rack::Deflater` and proxies which compress responses (like gzip)
|
|
120
|
+
# are not going to try to compress your response again (and, with `Rack::Deflater` set to `sync: false`, buffer all of it to do so).
|
|
121
|
+
# It says nothing about caching, so add your own directives to it if you need them, and keep `no-transform` in there
|
|
122
|
+
# * `content-encoding` will be set to `identity`. This tells the receiving browsers (or downstream proxies) that they should not attempt to
|
|
123
|
+
# open or uncompress the response before saving it or passing it onwards. Note that it does _not_ stop `Rack::Deflater`, which compresses
|
|
124
|
+
# responses with `content-encoding: identity` just the same
|
|
122
125
|
# * `x-accel-buffering` will be set to `no` - this tells both nginx and the Google Cloud load balancer that the response should not be buffered
|
|
123
126
|
#
|
|
124
127
|
# These header values are known to get as close as possible to guaranteeing streaming on most environments where Ruby web applications may be hosted.
|
|
@@ -134,8 +137,9 @@ class ZipKit::OutputEnumerator
|
|
|
134
137
|
# Set this even when not streaming for consistency. The fact that there would be
|
|
135
138
|
# a weak ETag generated would mean that the middleware buffers, so we have tests for that.
|
|
136
139
|
"last-modified" => Time.now.httpdate,
|
|
137
|
-
# Make sure Rack::Deflater does not touch our response body either
|
|
138
|
-
#
|
|
140
|
+
# Make sure Rack::Deflater does not touch our response body either. It only skips
|
|
141
|
+
# responses which have `no-transform` or a content-encoding other than `identity`
|
|
142
|
+
"cache-control" => "no-transform",
|
|
139
143
|
"content-encoding" => "identity",
|
|
140
144
|
# Disable buffering for both nginx and Google Load Balancer, see
|
|
141
145
|
# https://cloud.google.com/appengine/docs/flexible/how-requests-are-handled?tab=python#x-accel-buffering
|
|
@@ -45,9 +45,16 @@ module ZipKit::RailsStreaming
|
|
|
45
45
|
# names would send some headers twice
|
|
46
46
|
present_header_names = response.headers.keys.map(&:downcase)
|
|
47
47
|
ZipKit::OutputEnumerator.streaming_http_headers.each_pair do |name, value|
|
|
48
|
+
next if name == "cache-control"
|
|
48
49
|
response.headers[name] = value unless present_header_names.include?(name)
|
|
49
50
|
end
|
|
50
51
|
|
|
52
|
+
# Rails rewrites Cache-Control when the response gets committed, and before 7.1 it only
|
|
53
|
+
# looks for it under that name - a lowercase one would go out next to its own. no-transform
|
|
54
|
+
# is what keeps Rack::Deflater away, so it goes on top of whatever the controller has set
|
|
55
|
+
cache_control = response.headers["Cache-Control"].to_s.split(/,\s*/)
|
|
56
|
+
response.headers["Cache-Control"] = (cache_control | ["no-transform"]).join(", ")
|
|
57
|
+
|
|
51
58
|
# The output enumerator yields chunks of bytes generated from the Streamer,
|
|
52
59
|
# with some buffering. See OutputEnumerator docs for more.
|
|
53
60
|
rack_zip_body = ZipKit::OutputEnumerator.new(**output_enumerator_options, &zip_streaming_blk)
|
data/lib/zip_kit/version.rb
CHANGED