zip_kit 6.3.2 → 6.3.3

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: 2ff5f4284066004d435d36f28020e6ef2279fe0abe36c520436db3bd39d7608a
4
- data.tar.gz: ac9f9c4312c632410cee6ffb98c2ec471f9de16e5260793a30df8ac28639218d
3
+ metadata.gz: e037e0584d0dbee2e4ab4d934567d04564a26e4f30348f538bda6f7697f8d700
4
+ data.tar.gz: e069300aae55e54c3d88ed1a6c1e23803bdffbc92a7764de3f58a38a892f2b82
5
5
  SHA512:
6
- metadata.gz: 34389f0a2d38a532af341c7694fcd10c2bbfb2f819ce9d1168afd51fbfc815b8c4ef0b36929f286f3ad31ead98479b07b871ec35d1d8f5adefbf2bb2717f3f58
7
- data.tar.gz: 821151643cc5adafd9fe3446412e9d9de60ff049f6bab3ca1e9a7c0dc24dbcef0688b8bc0ab1fa51d88feb85892b3707ccbf8483e41c532680c2e9ddb073e823
6
+ metadata.gz: d35e87fc08da2212b9238e4f39df44091454ff77ef41e9810785aaa6855b46c017b285672b1fd0c0bb36df3255a24219331185ce1fc5f7dcf49584c46cce92f0
7
+ data.tar.gz: b50c429db7b8bbfe7b62d6b14be6cd0827cf1937f6065078b28a6d19b5e016e90e3ac2af0a3567b645ce14f44e4767ac42a5f5c20bb0e0d65ba207b310c02fc0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 6.3.3
2
+
3
+ * Make sure `Writable#<<` converts the strings it is given into binary if they are not already in binary. This fixes an issue where `Heuristic` would suddenly start forwarding strings as-is to downstream callees. There is a lot of spots where the string-to-write gets forwarded and converting in every single one will be quite wasteful, but it can be handy to do in a few key places.
4
+ * Make sure `WritableBuffer#<<` converts the strings it is given into binary if they are not already in binary. This helps prevent an issue where the receiving object the buffer flushes to is in a different encoding than binary (and all of our use cases assume bytes anyway, except for filenames).
5
+ * When rescuing a failed `write_file`, differentiate between `#close`
6
+ and `#release_resources_on_failure!`. Closing a Writable can still try
7
+ to do things to the Streamer output, it can try to write to the destination
8
+ IO which is no longer accepting writes and so on. What we do want is to
9
+ safely destroy the zlib deflaters.
10
+
1
11
  ## 6.3.2
2
12
 
3
13
  * Make sure `rollback!` correctly works with `write_file` and the original exception gets re-raised from `write_file` if
data/Rakefile CHANGED
@@ -3,7 +3,6 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
5
  require "yard"
6
- require "rubocop/rake_task"
7
6
  require "standard/rake"
8
7
 
9
8
  task :format do
@@ -16,6 +15,7 @@ RSpec::Core::RakeTask.new(:spec)
16
15
 
17
16
  task :generate_typedefs do
18
17
  `bundle exec sord rbi/zip_kit.rbi`
18
+ `bundle exec sord rbi/zip_kit.rbs`
19
19
  end
20
20
 
21
21
  task default: [:spec, :standard, :generate_typedefs]
@@ -5,6 +5,7 @@
5
5
  # interchangeable with the StoredWriter in terms of interface.
6
6
  class ZipKit::Streamer::DeflatedWriter
7
7
  include ZipKit::WriteShovel
8
+ include ZipKit::ZlibCleanup
8
9
 
9
10
  # The amount of bytes we will buffer before computing the intermediate
10
11
  # CRC32 checksums. Benchmarks show that the optimum is 64KB (see
@@ -42,4 +43,8 @@ class ZipKit::Streamer::DeflatedWriter
42
43
  ensure
43
44
  @deflater.close
44
45
  end
46
+
47
+ def release_resources_on_failure!
48
+ safely_dispose_of_incomplete_deflater(@deflater)
49
+ end
45
50
  end
@@ -13,6 +13,8 @@ require "zlib"
13
13
  # on the Streamer passed into it once it knows which compression
14
14
  # method should be applied
15
15
  class ZipKit::Streamer::Heuristic < ZipKit::Streamer::Writable
16
+ include ZipKit::ZlibCleanup
17
+
16
18
  BYTES_WRITTEN_THRESHOLD = 128 * 1024
17
19
  MINIMUM_VIABLE_COMPRESSION = 0.75
18
20
 
@@ -48,6 +50,11 @@ class ZipKit::Streamer::Heuristic < ZipKit::Streamer::Writable
48
50
  @winner.close
49
51
  end
50
52
 
53
+ def release_resources_on_failure!
54
+ safely_dispose_of_incomplete_deflater(@deflater)
55
+ @winner&.release_resources_on_failure!
56
+ end
57
+
51
58
  private def decide
52
59
  # Finish and then close the deflater - it has likely buffered some data
53
60
  @bytes_deflated += @deflater.finish.bytesize until @deflater.finished?
@@ -36,4 +36,8 @@ class ZipKit::Streamer::StoredWriter
36
36
  @crc.flush
37
37
  {crc32: @crc_compute.to_i, compressed_size: @io.tell, uncompressed_size: @io.tell}
38
38
  end
39
+
40
+ def release_resources_on_failure!
41
+ # Nothing to do
42
+ end
39
43
  end
@@ -18,11 +18,11 @@ class ZipKit::Streamer::Writable
18
18
 
19
19
  # Writes the given data to the output stream
20
20
  #
21
- # @param d[String] the binary string to write (part of the uncompressed file)
21
+ # @param string[String] the string to write (part of the uncompressed file)
22
22
  # @return [self]
23
- def <<(d)
23
+ def <<(string)
24
24
  raise "Trying to write to a closed Writable" if @closed
25
- @writer << d
25
+ @writer << string.b
26
26
  self
27
27
  end
28
28
 
@@ -33,4 +33,10 @@ class ZipKit::Streamer::Writable
33
33
  @streamer.update_last_entry_and_write_data_descriptor(**@writer.finish)
34
34
  @closed = true
35
35
  end
36
+
37
+ def release_resources_on_failure!
38
+ return if @closed
39
+ @closed = true
40
+ @writer.release_resources_on_failure!
41
+ end
36
42
  end
@@ -524,7 +524,7 @@ class ZipKit::Streamer
524
524
  yield(writable)
525
525
  writable.close
526
526
  rescue
527
- writable.close
527
+ writable.release_resources_on_failure!
528
528
  rollback!
529
529
  raise
530
530
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZipKit
4
- VERSION = "6.3.2"
4
+ VERSION = "6.3.3"
5
5
  end
@@ -42,14 +42,14 @@ class ZipKit::WriteBuffer
42
42
  # Appends the given data to the write buffer, and flushes the buffer into the
43
43
  # writable if the buffer size exceeds the `buffer_size` given at initialization
44
44
  #
45
- # @param data[String] data to be written
45
+ # @param string[String] data to be written
46
46
  # @return self
47
- def <<(data)
48
- if data.bytesize >= @buffer_size
49
- flush unless @buf.empty? # <- this is were we can output less than @buffer_size
50
- @writable << data
47
+ def <<(string)
48
+ if string.bytesize >= @buffer_size
49
+ flush # <- this is were we can output less than @buffer_size
50
+ @writable << string.b
51
51
  else
52
- @buf << data
52
+ @buf << string.b
53
53
  flush if @buf.bytesize >= @buffer_size
54
54
  end
55
55
  self
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ZipKit::ZlibCleanup
4
+ # This method is used to flush and close the native zlib handles
5
+ # should an archiving routine encounter an error. This is necessary,
6
+ # since otherwise unclosed deflaters may hang around in memory
7
+ # indefinitely, creating leaks.
8
+ #
9
+ # @param [Zlib::Deflater?]deflater the deflater to safely finish and close
10
+ # @return void
11
+ def safely_dispose_of_incomplete_deflater(deflater)
12
+ return unless deflater
13
+
14
+ # It can be a bit tricky to close and dealloc the deflater correctly.
15
+ # We want to do the right things for it to be GCd, including the
16
+ # native zlib handle. Also, leaving zlib handles dangling around
17
+ # creates warnings with "...with N bytes remaining to read", which are an
18
+ # eyesore. But they are there for a reason - so that we don't forget to do
19
+ # exactly this.
20
+ if !deflater.closed? && !deflater.finished?
21
+ deflater.finish until deflater.finished?
22
+ end
23
+ deflater.close unless deflater.closed?
24
+ end
25
+ end
data/lib/zip_kit.rb CHANGED
@@ -24,6 +24,7 @@ module ZipKit
24
24
  autoload :WriteShovel, File.dirname(__FILE__) + "/zip_kit/write_shovel.rb"
25
25
  autoload :RackChunkedBody, File.dirname(__FILE__) + "/zip_kit/rack_chunked_body.rb"
26
26
  autoload :RackTempfileBody, File.dirname(__FILE__) + "/zip_kit/rack_tempfile_body.rb"
27
+ autoload :ZlibCleanup, File.dirname(__FILE__) + "/zip_kit/zlib_cleanup.rb"
27
28
 
28
29
  require_relative "zip_kit/railtie" if defined?(::Rails)
29
30
  end
data/rbi/zip_kit.rbi CHANGED
@@ -1,6 +1,6 @@
1
1
  # typed: strong
2
2
  module ZipKit
3
- VERSION = T.let("6.3.1", T.untyped)
3
+ VERSION = T.let("6.3.3", T.untyped)
4
4
 
5
5
  class Railtie < Rails::Railtie
6
6
  end
@@ -106,8 +106,12 @@ module ZipKit
106
106
  # Is used to write ZIP archives without having to read them back or to overwrite
107
107
  # data. It outputs into any object that supports `<<` or `write`, namely:
108
108
  #
109
- # An `Array`, `File`, `IO`, `Socket` and even `String` all can be output destinations
110
- # for the `Streamer`.
109
+ # * `Array` - will contain binary strings
110
+ # * `File` - data will be written to it as it gets generated
111
+ # * `IO` (`Socket`, `StringIO`) - data gets written into it
112
+ # * `String` - in binary encoding and unfrozen - also makes a decent output target
113
+ #
114
+ # or anything else that responds to `#<<` or `#write`.
111
115
  #
112
116
  # You can also combine output through the `Streamer` with direct output to the destination,
113
117
  # all while preserving the correct offsets in the ZIP file structures. This allows usage
@@ -514,6 +518,10 @@ module ZipKit
514
518
  # is likely already on the wire. However, excluding the entry from the central directory of the ZIP
515
519
  # file will allow better-behaved ZIP unarchivers to extract the entries which did store correctly,
516
520
  # provided they read the ZIP from the central directory and not straight-ahead.
521
+ # Rolling back does not perform any writes.
522
+ #
523
+ # `rollback!` gets called for you if an exception is raised inside the block of `write_file`,
524
+ # `write_deflated_file` and `write_stored_file`.
517
525
  #
518
526
  # _@return_ — position in the output stream / ZIP archive
519
527
  #
@@ -670,9 +678,9 @@ module ZipKit
670
678
 
671
679
  # Writes the given data to the output stream
672
680
  #
673
- # _@param_ `d` — the binary string to write (part of the uncompressed file)
674
- sig { params(d: String).returns(T.self_type) }
675
- def <<(d); end
681
+ # _@param_ `string` — the string to write (part of the uncompressed file)
682
+ sig { params(string: String).returns(T.self_type) }
683
+ def <<(string); end
676
684
 
677
685
  # sord omit - no YARD return type given, using untyped
678
686
  # Flushes the writer and recovers the CRC32/size values. It then calls
@@ -680,6 +688,10 @@ module ZipKit
680
688
  sig { returns(T.untyped) }
681
689
  def close; end
682
690
 
691
+ # sord omit - no YARD return type given, using untyped
692
+ sig { returns(T.untyped) }
693
+ def release_resources_on_failure!; end
694
+
683
695
  # Writes the given data to the output stream. Allows the object to be used as
684
696
  # a target for `IO.copy_stream(from, to)`
685
697
  #
@@ -701,6 +713,7 @@ module ZipKit
701
713
  # on the Streamer passed into it once it knows which compression
702
714
  # method should be applied
703
715
  class Heuristic < ZipKit::Streamer::Writable
716
+ include ZipKit::ZlibCleanup
704
717
  BYTES_WRITTEN_THRESHOLD = T.let(128 * 1024, T.untyped)
705
718
  MINIMUM_VIABLE_COMPRESSION = T.let(0.75, T.untyped)
706
719
 
@@ -718,9 +731,25 @@ module ZipKit
718
731
  sig { returns(T.untyped) }
719
732
  def close; end
720
733
 
734
+ # sord omit - no YARD return type given, using untyped
735
+ sig { returns(T.untyped) }
736
+ def release_resources_on_failure!; end
737
+
721
738
  # sord omit - no YARD return type given, using untyped
722
739
  sig { returns(T.untyped) }
723
740
  def decide; end
741
+
742
+ # sord warn - "Zlib::Deflater?" does not appear to be a type
743
+ # This method is used to flush and close the native zlib handles
744
+ # should an archiving routine encounter an error. This is necessary,
745
+ # since otherwise unclosed deflaters may hang around in memory
746
+ # indefinitely, creating leaks.
747
+ #
748
+ # _@param_ `deflater` — the deflater to safely finish and close
749
+ #
750
+ # _@return_ — void
751
+ sig { params(deflater: SORD_ERROR_ZlibDeflater).returns(T.untyped) }
752
+ def safely_dispose_of_incomplete_deflater(deflater); end
724
753
  end
725
754
 
726
755
  # Sends writes to the given `io`, and also registers all the data passing
@@ -749,6 +778,10 @@ module ZipKit
749
778
  sig { returns(T::Hash[T.untyped, T.untyped]) }
750
779
  def finish; end
751
780
 
781
+ # sord omit - no YARD return type given, using untyped
782
+ sig { returns(T.untyped) }
783
+ def release_resources_on_failure!; end
784
+
752
785
  # Writes the given data to the output stream. Allows the object to be used as
753
786
  # a target for `IO.copy_stream(from, to)`
754
787
  #
@@ -764,6 +797,7 @@ module ZipKit
764
797
  # interchangeable with the StoredWriter in terms of interface.
765
798
  class DeflatedWriter
766
799
  include ZipKit::WriteShovel
800
+ include ZipKit::ZlibCleanup
767
801
  CRC32_BUFFER_SIZE = T.let(64 * 1024, T.untyped)
768
802
 
769
803
  # sord omit - no YARD type given for "io", using untyped
@@ -787,6 +821,22 @@ module ZipKit
787
821
  sig { returns(T::Hash[T.untyped, T.untyped]) }
788
822
  def finish; end
789
823
 
824
+ # sord omit - no YARD return type given, using untyped
825
+ sig { returns(T.untyped) }
826
+ def release_resources_on_failure!; end
827
+
828
+ # sord warn - "Zlib::Deflater?" does not appear to be a type
829
+ # This method is used to flush and close the native zlib handles
830
+ # should an archiving routine encounter an error. This is necessary,
831
+ # since otherwise unclosed deflaters may hang around in memory
832
+ # indefinitely, creating leaks.
833
+ #
834
+ # _@param_ `deflater` — the deflater to safely finish and close
835
+ #
836
+ # _@return_ — void
837
+ sig { params(deflater: SORD_ERROR_ZlibDeflater).returns(T.untyped) }
838
+ def safely_dispose_of_incomplete_deflater(deflater); end
839
+
790
840
  # Writes the given data to the output stream. Allows the object to be used as
791
841
  # a target for `IO.copy_stream(from, to)`
792
842
  #
@@ -1712,11 +1762,11 @@ end, T.untyped)
1712
1762
  # Appends the given data to the write buffer, and flushes the buffer into the
1713
1763
  # writable if the buffer size exceeds the `buffer_size` given at initialization
1714
1764
  #
1715
- # _@param_ `data` — data to be written
1765
+ # _@param_ `string` — data to be written
1716
1766
  #
1717
1767
  # _@return_ — self
1718
- sig { params(data: String).returns(T.untyped) }
1719
- def <<(data); end
1768
+ sig { params(string: String).returns(T.untyped) }
1769
+ def <<(string); end
1720
1770
 
1721
1771
  # Explicitly flushes the buffer if it contains anything
1722
1772
  #
@@ -1745,6 +1795,20 @@ end, T.untyped)
1745
1795
  def write(bytes); end
1746
1796
  end
1747
1797
 
1798
+ module ZlibCleanup
1799
+ # sord warn - "Zlib::Deflater?" does not appear to be a type
1800
+ # This method is used to flush and close the native zlib handles
1801
+ # should an archiving routine encounter an error. This is necessary,
1802
+ # since otherwise unclosed deflaters may hang around in memory
1803
+ # indefinitely, creating leaks.
1804
+ #
1805
+ # _@param_ `deflater` — the deflater to safely finish and close
1806
+ #
1807
+ # _@return_ — void
1808
+ sig { params(deflater: SORD_ERROR_ZlibDeflater).returns(T.untyped) }
1809
+ def safely_dispose_of_incomplete_deflater(deflater); end
1810
+ end
1811
+
1748
1812
  # Permits Deflate compression in independent blocks. The workflow is as follows:
1749
1813
  #
1750
1814
  # * Run every block to compress through deflate_chunk, remove the header,