@@ -640,25 +640,39 @@ def stream_with_just_write.write(bytes)
640
640
expect ( per_filename [ "stored.txt" ] ) . to eq ( "this is attempt 2" )
641
641
end
642
642
643
- it "correctly rolls back if an exception is raised after the local entry has been written in write_file" do
643
+ it "correctly rolls back if an exception is raised from Writable#close when using write_file" do
644
644
# A Unicode string will not be happy about binary writes
645
645
# and will raise an exception. The exception won't be raised when
646
- # starting the writes, but in `#close` of the Writable. If this is not handled correctly,
647
- # the exception we will get raised won't be the original exception (Encoding::CompatibilityError that
648
- # we need to see - to know what went wrong inside the writing block) but a duplicate zip entry exception.
646
+ # starting the writes, but in `#close` of the Writable with write_file. When the size of the byte
647
+ # stream is small, the decision whether to call write_stored_file or write_deflated_file is going to
648
+ # be deferred until the Writable gets close() called on it. In that situation a correct rollback
649
+ # must be performed.
650
+ # If this is not handled correctly, the exception we will get raised won't be the original exception
651
+ # (Encoding::CompatibilityError that we need to see - to know what went wrong inside the writing block)
652
+ # but a PathSet::Conflict duplicate zip entry exception. Also, the IO offsets will be out of whack.
649
653
# To cause this, we need something that will raise during `Writable#close` - we will use a Unicode string
650
654
# for that purpose. See https://github.com/julik/zip_kit/issues/15
651
- uniсode_str_buf = "Ж"
652
- described_class . open ( uniсode_str_buf ) do |zip |
653
- 4 . times do
654
- begin
655
- zip . write_file ( "deflated.txt" ) do |sink |
656
- sink . write ( "x" )
657
- end
658
- rescue => e
659
- expect ( e ) . to be_kind_of ( Encoding ::CompatibilityError )
655
+ uniсode_str_buf = +"Ж"
656
+
657
+ zip = described_class . new ( uniсode_str_buf )
658
+ 4 . times do
659
+ bytes_before_partial_write = uniсode_str_buf . bytesize
660
+ expect {
661
+ zip . write_file ( "deflated.txt" ) do |sink |
662
+ sink . write ( "x" )
660
663
end
661
- end
664
+ } . to raise_error ( Encoding ::CompatibilityError ) # Should not be a PathSet::Conflict
665
+
666
+ # Ensure there was a partial write
667
+ expect ( uniсode_str_buf . bytesize - bytes_before_partial_write ) . to be > 0
662
668
end
669
+
670
+ # We must force the string into binary so that the ZIP can be closed - we
671
+ # want to write out the EOCD since we want to make sure there is no byte offset
672
+ # mismatch (fillers have been correctly produced)
673
+ uniсode_str_buf . force_encoding ( Encoding ::BINARY )
674
+ expect {
675
+ zip . close
676
+ } . not_to raise_error # Offset validation should pass
663
677
end
664
678
end
0 commit comments