zip_tricks 5.2.0 → 5.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b686d9ddb071cdb677559d12879dfa88a77ac63ce6eabad99ce2d8e674c80c28
4
- data.tar.gz: fc8d6382de69962cf2cb7585b9a7476e7694abaca77cf969f2cf9b905076d59c
3
+ metadata.gz: 39403ce8cb20b4f772e5f44420b91be7e120df38b99de7de2a0db46f318760a8
4
+ data.tar.gz: c1c02357d9e5f322daf6d3ce338092020e753e362b3a70b3caf8d35a782925e8
5
5
  SHA512:
6
- metadata.gz: 2949e24de0a8bc731ed0ae4d5fc30534e97ad1102f11c07a353ab211c2948bc1957c35527b4cb00eeb5751713d17a7653adbf65ea188b4fa9d0b0f0567926afe
7
- data.tar.gz: 54972bf28272c68ce9bcada1f872840de04f7ae9a9e0276abeceb828373639f3ae9a999e788401937454d0354d86b87e462d4b60554e2478e7d8a7e6ac9c1348
6
+ metadata.gz: 9b16f5a53ee31ea9f1f5ec1316a62d172c897c510df9f1bb9f7fabe12aafe89e2b4e43f1533a01b5507b9d1a0fe438ee3646c7c10eb1117f8053403796167aad
7
+ data.tar.gz: 3808c09040d549644cb7aba6e8eb4aa0fcbae2123753e406d86db683e4e8d332453112622303be0b8027cff4c43dc60e6678073eb3e7c80cac3359200379210f
@@ -0,0 +1,96 @@
1
+ name: CI
2
+
3
+ on:
4
+ - push
5
+ - pull_request
6
+
7
+ env:
8
+ BUNDLE_PATH: vendor/bundle
9
+
10
+ jobs:
11
+ lint:
12
+ name: Code Style
13
+ runs-on: ubuntu-18.04
14
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
15
+ strategy:
16
+ matrix:
17
+ ruby:
18
+ - '2.7'
19
+ steps:
20
+ - name: Checkout
21
+ uses: actions/checkout@v2
22
+ - name: Setup Ruby
23
+ uses: ruby/setup-ruby@v1
24
+ with:
25
+ ruby-version: ${{ matrix.ruby }}
26
+ - name: Gemfile Cache
27
+ uses: actions/cache@v2
28
+ with:
29
+ path: Gemfile.lock
30
+ key: ${{ runner.os }}-gemlock-${{ matrix.ruby }}-${{ hashFiles('Gemfile', 'zip_tricks.gemspec') }}
31
+ restore-keys: |
32
+ ${{ runner.os }}-gemlock-${{ matrix.ruby }}-
33
+ - name: Bundle Cache
34
+ id: cache-gems
35
+ uses: actions/cache@v2
36
+ with:
37
+ path: vendor/bundle
38
+ key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ hashFiles('Gemfile', 'Gemfile.lock', 'zip_tricks.gemspec') }}
39
+ restore-keys: |
40
+ ${{ runner.os }}-gems-${{ matrix.ruby }}-
41
+ ${{ runner.os }}-gems-
42
+ - name: Bundle Install
43
+ if: steps.cache-gems.outputs.cache-hit != 'true'
44
+ run: bundle install --jobs 4 --retry 3
45
+ - name: Rubocop Cache
46
+ uses: actions/cache@v2
47
+ with:
48
+ path: ~/.cache/rubocop_cache
49
+ key: ${{ runner.os }}-rubocop-${{ hashFiles('.rubocop.yml') }}
50
+ restore-keys: |
51
+ ${{ runner.os }}-rubocop-
52
+ - name: Rubocop
53
+ run: bundle exec rubocop
54
+ test:
55
+ name: Specs
56
+ runs-on: ubuntu-18.04
57
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
58
+ strategy:
59
+ matrix:
60
+ ruby:
61
+ - '2.7'
62
+ - '2.1'
63
+ - 'jruby-9.2'
64
+ experimental: [false]
65
+ include:
66
+ - ruby: 3.0
67
+ experimental: true
68
+ steps:
69
+ - name: Checkout
70
+ uses: actions/checkout@v2
71
+ - name: Setup Ruby
72
+ uses: ruby/setup-ruby@v1
73
+ with:
74
+ ruby-version: ${{ matrix.ruby }}
75
+ - name: Gemfile Cache
76
+ uses: actions/cache@v2
77
+ with:
78
+ path: Gemfile.lock
79
+ key: ${{ runner.os }}-gemlock-${{ matrix.ruby }}-${{ hashFiles('Gemfile', 'zip_tricks.gemspec') }}
80
+ restore-keys: |
81
+ ${{ runner.os }}-gemlock-${{ matrix.ruby }}-
82
+ - name: Bundle Cache
83
+ id: cache-gems
84
+ uses: actions/cache@v2
85
+ with:
86
+ path: vendor/bundle
87
+ key: ${{ runner.os }}-gems-${{ matrix.ruby }}-${{ hashFiles('Gemfile', 'Gemfile.lock', 'zip_tricks.gemspec') }}
88
+ restore-keys: |
89
+ ${{ runner.os }}-gems-${{ matrix.ruby }}-
90
+ ${{ runner.os }}-gems-
91
+ - name: Bundle Install
92
+ if: steps.cache-gems.outputs.cache-hit != 'true'
93
+ run: bundle install --jobs 4 --retry 3
94
+ - name: RSpec
95
+ continue-on-error: ${{ matrix.experimental }}
96
+ run: bundle exec rspec
data/.gitignore CHANGED
@@ -19,7 +19,7 @@ Gemfile.lock
19
19
  # jeweler generated
20
20
  pkg
21
21
 
22
- # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
22
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
23
23
  #
24
24
  # * Create a file at ~/.gitignore
25
25
  # * Include files you want ignored
@@ -36,6 +36,7 @@ pkg
36
36
 
37
37
  tmp
38
38
  testing/*.zip
39
+ .ruby-version
39
40
 
40
41
  # For TextMate
41
42
  #*.tmproj
data/.rubocop.yml CHANGED
@@ -11,3 +11,5 @@ Style/GlobalVars:
11
11
  - qa/*.rb
12
12
  - spec/spec_helper.rb
13
13
  - spec/support/zip_inspection.rb
14
+ Layout/IndentHeredoc:
15
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,4 +1,40 @@
1
- ## 5.2
1
+ ## 5.6.0
2
+
3
+ * Add customisable `unix_permissions` to Streamer and ZipWriter. Beware that customising these permissions can lead to the archive failing to expand with some unarchiving applications, and is especially sensitive for directories.
4
+
5
+ ## 5.5.0
6
+
7
+ * In `OutputEnumerator` apply some amount of buffering to be within a UNIX socket size for metatada writes. This
8
+ speeds up usage with Puma by about 20 percent, as there won't be as many `syswrite` calls on the socket.
9
+ * Make `StoredWriter` and `DeflatedWriter` public constants so that standalone tests can be written for them
10
+
11
+ ## 5.4.0
12
+
13
+ * Use block form for zlib Deflater calls to conserve memory
14
+ * Do not change string encoding in writer wrappers (avoid extra work)
15
+ * Fix a zlib deflater object being leaked per archived file
16
+ * Speed up streaming CRC32 computation
17
+ * When running tests, assign the port for the Puma server dynamically
18
+ * Reduce string allocations in the block deflate spec
19
+ * Make sure RemoteUncap specs run under JRuby correctly
20
+ * Replace Rails::Live streaming with iterable body streaming to avoid issues with Rails::Live across the board
21
+ * Remove `qa/` directory and scripts, as the tests for the library proper should now be sufficient
22
+ * Fix some documentation and sample code omissions and inconsistencies.
23
+
24
+ ## 5.3.1
25
+
26
+ * Fix extended timestamp timestamp value encoding. Previously we would use an incorrect encoding for the timestamp value, which would output correct but nonsensical timestamps. The pack specifier is now changed to output the correct value.
27
+
28
+ ## 5.3.0
29
+
30
+ * Raise in `Streamer#close` when the IO offset of the Streamer does not match the size of the written entries. This is a situation which
31
+ can occur if one adds the local headers, writes the bodies of the files to the socket/output directly, and forgets to adjust the internal
32
+ Streamer offset. The unadjusted offset would then produce incorrect values in both the local headers which come after the missing
33
+ offset adjustment _and_ in the central directory headers. Some ZIP unarchivers are able to recover from this (ones that read
34
+ files "straight-ahead" but others aren't - if the ZIP unarchiver uses central directory entries it would be using incorrect offsets.
35
+ Instead of producing an invalid ZIP, raise an exception which explains what happened and how it can be resolved.
36
+
37
+ ## 5.2.0
2
38
 
3
39
  * Remove `Streamer#add_compressed_entry` and `SizeEstimator#add_compressed_entry`
4
40
 
data/CONTRIBUTING.md CHANGED
@@ -27,7 +27,7 @@ The issue tracker is the preferred channel for [bug reports](#bug-reports),
27
27
  [feature requests](#feature-requests) and [submitting pull
28
28
  requests](#pull-requests), but please respect the following restrictions:
29
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_tricks/blob/master/CODE_OF_CONDUCT.md).
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_tricks/blob/main/CODE_OF_CONDUCT.md).
31
31
 
32
32
  ## Bug reports
33
33
 
@@ -41,7 +41,7 @@ Guidelines for bug reports:
41
41
  reported.
42
42
 
43
43
  2. **Check if the issue has been fixed** — try to reproduce it using the
44
- latest `master` branch in the repository.
44
+ latest `main` branch in the repository.
45
45
 
46
46
  3. **Isolate the problem** — create a [reduced test
47
47
  case](http://css-tricks.com/reduced-test-cases/) and a live example.
@@ -96,7 +96,7 @@ accurate comments, etc.) and any other requirements (such as test coverage).
96
96
  The project uses Rubocop which can be run using `bundle exec rubocop`. The test
97
97
  suite can be run with `bundle exec rspec`. You are also encouraged to use the
98
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_tricks/blob/master/testing/README_TESTING.md).
99
+ with various zip/unzip utilities. Further instructions are [here](https://github.com/WeTransfer/zip_tricks/blob/main/testing/README_TESTING.md).
100
100
 
101
101
  Follow this process if you'd like your work considered for inclusion in the
102
102
  project:
@@ -148,4 +148,4 @@ project:
148
148
 
149
149
  **IMPORTANT**: By submitting a patch, you agree to allow the project owner to
150
150
  license your work under the same license as that used by the project, which you
151
- can see by clicking [here](https://github.com/WeTransfer/zip_tricks/blob/master/LICENSE.txt).
151
+ can see by clicking [here](https://github.com/WeTransfer/zip_tricks/blob/main/LICENSE.txt).
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # zip_tricks
2
2
 
3
- [![Build Status](https://travis-ci.org/WeTransfer/zip_tricks.svg?branch=master)](https://travis-ci.org/WeTransfer/zip_tricks)
3
+ [![Build Status](https://travis-ci.org/WeTransfer/zip_tricks.svg?branch=main)](https://travis-ci.org/WeTransfer/zip_tricks)
4
4
  [![Gem Version](https://badge.fury.io/rb/zip_tricks.svg)](https://badge.fury.io/rb/zip_tricks)
5
5
 
6
6
  Allows streaming, non-rewinding ZIP file output from Ruby.
@@ -24,20 +24,20 @@ to [32 bit sizes.](https://github.com/jruby/jruby/issues/3817)
24
24
 
25
25
  ## Diving in: send some large CSV reports from Rails
26
26
 
27
- The easiest is to use the Rails' built-in streaming feature:
27
+ The easiest is to include the `ZipTricks::RailsStreaming` module into your
28
+ controller.
28
29
 
29
30
  ```ruby
30
31
  class ZipsController < ActionController::Base
31
- include ActionController::Live # required for streaming
32
32
  include ZipTricks::RailsStreaming
33
33
 
34
34
  def download
35
35
  zip_tricks_stream do |zip|
36
36
  zip.write_deflated_file('report1.csv') do |sink|
37
37
  CSV(sink) do |csv_write|
38
- csv << Person.column_names
38
+ csv_write << Person.column_names
39
39
  Person.all.find_each do |person|
40
- csv << person.attributes.values
40
+ csv_write << person.attributes.values
41
41
  end
42
42
  end
43
43
  end
@@ -49,6 +49,10 @@ class ZipsController < ActionController::Base
49
49
  end
50
50
  ```
51
51
 
52
+ If you want some more conveniences you can also use [zipline](https://github.com/fringd/zipline) which
53
+ will automatically process and stream attachments (Carrierwave, Shrine, ActiveStorage) and remote objects
54
+ via HTTP.
55
+
52
56
  ## Create a ZIP file without size estimation, compress on-the-fly during writes
53
57
 
54
58
  Basic use case is compressing on the fly. Some data will be buffered by the Zlib deflater, but
@@ -71,12 +75,15 @@ since you do not know how large the compressed data segments are going to be.
71
75
 
72
76
  ## Send a ZIP from a Rack response
73
77
 
74
- Create a `RackBody` object and give it's constructor a block that adds files.
75
- The block will only be called when actually sending the response to the client
78
+ To "pull" data from ZipTricks you can create an `OutputEnumerator` object which will yield the binary chunks piece
79
+ by piece, and apply some amount of buffering as well. Since this `OutputEnumerator` responds to `#each` and yields
80
+ Strings it also can (and should!) be used as a Rack response body. Return it to your webserver and you will
81
+ have your ZIP streamed. The block that you give to the `OutputEnumerator` will only start executing once your
82
+ response body starts getting iterated over - when actually sending the response to the client
76
83
  (unless you are using a buffering Rack webserver, such as Webrick).
77
84
 
78
85
  ```ruby
79
- body = ZipTricks::RackBody.new do | zip |
86
+ body = ZipTricks::Streamer.output_enum do | zip |
80
87
  zip.write_stored_file('mov.mp4') do |sink| # Those MPEG4 files do not compress that well
81
88
  File.open('mov.mp4', 'rb'){|source| IO.copy_stream(source, sink) }
82
89
  end
@@ -84,7 +91,7 @@ body = ZipTricks::RackBody.new do | zip |
84
91
  File.open('novel.txt', 'rb'){|source| IO.copy_stream(source, sink) }
85
92
  end
86
93
  end
87
- [200, {'Transfer-Encoding' => 'chunked'}, body]
94
+ [200, {}, body]
88
95
  ```
89
96
 
90
97
  ## Send a ZIP file of known size, with correct headers
@@ -123,11 +130,12 @@ ZipTricks::Streamer.open(io) do | zip |
123
130
  # Write the local file header first..
124
131
  zip.add_stored_entry(filename: "first-file.bin", size: raw_file.size, crc32: raw_file_crc32)
125
132
 
126
- # then send the actual file contents bypassing the Streamer interface
133
+ # Adjust the ZIP offsets within the Streamer
134
+ zip.simulate_write(my_temp_file.size)
135
+
136
+ # ...and then send the actual file contents bypassing the Streamer interface
127
137
  io.sendfile(my_temp_file)
128
138
 
129
- # ...and then adjust the ZIP offsets within the Streamer
130
- zip.simulate_write(my_temp_file.size)
131
139
  end
132
140
  ```
133
141
 
@@ -167,16 +175,19 @@ that have not been formally verified (ours hasn't been).
167
175
 
168
176
  ## Contributing to zip_tricks
169
177
 
170
- * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
178
+ * Check out the latest `main` to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
171
179
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
172
180
  * Fork the project.
173
181
  * Start a feature/bugfix branch.
174
182
  * Commit and push until you are happy with your contribution.
175
183
  * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
176
184
  * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
177
- * If you alter the `ZipWriter`, please take the time to run the test in the `qa/` directory. Ensure that the generated (large) files open manually - see README_QA for more.
178
185
 
179
- ## Copyright
186
+ ## Copyright and license
187
+
188
+ Copyright (c) 2020 WeTransfer.
180
189
 
181
- Copyright (c) 2019 WeTransfer. `zip_tricks` is distributed under the conditions of the [Hippocratic License](https://firstdonoharm.dev/version/1/2/license.html)
182
- - See LICENSE.txt for further details.
190
+ `zip_tricks` is distributed under the conditions of the [Hippocratic License](https://firstdonoharm.dev/version/1/2/license.html)
191
+ See LICENSE.txt for further details. If this license is not acceptable for your use case we still maintain the 4.x version tree
192
+ which remains under the MIT license, see https://rubygems.org/gems/zip_tricks/versions for more information.
193
+ Note that we only backport some performance optimizations and crucial bugfixes but not the new features to that tree.
@@ -1,11 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Stashes a block given by the Rack webserver when calling each() on a body, and calls
4
- # that block every time it is written to using :<< (shovel). Poses as an IO for rubyzip.
5
-
3
+ # Acts as a converter between callers which send data to the `#<<` method (such as all the ZipTricks
4
+ # writer methods, which push onto anything), and a given block. Every time `#<<` gets called on the BlockWrite,
5
+ # the block given to the constructor will be called with the same argument. ZipTricks uses this object
6
+ # when integrating with Rack and in the OutputEnumerator. Normally you wouldn't need to use it manually but
7
+ # you always can. BlockWrite will also ensure the binary string encoding is forced onto any string
8
+ # that passes through it.
9
+ #
10
+ # For example, you can create a Rack response body like so:
11
+ #
12
+ # class MyRackResponse
13
+ # def each
14
+ # writer = ZipTricks::BlockWrite.new {|chunk| yield(chunk) }
15
+ # writer << "Hello" << "world" << "!"
16
+ # end
17
+ # end
18
+ # [200, {}, MyRackResponse.new]
6
19
  class ZipTricks::BlockWrite
7
- # The block is the block given to each() of the Rack body, or other block you want
8
- # to receive the string chunks written by the zip compressor.
20
+ # Creates a new BlockWrite.
21
+ #
22
+ # @param block The block that will be called when this object receives the `<<` message
9
23
  def initialize(&block)
10
24
  @block = block
11
25
  end
@@ -17,26 +31,17 @@ class ZipTricks::BlockWrite
17
31
  end
18
32
  end
19
33
 
20
- # Every time this object gets written to, call the Rack body each() block
21
- # with the bytes given instead.
34
+ # Sends a string through to the block stored in the BlockWrite.
35
+ #
36
+ # @param buf[String] the string to write. Note that a zero-length String
37
+ # will not be forwarded to the block, as it has special meaning when used
38
+ # with chunked encoding (it indicates the end of the stream).
39
+ # @return self
22
40
  def <<(buf)
23
41
  # Zero-size output has a special meaning when using chunked encoding
24
42
  return if buf.nil? || buf.bytesize.zero?
25
43
 
26
- # Ensure we ALWAYS write in binary encoding.
27
- encoded =
28
- if buf.encoding != Encoding::BINARY
29
- # If we got a frozen string we can't force_encoding on it
30
- begin
31
- buf.force_encoding(Encoding::BINARY)
32
- rescue
33
- buf.dup.force_encoding(Encoding::BINARY)
34
- end
35
- else
36
- buf
37
- end
38
-
39
- @block.call(encoded)
44
+ @block.call(buf.b)
40
45
  self
41
46
  end
42
47
  end
@@ -19,7 +19,7 @@ require 'stringio'
19
19
  # ## Usage
20
20
  #
21
21
  # File.open('zipfile.zip', 'rb') do |f|
22
- # entries = FileReader.read_zip_structure(f)
22
+ # entries = ZipTricks::FileReader.read_zip_structure(io: f)
23
23
  # entries.each do |e|
24
24
  # File.open(e.filename, 'wb') do |extracted_file|
25
25
  # ex = e.extractor_from(f)
@@ -82,7 +82,9 @@ class ZipTricks::FileReader
82
82
 
83
83
  private_constant :StoredReader, :InflatingReader
84
84
 
85
- # Represents a file within the ZIP archive being read
85
+ # Represents a file within the ZIP archive being read. This is different from
86
+ # the Entry object used in Streamer for ZIP writing, since during writing more
87
+ # data can be kept in memory for immediate use.
86
88
  class ZipEntry
87
89
  # @return [Fixnum] bit-packed version signature of the program that made the archive
88
90
  attr_accessor :made_by
@@ -279,7 +281,7 @@ class ZipTricks::FileReader
279
281
  seek(io, next_local_header_offset) # Seek to the next entry, and raise if seek is impossible
280
282
  end
281
283
  entries
282
- rescue ReadError
284
+ rescue ReadError, RangeError # RangeError is raised if offset exceeds int32/int64 range
283
285
  log do
284
286
  'Got a read/seek error after reaching %<cur_offset>d, no more entries can be recovered' %
285
287
  {cur_offset: cur_offset}
@@ -363,7 +365,7 @@ class ZipTricks::FileReader
363
365
  # (read starting at this offset to get the data).
364
366
  #
365
367
  # @param io[#seek, #read] an IO-ish object the ZIP file can be read from
366
- # @param local_header_offset[Fixnum] absolute offset (0-based) where the
368
+ # @param local_file_header_offset[Fixnum] absolute offset (0-based) where the
367
369
  # local file header is supposed to begin @return [Fixnum] absolute offset
368
370
  # (0-based) of where the compressed data begins for this file within the ZIP
369
371
  def get_compressed_data_offset(io:, local_file_header_offset:)
@@ -375,7 +377,7 @@ class ZipTricks::FileReader
375
377
  # Parse an IO handle to a ZIP archive into an array of Entry objects, reading from the end
376
378
  # of the IO object.
377
379
  #
378
- # @see {#read_zip_structure}
380
+ # @see #read_zip_structure
379
381
  # @param options[Hash] any options the instance method of the same name accepts
380
382
  # @return [Array<ZipEntry>] an array of entries within the ZIP being parsed
381
383
  def self.read_zip_structure(**options)
@@ -385,7 +387,7 @@ class ZipTricks::FileReader
385
387
  # Parse an IO handle to a ZIP archive into an array of Entry objects, reading from the start of
386
388
  # the file and parsing local file headers one-by-one
387
389
  #
388
- # @see {#read_zip_straight_ahead}
390
+ # @see #read_zip_straight_ahead
389
391
  # @param options[Hash] any options the instance method of the same name accepts
390
392
  # @return [Array<ZipEntry>] an array of entries within the ZIP being parsed
391
393
  def self.read_zip_straight_ahead(**options)
@@ -4,7 +4,7 @@
4
4
  # write operations, but want to discard the data (like when
5
5
  # estimating the size of a ZIP)
6
6
  module ZipTricks::NullWriter
7
- # @param data[String] the data to write
7
+ # @param _[String] the data to write
8
8
  # @return [self]
9
9
  def self.<<(_)
10
10
  self