zip_kit 6.3.1 → 6.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 442833a965d373fd56e8d085164f536e17560ed4a74e11aab5befc61946de1bc
4
- data.tar.gz: 6c470314702d643b8f5b55fb3a328ab62cecfe1982587df9b8179fd84bf9013e
3
+ metadata.gz: 2ff5f4284066004d435d36f28020e6ef2279fe0abe36c520436db3bd39d7608a
4
+ data.tar.gz: ac9f9c4312c632410cee6ffb98c2ec471f9de16e5260793a30df8ac28639218d
5
5
  SHA512:
6
- metadata.gz: 8532b5faf979cc98ba0f1e05dff6adf3e6748466553446397c3f7f706fe663182f8fab47a973e3e911b2457eb0622579a62d7b35c1fe18644b7ea05541d7e316
7
- data.tar.gz: 377ef88938f5ec86ea6cd2e15c6857eac9ff2ffc25bb629cf2a0d0ba774a823013e4b4061d1a5392dc4b88e8cbd3d022a1f90e41d1cb68ad72c5e9d42f3c51cd
6
+ metadata.gz: 34389f0a2d38a532af341c7694fcd10c2bbfb2f819ce9d1168afd51fbfc815b8c4ef0b36929f286f3ad31ead98479b07b871ec35d1d8f5adefbf2bb2717f3f58
7
+ data.tar.gz: 821151643cc5adafd9fe3446412e9d9de60ff049f6bab3ca1e9a7c0dc24dbcef0688b8bc0ab1fa51d88feb85892b3707ccbf8483e41c532680c2e9ddb073e823
@@ -7,23 +7,46 @@ env:
7
7
  BUNDLE_PATH: vendor/bundle
8
8
 
9
9
  jobs:
10
- test:
11
- name: Tests and Lint
10
+ test_baseline_ruby:
11
+ name: "Tests (Ruby 2.6 baseline)"
12
12
  runs-on: ubuntu-22.04
13
- strategy:
14
- matrix:
15
- ruby:
16
- - '2.6'
17
- - '3.3'
18
13
  steps:
19
14
  - name: Checkout
20
15
  uses: actions/checkout@v4
21
16
  - name: Setup Ruby
22
17
  uses: ruby/setup-ruby@v1
23
18
  with:
24
- ruby-version: ${{ matrix.ruby }}
19
+ ruby-version: '2.6'
25
20
  bundler-cache: true
26
21
  - name: "Tests"
27
22
  run: bundle exec rspec --backtrace --fail-fast
23
+
24
+ test_newest_ruby:
25
+ name: "Tests (Ruby 3.4 with frozen string literals)"
26
+ runs-on: ubuntu-22.04
27
+ steps:
28
+ - name: Checkout
29
+ uses: actions/checkout@v4
30
+ - name: Setup Ruby
31
+ uses: ruby/setup-ruby@v1
32
+ with:
33
+ ruby-version: '3.4.1'
34
+ bundler-cache: true
35
+ - name: "Tests" # Make the test suite hard-crash on frozen string literal violations
36
+ env:
37
+ RUBYOPT: "--enable=frozen-string-literal --debug=frozen-string-literal"
38
+ run: "bundle exec rspec --backtrace --fail-fast"
39
+
40
+ lint_baseline_ruby: # We need to use syntax appropriate for the minimum supported Ruby version
41
+ name: Lint (Ruby 2.6 syntax)
42
+ runs-on: ubuntu-22.04
43
+ steps:
44
+ - name: Checkout
45
+ uses: actions/checkout@v4
46
+ - name: Setup Ruby
47
+ uses: ruby/setup-ruby@v1
48
+ with:
49
+ ruby-version: '2.6'
50
+ bundler-cache: true
28
51
  - name: "Lint"
29
52
  run: bundle exec rake standard
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 6.3.2
2
+
3
+ * Make sure `rollback!` correctly works with `write_file` and the original exception gets re-raised from `write_file` if
4
+ closing the current entry happens in `Writable#close`
5
+
1
6
  ## 6.3.1
2
7
 
3
8
  * Include `RailsStreaming` in a Rails loader callback, so that ActionController does not need to be in the namespace.
data/README.md CHANGED
@@ -21,6 +21,11 @@ the library can be used to generate JAR files, EPUBs, OpenOffice/Office document
21
21
 
22
22
  ## How does it work? How is it different from Rubyzip?
23
23
 
24
+ zip_kit outputs the metadata of the ZIP file as it becomes available. Same for the content of the ZIP
25
+ entries. This allows nearly-unbuffered, streaming output. When reading ZIP files, zip_kit only reads
26
+ the metadata and does so in an accelerated, efficient way - permitting ZIP unarchiving directly from
27
+ a resource on HTTP (provided that the server supports HTTP ranges).
28
+
24
29
  Check out [the implementation details](IMPLEMENTATION_DETAILS.md) on the design of the library, and
25
30
  we have a separate [reference](RUBYZIP_DIFFERENCES.md) on why you might want to use ZipKit over
26
31
  Rubyzip and vice versa.
@@ -112,6 +112,17 @@ class ZipKit::OutputEnumerator
112
112
  # but are of a file format built "on top" of ZIPs - such as ODTs, [pkpass files](https://developer.apple.com/documentation/walletpasses/building_a_pass)
113
113
  # and ePubs.
114
114
  #
115
+ # More value, however, is in the "technical" headers this method will provide. It will take the following steps to make sure streaming works correctly.
116
+ #
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
+ # calculate a lax ETag value and thus won't start buffering your response out of nowhere
119
+ # * `Content-Encoding` will be set to `identity`. This is so that proxies or the Rack middleware that applies compression to the response (like gzip)
120
+ # is not going to try to compress your response. It also tells the receiving browsers (or downstream proxies) that they should not attempt to
121
+ # open or uncompress the response before saving it or passing it onwards.
122
+ # * `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
+ #
124
+ # These header values are known to get as close as possible to guaranteeing streaming on most environments where Ruby web applications may be hosted.
125
+ #
115
126
  # @return [Hash]
116
127
  def self.streaming_http_headers
117
128
  _headers = {
@@ -74,7 +74,7 @@ class ZipKit::RemoteIO
74
74
  # @param range[Range] the HTTP range of data to fetch from remote
75
75
  # @return [String] the response body of the ranged request
76
76
  def request_range(range)
77
- http = Net::HTTP.start(@uri.hostname, @uri.port)
77
+ http = Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == "https")
78
78
  request = Net::HTTP::Get.new(@uri)
79
79
  request.range = range
80
80
  response = http.request(request)
@@ -91,7 +91,7 @@ class ZipKit::RemoteIO
91
91
  #
92
92
  # @return [Integer] the size of the remote resource, parsed either from Content-Length or Content-Range header
93
93
  def request_object_size
94
- http = Net::HTTP.start(@uri.hostname, @uri.port)
94
+ http = Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: @uri.scheme == "https")
95
95
  request = Net::HTTP::Get.new(@uri)
96
96
  request.range = 0..0
97
97
  response = http.request(request)
@@ -36,8 +36,11 @@ class ZipKit::SizeEstimator
36
36
  #
37
37
  # @param filename [String] the name of the file (filenames are variable-width in the ZIP)
38
38
  # @param size [Integer] size of the uncompressed entry
39
- # @param use_data_descriptor[Boolean] whether the entry uses a postfix
40
- # data descriptor to specify size
39
+ # @param use_data_descriptor[Boolean] whether there is going to be a data descriptor written
40
+ # after the entry body, to specify size.
41
+ # You must enable this if you are going to be
42
+ # using {Streamer#write_stored_file} as otherwise your
43
+ # estimated size is not going to be accurate
41
44
  # @return self
42
45
  def add_stored_entry(filename:, size:, use_data_descriptor: false)
43
46
  @streamer.add_stored_entry(filename: filename,
@@ -56,8 +59,11 @@ class ZipKit::SizeEstimator
56
59
  # @param filename [String] the name of the file (filenames are variable-width in the ZIP)
57
60
  # @param uncompressed_size [Integer] size of the uncompressed entry
58
61
  # @param compressed_size [Integer] size of the compressed entry
59
- # @param use_data_descriptor[Boolean] whether the entry uses a postfix data
60
- # descriptor to specify size
62
+ # @param use_data_descriptor[Boolean] whether there is going to be a data descriptor written
63
+ # after the entry body, to specify size.
64
+ # You must enable this if you are going to be
65
+ # using {Streamer#write_deflated_file} as otherwise your
66
+ # estimated size is not going to be accurate
61
67
  # @return self
62
68
  def add_deflated_entry(filename:, uncompressed_size:, compressed_size:, use_data_descriptor: false)
63
69
  @streamer.add_deflated_entry(filename: filename,
@@ -26,6 +26,7 @@ class ZipKit::Streamer::Heuristic < ZipKit::Streamer::Writable
26
26
  @bytes_deflated = 0
27
27
 
28
28
  @winner = nil
29
+ @started_closing = false
29
30
  end
30
31
 
31
32
  def <<(bytes)
@@ -40,6 +41,9 @@ class ZipKit::Streamer::Heuristic < ZipKit::Streamer::Writable
40
41
  end
41
42
 
42
43
  def close
44
+ return if @started_closing
45
+ @started_closing = true # started_closing because an exception may get raised inside close(), as we add an entry there
46
+
43
47
  decide unless @winner
44
48
  @winner.close
45
49
  end
@@ -47,6 +51,7 @@ class ZipKit::Streamer::Heuristic < ZipKit::Streamer::Writable
47
51
  private def decide
48
52
  # Finish and then close the deflater - it has likely buffered some data
49
53
  @bytes_deflated += @deflater.finish.bytesize until @deflater.finished?
54
+
50
55
  # If the deflated version is smaller than the stored one
51
56
  # - use deflate, otherwise stored
52
57
  ratio = @bytes_deflated / @buf.size.to_f
@@ -55,9 +60,12 @@ class ZipKit::Streamer::Heuristic < ZipKit::Streamer::Writable
55
60
  else
56
61
  @streamer.write_stored_file(@filename, **@write_file_options)
57
62
  end
63
+
58
64
  # Copy the buffered uncompressed data into the newly initialized writable
59
65
  @buf.rewind
60
66
  IO.copy_stream(@buf, @winner)
61
67
  @buf.truncate(0)
68
+ ensure
69
+ @deflater.close
62
70
  end
63
71
  end
@@ -5,8 +5,12 @@ require "set"
5
5
  # Is used to write ZIP archives without having to read them back or to overwrite
6
6
  # data. It outputs into any object that supports `<<` or `write`, namely:
7
7
  #
8
- # An `Array`, `File`, `IO`, `Socket` and even `String` all can be output destinations
9
- # for the `Streamer`.
8
+ # * `Array` - will contain binary strings
9
+ # * `File` - data will be written to it as it gets generated
10
+ # * `IO` (`Socket`, `StringIO`) - data gets written into it
11
+ # * `String` - in binary encoding and unfrozen - also makes a decent output target
12
+ #
13
+ # or anything else that responds to `#<<` or `#write`.
10
14
  #
11
15
  # You can also combine output through the `Streamer` with direct output to the destination,
12
16
  # all while preserving the correct offsets in the ZIP file structures. This allows usage
@@ -482,6 +486,10 @@ class ZipKit::Streamer
482
486
  # is likely already on the wire. However, excluding the entry from the central directory of the ZIP
483
487
  # file will allow better-behaved ZIP unarchivers to extract the entries which did store correctly,
484
488
  # provided they read the ZIP from the central directory and not straight-ahead.
489
+ # Rolling back does not perform any writes.
490
+ #
491
+ # `rollback!` gets called for you if an exception is raised inside the block of `write_file`,
492
+ # `write_deflated_file` and `write_stored_file`.
485
493
  #
486
494
  # @example
487
495
  # zip.add_stored_entry(filename: "data.bin", size: 4.megabytes, crc32: the_crc)
@@ -493,14 +501,17 @@ class ZipKit::Streamer
493
501
  # end
494
502
  # @return [Integer] position in the output stream / ZIP archive
495
503
  def rollback!
496
- removed_entry = @files.pop
497
- return @out.tell unless removed_entry
504
+ @files.pop if @remove_last_file_at_rollback
498
505
 
506
+ # Recreate the path set from remaining entries (PathSet does not support cheap deletes yet)
499
507
  @path_set.clear
500
508
  @files.each do |e|
501
509
  @path_set.add_directory_or_file_path(e.filename) unless e.filler?
502
510
  end
503
- @files << Filler.new(@out.tell - removed_entry.local_header_offset)
511
+
512
+ # Create filler for the truncated or unusable local file entry that did get written into the output
513
+ filler_size_bytes = @out.tell - @offset_before_last_local_file_header
514
+ @files << Filler.new(filler_size_bytes)
504
515
 
505
516
  @out.tell
506
517
  end
@@ -554,6 +565,11 @@ class ZipKit::Streamer
554
565
  use_data_descriptor:,
555
566
  unix_permissions:
556
567
  )
568
+ # Set state needed for proper rollback later. If write_local_file_header
569
+ # does manage to write _some_ bytes, but fails later (we write in tiny bits sometimes)
570
+ # we should be able to create a filler from this offset on when we
571
+ @offset_before_last_local_file_header = @out.tell
572
+ @remove_last_file_at_rollback = false
557
573
 
558
574
  # Clean backslashes
559
575
  filename = remove_backslash(filename)
@@ -600,9 +616,11 @@ class ZipKit::Streamer
600
616
  mtime: e.mtime,
601
617
  filename: e.filename,
602
618
  storage_mode: e.storage_mode)
619
+
603
620
  e.bytes_used_for_local_header = @out.tell - e.local_header_offset
604
621
 
605
622
  @files << e
623
+ @remove_last_file_at_rollback = true
606
624
  end
607
625
 
608
626
  def remove_backslash(filename)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZipKit
4
- VERSION = "6.3.1"
4
+ VERSION = "6.3.2"
5
5
  end
data/rbi/zip_kit.rbi CHANGED
@@ -1897,13 +1897,11 @@ end, T.untyped)
1897
1897
 
1898
1898
  # Add a fake entry to the archive, to see how big it is going to be in the end.
1899
1899
  #
1900
- # data descriptor to specify size
1901
- #
1902
1900
  # _@param_ `filename` — the name of the file (filenames are variable-width in the ZIP)
1903
1901
  #
1904
1902
  # _@param_ `size` — size of the uncompressed entry
1905
1903
  #
1906
- # _@param_ `use_data_descriptor` — whether the entry uses a postfix
1904
+ # _@param_ `use_data_descriptor` — whether there is going to be a data descriptor written after the entry body, to specify size. You must enable this if you are going to be using {Streamer#write_stored_file} as otherwise your estimated size is not going to be accurate
1907
1905
  #
1908
1906
  # _@return_ — self
1909
1907
  sig { params(filename: String, size: Integer, use_data_descriptor: T::Boolean).returns(T.untyped) }
@@ -1917,7 +1915,7 @@ end, T.untyped)
1917
1915
  #
1918
1916
  # _@param_ `compressed_size` — size of the compressed entry
1919
1917
  #
1920
- # _@param_ `use_data_descriptor` — whether the entry uses a postfix data descriptor to specify size
1918
+ # _@param_ `use_data_descriptor` — whether there is going to be a data descriptor written after the entry body, to specify size. You must enable this if you are going to be using {Streamer#write_deflated_file} as otherwise your estimated size is not going to be accurate
1921
1919
  #
1922
1920
  # _@return_ — self
1923
1921
  sig do
@@ -2102,6 +2100,17 @@ end, T.untyped)
2102
2100
  # ones - for example, specific content types are needed for files which are, technically, ZIP files
2103
2101
  # but are of a file format built "on top" of ZIPs - such as ODTs, [pkpass files](https://developer.apple.com/documentation/walletpasses/building_a_pass)
2104
2102
  # and ePubs.
2103
+ #
2104
+ # More value, however, is in the "technical" headers this method will provide. It will take the following steps to make sure streaming works correctly.
2105
+ #
2106
+ # * `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
2107
+ # calculate a lax ETag value and thus won't start buffering your response out of nowhere
2108
+ # * `Content-Encoding` will be set to `identity`. This is so that proxies or the Rack middleware that applies compression to the response (like gzip)
2109
+ # is not going to try to compress your response. It also tells the receiving browsers (or downstream proxies) that they should not attempt to
2110
+ # open or uncompress the response before saving it or passing it onwards.
2111
+ # * `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
2112
+ #
2113
+ # These header values are known to get as close as possible to guaranteeing streaming on most environments where Ruby web applications may be hosted.
2105
2114
  sig { returns(T::Hash[T.untyped, T.untyped]) }
2106
2115
  def self.streaming_http_headers; end
2107
2116
 
data/zip_kit.gemspec CHANGED
@@ -40,6 +40,8 @@ Gem::Specification.new do |spec|
40
40
  spec.add_development_dependency "standard", "1.28.5" # Very specific version of standard for 2.6 with _known_ settings
41
41
  spec.add_development_dependency "magic_frozen_string_literal"
42
42
  spec.add_development_dependency "puma"
43
+ spec.add_development_dependency "mutex_m" # Some deps use it but it is no longer in stdlib since 3.4
44
+ spec.add_development_dependency "bigdecimal" # Some deps use it but it is no longer in stdlib since 3.4
43
45
  spec.add_development_dependency "rails", "~> 5" # For testing RailsStreaming against an actual Rails controller
44
46
  spec.add_development_dependency "actionpack", "~> 5" # For testing RailsStreaming against an actual Rails controller
45
47
  spec.add_development_dependency "nokogiri", "~> 1", ">= 1.13" # Rails 5 does by mistake use an older Nokogiri otherwise
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zip_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.3.1
4
+ version: 6.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
@@ -9,10 +9,9 @@ authors:
9
9
  - Dmitry Tymchuk
10
10
  - David Bosveld
11
11
  - Felix Bünemann
12
- autorequire:
13
12
  bindir: exe
14
13
  cert_chain: []
15
- date: 2024-08-11 00:00:00.000000000 Z
14
+ date: 2025-02-25 00:00:00.000000000 Z
16
15
  dependencies:
17
16
  - !ruby/object:Gem::Dependency
18
17
  name: bundler
@@ -216,6 +215,34 @@ dependencies:
216
215
  - - ">="
217
216
  - !ruby/object:Gem::Version
218
217
  version: '0'
218
+ - !ruby/object:Gem::Dependency
219
+ name: mutex_m
220
+ requirement: !ruby/object:Gem::Requirement
221
+ requirements:
222
+ - - ">="
223
+ - !ruby/object:Gem::Version
224
+ version: '0'
225
+ type: :development
226
+ prerelease: false
227
+ version_requirements: !ruby/object:Gem::Requirement
228
+ requirements:
229
+ - - ">="
230
+ - !ruby/object:Gem::Version
231
+ version: '0'
232
+ - !ruby/object:Gem::Dependency
233
+ name: bigdecimal
234
+ requirement: !ruby/object:Gem::Requirement
235
+ requirements:
236
+ - - ">="
237
+ - !ruby/object:Gem::Version
238
+ version: '0'
239
+ type: :development
240
+ prerelease: false
241
+ version_requirements: !ruby/object:Gem::Requirement
242
+ requirements:
243
+ - - ">="
244
+ - !ruby/object:Gem::Version
245
+ version: '0'
219
246
  - !ruby/object:Gem::Dependency
220
247
  name: rails
221
248
  requirement: !ruby/object:Gem::Requirement
@@ -356,7 +383,6 @@ licenses:
356
383
  - MIT
357
384
  metadata:
358
385
  allowed_push_host: https://rubygems.org
359
- post_install_message:
360
386
  rdoc_options: []
361
387
  require_paths:
362
388
  - lib
@@ -371,8 +397,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
371
397
  - !ruby/object:Gem::Version
372
398
  version: '0'
373
399
  requirements: []
374
- rubygems_version: 3.5.3
375
- signing_key:
400
+ rubygems_version: 3.6.2
376
401
  specification_version: 4
377
402
  summary: Stream out ZIP files from Ruby. Successor to zip_tricks.
378
403
  test_files: []