From c5aaa76b11b1287d89817154a37910782b2aff57 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Fri, 29 Nov 2019 18:45:39 +0100 Subject: [PATCH 1/4] Support reading zip files compressed with bzip2 --- README.md | 7 ++ lib/zip.rb | 4 + lib/zip/bzip2/decompress.rb | 104 ++++++++++++++++++ lib/zip/bzip2/errors.rb | 61 ++++++++++ lib/zip/bzip2_decompressor.rb | 57 ++++++++++ lib/zip/entry.rb | 1 + lib/zip/input_stream.rb | 4 + rubyzip.gemspec | 1 + test/bzip2_decompressor_test.rb | 14 +++ test/bzip2_support_test.rb | 34 ++++++ test/data/file1.txt.bz2 | Bin 0 -> 548 bytes test/data/zipWithBzip2Compression.zip | Bin 0 -> 9259 bytes .../zipWithBzip2CompressionAndEncryption.zip | Bin 0 -> 9315 bytes 13 files changed, 287 insertions(+) create mode 100644 lib/zip/bzip2/decompress.rb create mode 100644 lib/zip/bzip2/errors.rb create mode 100644 lib/zip/bzip2_decompressor.rb create mode 100644 test/bzip2_decompressor_test.rb create mode 100644 test/bzip2_support_test.rb create mode 100644 test/data/file1.txt.bz2 create mode 100644 test/data/zipWithBzip2Compression.zip create mode 100644 test/data/zipWithBzip2CompressionAndEncryption.zip diff --git a/README.md b/README.md index 059f22d1..7e1bd8c4 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,13 @@ Or in your Gemfile: gem 'rubyzip' ``` +If you want to read zip files that use the bzip2 compression method, +you must also install the optional dependency `bzip2-ffi`: + +```ruby +gem 'bzip2-ffi' +``` + ## Usage ### Basic zip archive creation diff --git a/lib/zip.rb b/lib/zip.rb index fa382376..e8662d62 100644 --- a/lib/zip.rb +++ b/lib/zip.rb @@ -26,6 +26,10 @@ require 'zip/crypto/traditional_encryption' require 'zip/inflater' require 'zip/deflater' +begin + require 'zip/bzip2_decompressor' +rescue LoadError +end require 'zip/streamable_stream' require 'zip/streamable_directory' require 'zip/constants' diff --git a/lib/zip/bzip2/decompress.rb b/lib/zip/bzip2/decompress.rb new file mode 100644 index 00000000..af3c63d3 --- /dev/null +++ b/lib/zip/bzip2/decompress.rb @@ -0,0 +1,104 @@ +require 'bzip2/ffi' +require 'zip/bzip2/errors' + +module Zip + module Bzip2 + class Decompress + OUT_BUFFER_SIZE = 4096 + + class << self + private + + def finalize(stream) + -> (id) do + res = ::Bzip2::FFI::Libbz2::BZ2_bzDecompressEnd(stream) + check_error(res) + end + end + end + + def initialize(options = {}) + small = options[:small] + + @stream = ::Bzip2::FFI::Libbz2::BzStream.new + @out_eof = false + + res = ::Bzip2::FFI::Libbz2::BZ2_bzDecompressInit(stream, 0, small ? 1 : 0) + check_error(res) + + ObjectSpace.define_finalizer(self, self.class.send(:finalize, stream)) + end + + def decompress(decompress_string) + return nil if @out_eof + + out_buffer = nil + in_buffer = nil + begin + out_buffer = ::FFI::MemoryPointer.new(1, OUT_BUFFER_SIZE) + in_buffer = ::FFI::MemoryPointer.new(1, decompress_string.bytesize) + + in_buffer.write_bytes(decompress_string) + stream[:next_in] = in_buffer + stream[:avail_in] = in_buffer.size + + result = String.new + while stream[:avail_in].positive? + stream[:next_out] = out_buffer + stream[:avail_out] = out_buffer.size + + res = ::Bzip2::FFI::Libbz2::BZ2_bzDecompress(stream) + check_error(res) + + result += out_buffer.read_bytes(out_buffer.size - stream[:avail_out]) + + if res == ::Bzip2::FFI::Libbz2::BZ_STREAM_END + @out_eof = true + + res = ::Bzip2::FFI::Libbz2::BZ2_bzDecompressEnd(stream) + ObjectSpace.undefine_finalizer(self) + check_error(res) + + break + end + end + result + ensure + in_buffer.free if in_buffer + in_buffer = nil + out_buffer.free if out_buffer + out_buffer = nil + end + end + + def finished? + @out_eof + end + + protected + + attr_reader :stream + + private + + def check_error(res) + return res if res >= 0 + + error_class = case res + when ::Bzip2::FFI::Libbz2::BZ_MEM_ERROR + MemError + when ::Bzip2::FFI::Libbz2::BZ_DATA_ERROR + DataError + when ::Bzip2::FFI::Libbz2::BZ_DATA_ERROR_MAGIC + MagicDataError + when ::Bzip2::FFI::Libbz2::BZ_CONFIG_ERROR + ConfigError + else + raise UnexpectedError.new(res) + end + + raise error_class.new + end + end + end +end diff --git a/lib/zip/bzip2/errors.rb b/lib/zip/bzip2/errors.rb new file mode 100644 index 00000000..8d76c9f4 --- /dev/null +++ b/lib/zip/bzip2/errors.rb @@ -0,0 +1,61 @@ +module Zip + module Bzip2 + # Base class for Zip::Bzip2 exceptions. + class Error < IOError + end + + # Raised if a failure occurred allocating memory to complete a request. + class MemError < Error + # Initializes a new instance of MemError. + # + # @private + def initialize #:nodoc: + super('Could not allocate enough memory to perform this request') + end + end + + # Raised if a data integrity error is detected (a mismatch between + # stored and computed CRCs or another anomaly in the compressed data). + class DataError < Error + # Initializes a new instance of DataError. + # + # @param message [String] Exception message (overrides the default). + # @private + def initialize(message = nil) #:nodoc: + super(message || 'Data integrity error detected (mismatch between stored and computed CRCs, or other anomaly in the compressed data)') + end + end + + # Raised if the compressed data does not start with the correct magic + # bytes ('BZh'). + class MagicDataError < DataError + # Initializes a new instance of MagicDataError. + # + # @private + def initialize #:nodoc: + super('Compressed data does not start with the correct magic bytes (\'BZh\')') + end + end + + # Raised if libbz2 detects that it has been improperly compiled. + class ConfigError < DataError + # Initializes a new instance of ConfigError. + # + # @private + def initialize #:nodoc: + super('libbz2 has been improperly compiled on your platform') + end + end + + # Raised if libbz2 reported an unexpected error code. + class UnexpectedError < Error + # Initializes a new instance of UnexpectedError. + # + # @param error_code [Integer] The error_code reported by libbz2. + # @private + def initialize(error_code) #:nodoc: + super("An unexpected error was detected (error code: #{error_code})") + end + end + end +end diff --git a/lib/zip/bzip2_decompressor.rb b/lib/zip/bzip2_decompressor.rb new file mode 100644 index 00000000..96e511eb --- /dev/null +++ b/lib/zip/bzip2_decompressor.rb @@ -0,0 +1,57 @@ +require 'zip/bzip2/decompress' + +module Zip + class Bzip2Decompressor < Decompressor #:nodoc:all + def initialize(input_stream, decrypter = NullDecrypter.new) + super(input_stream) + @bzip2_ffi_decompressor = Bzip2::Decompress.new + @output_buffer = ''.dup + @has_returned_empty_string = false + @decrypter = decrypter + end + + def sysread(number_of_bytes = nil, buf = '') + readEverything = number_of_bytes.nil? + while readEverything || @output_buffer.bytesize < number_of_bytes + break if internal_input_finished? + @output_buffer << internal_produce_input(buf) + end + return value_when_finished if @output_buffer.bytesize == 0 && input_finished? + end_index = number_of_bytes.nil? ? @output_buffer.bytesize : number_of_bytes + @output_buffer.slice!(0...end_index) + end + + def produce_input + if @output_buffer.empty? + internal_produce_input + else + @output_buffer.slice!(0...(@output_buffer.length)) + end + end + + # to be used with produce_input, not read (as read may still have more data cached) + # is data cached anywhere other than @outputBuffer? the comment above may be wrong + def input_finished? + @output_buffer.empty? && internal_input_finished? + end + + alias :eof input_finished? + alias :eof? input_finished? + + private + + def internal_produce_input(buf = '') + @bzip2_ffi_decompressor.decompress(@decrypter.decrypt(@input_stream.read(1024, buf))) + end + + def internal_input_finished? + @bzip2_ffi_decompressor.finished? + end + + def value_when_finished # mimic behaviour of ruby File object. + return if @has_returned_empty_string + @has_returned_empty_string = true + '' + end + end +end diff --git a/lib/zip/entry.rb b/lib/zip/entry.rb index 37b1690b..f4f0babe 100644 --- a/lib/zip/entry.rb +++ b/lib/zip/entry.rb @@ -3,6 +3,7 @@ module Zip class Entry STORED = 0 DEFLATED = 8 + BZIP2ED = 12 # Language encoding flag (EFS) bit EFS = 0b100000000000 diff --git a/lib/zip/input_stream.rb b/lib/zip/input_stream.rb index b9c35111..c0b80a0c 100644 --- a/lib/zip/input_stream.rb +++ b/lib/zip/input_stream.rb @@ -152,6 +152,10 @@ def get_decompressor header = @archive_io.read(@decrypter.header_bytesize) @decrypter.reset!(header) ::Zip::Inflater.new(@archive_io, @decrypter) + elsif @current_entry.compression_method == ::Zip::Entry::BZIP2ED && defined?(::Zip::Bzip2Decompressor) + header = @archive_io.read(@decrypter.header_bytesize) + @decrypter.reset!(header) + ::Zip::Bzip2Decompressor.new(@archive_io, @decrypter) else raise ::Zip::CompressionMethodError, "Unsupported compression method #{@current_entry.compression_method}" diff --git a/rubyzip.gemspec b/rubyzip.gemspec index f8c59a18..ec391dd7 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -23,6 +23,7 @@ Gem::Specification.new do |s| 'wiki_uri' => 'https://github.com/rubyzip/rubyzip/wiki' } s.required_ruby_version = '>= 2.4' + s.add_development_dependency 'bzip2-ffi', '~> 1.0' s.add_development_dependency 'rake', '~> 10.3' s.add_development_dependency 'pry', '~> 0.10' s.add_development_dependency 'minitest', '~> 5.4' diff --git a/test/bzip2_decompressor_test.rb b/test/bzip2_decompressor_test.rb new file mode 100644 index 00000000..a54c9272 --- /dev/null +++ b/test/bzip2_decompressor_test.rb @@ -0,0 +1,14 @@ +require 'test_helper' +class Bzip2DecompressorTest < MiniTest::Test + include DecompressorTests + + def setup + super + @file = File.new('test/data/file1.txt.bz2', 'rb') + @decompressor = ::Zip::Bzip2Decompressor.new(@file) + end + + def teardown + @file.close + end +end diff --git a/test/bzip2_support_test.rb b/test/bzip2_support_test.rb new file mode 100644 index 00000000..741f3bfe --- /dev/null +++ b/test/bzip2_support_test.rb @@ -0,0 +1,34 @@ +require 'test_helper' + +class Bzip2SupportTest < MiniTest::Test + BZIP2_ZIP_TEST_FILE = 'test/data/zipWithBzip2Compression.zip' + ENCRYPTED_BZIP2_ZIP_TEST_FILE = 'test/data/zipWithBzip2CompressionAndEncryption.zip' + INPUT_FILE1 = 'test/data/file1.txt' + INPUT_FILE2 = 'test/data/file2.txt' + + def test_read + Zip::InputStream.open(BZIP2_ZIP_TEST_FILE) do |zis| + entry = zis.get_next_entry + assert_equal 'file1.txt', entry.name + assert_equal 1327, entry.size + assert_equal open(INPUT_FILE1, 'r').read, zis.read + entry = zis.get_next_entry + assert_equal 'file2.txt', entry.name + assert_equal 41234, entry.size + assert_equal open(INPUT_FILE2, 'r').read, zis.read + end + end + + def test_encrypted_read + Zip::InputStream.open(ENCRYPTED_BZIP2_ZIP_TEST_FILE, 0, Zip::TraditionalDecrypter.new('password')) do |zis| + entry = zis.get_next_entry + assert_equal 'file1.txt', entry.name + assert_equal 1327, entry.size + assert_equal open(INPUT_FILE1, 'r').read, zis.read + entry = zis.get_next_entry + assert_equal 'file2.txt', entry.name + assert_equal 41234, entry.size + assert_equal open(INPUT_FILE2, 'r').read, zis.read + end + end +end diff --git a/test/data/file1.txt.bz2 b/test/data/file1.txt.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..b803ddf76603c979c21d2a66641097626a6bcd6d GIT binary patch literal 548 zcmV+<0^9vUT4*^jL0KkKSsIVn3IG5a|A2q65CBYX01|(%-bKIf-ta&I8JKrkFqBb* zX)vdxZ3qJoA01W^JmF8duHZjN7ed=`SQJy;<6`7kjAFK*Qoj5#7DSpBN}IaG6xqgy5d#ebFBw%3m)o+iqt4~4Q>!3uCGl#O*412CJo(r{O{oKYr)jgwvaM87^0P=5RKmhW zJ;_lb!y0HX5jbXj?&!RfCo&Lrn3|H)A-xwkPbf{pGxP~tk!>fna-iZ}?=yTw@v9``QWiEB*DtP~av!2oSC0}{Y74I+z~T4ge!5Y!qb%MJdL_Dnf7ykp$ofkkA|?qguj>TuG25Bmqc?Gh+~hAyrv~ m0I(2NI@oHZS!s6oMY3v!zFWr8MN&!v{}*yaI8cxpkJt(>x#NQX literal 0 HcmV?d00001 diff --git a/test/data/zipWithBzip2Compression.zip b/test/data/zipWithBzip2Compression.zip new file mode 100644 index 0000000000000000000000000000000000000000..1cd268b31eb9c26dc3120c29e1f0981b96cb59a4 GIT binary patch literal 9259 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 Ti}#;s5tKfC_{*p~EA literal 0 HcmV?d00001 diff --git a/test/data/zipWithBzip2CompressionAndEncryption.zip b/test/data/zipWithBzip2CompressionAndEncryption.zip new file mode 100644 index 0000000000000000000000000000000000000000..41c46e41a9e1b1975b46b4a458c32fe595e38b1e GIT binary patch literal 9315 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 literal 0 HcmV?d00001 From 2c76353b8f0b3a4d719d167b3e6eb93f85d6604f Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Fri, 29 Nov 2019 19:41:20 +0100 Subject: [PATCH 2/4] Do not depend on bzip2-ffi for bzip2 decompression --- README.md | 4 +- lib/zip/bzip2/decompress.rb | 22 ++++---- lib/zip/bzip2/libbz2.rb | 100 ++++++++++++++++++++++++++++++++++++ rubyzip.gemspec | 2 +- 4 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 lib/zip/bzip2/libbz2.rb diff --git a/README.md b/README.md index 7e1bd8c4..366ad5e5 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,10 @@ gem 'rubyzip' ``` If you want to read zip files that use the bzip2 compression method, -you must also install the optional dependency `bzip2-ffi`: +you must also install the optional dependency `ffi`: ```ruby -gem 'bzip2-ffi' +gem 'ffi' ``` ## Usage diff --git a/lib/zip/bzip2/decompress.rb b/lib/zip/bzip2/decompress.rb index af3c63d3..8d13eddb 100644 --- a/lib/zip/bzip2/decompress.rb +++ b/lib/zip/bzip2/decompress.rb @@ -1,4 +1,4 @@ -require 'bzip2/ffi' +require 'zip/bzip2/libbz2' require 'zip/bzip2/errors' module Zip @@ -11,7 +11,7 @@ class << self def finalize(stream) -> (id) do - res = ::Bzip2::FFI::Libbz2::BZ2_bzDecompressEnd(stream) + res = Libbz2::BZ2_bzDecompressEnd(stream) check_error(res) end end @@ -20,10 +20,10 @@ def finalize(stream) def initialize(options = {}) small = options[:small] - @stream = ::Bzip2::FFI::Libbz2::BzStream.new + @stream = Libbz2::BzStream.new @out_eof = false - res = ::Bzip2::FFI::Libbz2::BZ2_bzDecompressInit(stream, 0, small ? 1 : 0) + res = Libbz2::BZ2_bzDecompressInit(stream, 0, small ? 1 : 0) check_error(res) ObjectSpace.define_finalizer(self, self.class.send(:finalize, stream)) @@ -47,15 +47,15 @@ def decompress(decompress_string) stream[:next_out] = out_buffer stream[:avail_out] = out_buffer.size - res = ::Bzip2::FFI::Libbz2::BZ2_bzDecompress(stream) + res = Libbz2::BZ2_bzDecompress(stream) check_error(res) result += out_buffer.read_bytes(out_buffer.size - stream[:avail_out]) - if res == ::Bzip2::FFI::Libbz2::BZ_STREAM_END + if res == Libbz2::BZ_STREAM_END @out_eof = true - res = ::Bzip2::FFI::Libbz2::BZ2_bzDecompressEnd(stream) + res = Libbz2::BZ2_bzDecompressEnd(stream) ObjectSpace.undefine_finalizer(self) check_error(res) @@ -85,13 +85,13 @@ def check_error(res) return res if res >= 0 error_class = case res - when ::Bzip2::FFI::Libbz2::BZ_MEM_ERROR + when Libbz2::BZ_MEM_ERROR MemError - when ::Bzip2::FFI::Libbz2::BZ_DATA_ERROR + when Libbz2::BZ_DATA_ERROR DataError - when ::Bzip2::FFI::Libbz2::BZ_DATA_ERROR_MAGIC + when Libbz2::BZ_DATA_ERROR_MAGIC MagicDataError - when ::Bzip2::FFI::Libbz2::BZ_CONFIG_ERROR + when Libbz2::BZ_CONFIG_ERROR ConfigError else raise UnexpectedError.new(res) diff --git a/lib/zip/bzip2/libbz2.rb b/lib/zip/bzip2/libbz2.rb new file mode 100644 index 00000000..92efb992 --- /dev/null +++ b/lib/zip/bzip2/libbz2.rb @@ -0,0 +1,100 @@ +# This file is copied from: +# +# https://github.com/philr/bzip2-ffi/raw/master/lib/bzip2/ffi/libbz2.rb +# + +# Copyright (c) 2015-2016 Philip Ross +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is furnished to do +# so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +require 'ffi' + +module Zip + module Bzip2 + # FFI bindings for the libbz2 low-level interface. + # + # See bzlib.h and http://bzip.org/docs.html. + # + # @private + module Libbz2 #:nodoc: + extend ::FFI::Library + + ffi_lib ['bz2', 'libbz2.so.1', 'libbz2.dll'] + + BZ_RUN = 0 + BZ_FLUSH = 1 + BZ_FINISH = 2 + + BZ_OK = 0 + BZ_RUN_OK = 1 + BZ_FLUSH_OK = 2 + BZ_FINISH_OK = 3 + BZ_STREAM_END = 4 + BZ_SEQUENCE_ERROR = -1 + BZ_PARAM_ERROR = -2 + BZ_MEM_ERROR = -3 + BZ_DATA_ERROR = -4 + BZ_DATA_ERROR_MAGIC = -5 + BZ_CONFIG_ERROR = -9 + + # void *(*bzalloc)(void *,int,int); + callback :bzalloc, [:pointer, :int, :int], :pointer + + # void (*bzfree)(void *,void *); + callback :bzfree, [:pointer, :pointer], :void + + # typedef struct { ... } bz_stream; + class BzStream < ::FFI::Struct #:nodoc: + layout :next_in, :pointer, + :avail_in, :uint, + :total_in_lo32, :uint, + :total_in_hi32, :uint, + + :next_out, :pointer, + :avail_out, :uint, + :total_out_lo32, :uint, + :total_out_hi32, :uint, + + :state, :pointer, + + :bzalloc, :bzalloc, + :bzfree, :bzfree, + :opaque, :pointer + end + + # int BZ2_bzCompressInt(bz_stream* strm, int blockSize100k, int verbosity, int workFactor); + attach_function :BZ2_bzCompressInit, [BzStream.by_ref, :int, :int, :int], :int + + # int BZ2_bzCompress (bz_stream* strm, int action); + attach_function :BZ2_bzCompress, [BzStream.by_ref, :int], :int + + # int BZ2_bzCompressEnd (bz_stream* strm); + attach_function :BZ2_bzCompressEnd, [BzStream.by_ref], :int + + # int BZ2_bzDecompressInit (bz_stream *strm, int verbosity, int small); + attach_function :BZ2_bzDecompressInit, [BzStream.by_ref, :int, :int], :int + + # int BZ2_bzDecompress (bz_stream* strm); + attach_function :BZ2_bzDecompress, [BzStream.by_ref], :int + + # int BZ2_bzDecompressEnd (bz_stream *strm); + attach_function :BZ2_bzDecompressEnd, [BzStream.by_ref], :int + end + end +end diff --git a/rubyzip.gemspec b/rubyzip.gemspec index ec391dd7..add26bd0 100644 --- a/rubyzip.gemspec +++ b/rubyzip.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| 'wiki_uri' => 'https://github.com/rubyzip/rubyzip/wiki' } s.required_ruby_version = '>= 2.4' - s.add_development_dependency 'bzip2-ffi', '~> 1.0' + s.add_development_dependency 'ffi', '~> 1.0' s.add_development_dependency 'rake', '~> 10.3' s.add_development_dependency 'pry', '~> 0.10' s.add_development_dependency 'minitest', '~> 5.4' From ae82a90bd3d551b150625ff911fb6bcf6648f890 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 1 Dec 2019 17:41:46 +0100 Subject: [PATCH 3/4] Catch bzip2 decompress errors, re-raise as ::Zip::DecompressionError --- lib/zip/bzip2_decompressor.rb | 2 ++ lib/zip/errors.rb | 1 + test/bzip2_decompressor_test.rb | 8 ++++++++ test/data/file1.txt.corrupt.bz2 | Bin 0 -> 548 bytes 4 files changed, 11 insertions(+) create mode 100644 test/data/file1.txt.corrupt.bz2 diff --git a/lib/zip/bzip2_decompressor.rb b/lib/zip/bzip2_decompressor.rb index 96e511eb..e693464c 100644 --- a/lib/zip/bzip2_decompressor.rb +++ b/lib/zip/bzip2_decompressor.rb @@ -42,6 +42,8 @@ def input_finished? def internal_produce_input(buf = '') @bzip2_ffi_decompressor.decompress(@decrypter.decrypt(@input_stream.read(1024, buf))) + rescue Bzip2::Error => e + raise DecompressionError, e.message end def internal_input_finished? diff --git a/lib/zip/errors.rb b/lib/zip/errors.rb index 364c6eee..0ff0e1e1 100644 --- a/lib/zip/errors.rb +++ b/lib/zip/errors.rb @@ -7,6 +7,7 @@ class EntryNameError < Error; end class EntrySizeError < Error; end class InternalError < Error; end class GPFBit3Error < Error; end + class DecompressionError < Error; end # Backwards compatibility with v1 (delete in v2) ZipError = Error diff --git a/test/bzip2_decompressor_test.rb b/test/bzip2_decompressor_test.rb index a54c9272..75e29398 100644 --- a/test/bzip2_decompressor_test.rb +++ b/test/bzip2_decompressor_test.rb @@ -8,6 +8,14 @@ def setup @decompressor = ::Zip::Bzip2Decompressor.new(@file) end + def test_data_error + file = File.new('test/data/file1.txt.corrupt.bz2', 'rb') + decompressor = ::Zip::Bzip2Decompressor.new(file) + assert_raises(::Zip::DecompressionError) do + decompressor.sysread + end + end + def teardown @file.close end diff --git a/test/data/file1.txt.corrupt.bz2 b/test/data/file1.txt.corrupt.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..5b1ad090bac471e0464a483c04723a2e0dc73a69 GIT binary patch literal 548 zcmV+<0^9vUT4*^jL0KkKSsIVn3IG5aSXfwCSXfwCSXfwCSXjUB-ta&I8JKrkFqBb* zX)vdxZ3qJoA01W^JmF8duHZjN7ed=`SQJy;<6`7kjAFK*Qoj5#7DSpBN}IaG6xqgy5d#ebFBw%3m)o+iqt4~4Q>!3uCGl#O*412CJo(r{O{oKYr)jgwvaM87^0P=5RKmhW zJ;_lb!y0HX5jbXj?&!RfCo&Lrn3|H)A-xwkPbf{pGxP~tk!>fna-iZ}?=yTw@v9``QWiEB*DtP~av!2oSC0}{Y74I+z~T4ge!5Y!qb%MJdL_Dnf7ykp$ofkkA|?qguj>TuG25Bmqc?Gh+~hAyrv~ m0I(2NI@oHZS!s6oMY3v!zFWr8MN&!v{}*yaI8cxpkJt(a6ykRP literal 0 HcmV?d00001 From 39eec09a6e83353e03cbf8c2b2198beb1db31652 Mon Sep 17 00:00:00 2001 From: Jan-Joost Spanjers Date: Sun, 1 Dec 2019 18:01:26 +0100 Subject: [PATCH 4/4] Add tests for bzip2 errors --- test/bzip2_errors_test.rb | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/bzip2_errors_test.rb diff --git a/test/bzip2_errors_test.rb b/test/bzip2_errors_test.rb new file mode 100644 index 00000000..625c4dd6 --- /dev/null +++ b/test/bzip2_errors_test.rb @@ -0,0 +1,35 @@ +# encoding: utf-8 + +require 'test_helper' + +class Bzip2ErrorsTest < MiniTest::Test + def test_bzip2_error + raise ::Zip::Bzip2::Error + rescue ::Zip::Bzip2::Error + end + + def test_bzip2_mem_error + raise ::Zip::Bzip2::MemError + rescue ::Zip::Bzip2::Error + end + + def test_bzip2_data_error + raise ::Zip::Bzip2::DataError + rescue ::Zip::Bzip2::Error + end + + def test_bzip2_magic_data_error + raise ::Zip::Bzip2::MagicDataError + rescue ::Zip::Bzip2::Error + end + + def test_bzip2_config_error + raise ::Zip::Bzip2::ConfigError + rescue ::Zip::Bzip2::Error + end + + def test_bzip2_unexpected_error + raise ::Zip::Bzip2::UnexpectedError, -999 + rescue ::Zip::Bzip2::Error + end +end