-
Notifications
You must be signed in to change notification settings - Fork 313
Support decompressor plugins #427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e50fa4a
0b7b78d
c897bbd
e072c57
4a4c553
1b6aeb2
d20a683
8f7c5ca
4e28f72
b80ce3c
dd74bc0
4ac8373
2bbcec0
5707c52
cda7127
00b525d
c66277d
456bd4d
2b72683
a5d068d
0b9433c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
module Zip | ||
class DecryptedIo #:nodoc:all | ||
CHUNK_SIZE = 32_768 | ||
|
||
def initialize(io, decrypter) | ||
@io = io | ||
@decrypter = decrypter | ||
end | ||
|
||
def read(length = nil, outbuf = '') | ||
return ((length.nil? || length.zero?) ? "" : nil) if eof | ||
|
||
while length.nil? || (buffer.bytesize < length) | ||
break if input_finished? | ||
buffer << produce_input | ||
end | ||
|
||
outbuf.replace(buffer.slice!(0...(length || output_buffer.bytesize))) | ||
end | ||
|
||
private | ||
|
||
def eof | ||
buffer.empty? && input_finished? | ||
end | ||
|
||
def buffer | ||
@buffer ||= ''.dup | ||
end | ||
|
||
def input_finished? | ||
@io.eof | ||
end | ||
|
||
def produce_input | ||
@decrypter.decrypt(@io.read(CHUNK_SIZE)) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,6 +39,8 @@ module Zip | |
# class. | ||
|
||
class InputStream | ||
CHUNK_SIZE = 32_768 | ||
|
||
include ::Zip::IOExtras::AbstractInputStream | ||
|
||
# Opens the indicated zip file. An exception is thrown | ||
|
@@ -78,16 +80,10 @@ def rewind | |
end | ||
|
||
# Modeled after IO.sysread | ||
def sysread(number_of_bytes = nil, buf = nil) | ||
@decompressor.sysread(number_of_bytes, buf) | ||
end | ||
|
||
def eof | ||
@output_buffer.empty? && @decompressor.eof | ||
def sysread(length = nil, outbuf = '') | ||
@decompressor.read(length, outbuf) | ||
end | ||
|
||
alias :eof? eof | ||
|
||
class << self | ||
# Same as #initialize but if a block is passed the opened | ||
# stream is passed to the block and closed when the block | ||
|
@@ -124,46 +120,54 @@ def get_io(io_or_file, offset = 0) | |
|
||
def open_entry | ||
@current_entry = ::Zip::Entry.read_local_entry(@archive_io) | ||
if @current_entry && @current_entry.gp_flags & 1 == 1 && @decrypter.is_a?(NullEncrypter) | ||
if @current_entry && @current_entry.encrypted? && @decrypter.is_a?(NullEncrypter) | ||
raise Error, 'password required to decode zip file' | ||
end | ||
if @current_entry && @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 \ | ||
if @current_entry && @current_entry.incomplete? && @current_entry.crc == 0 \ | ||
&& @current_entry.compressed_size == 0 \ | ||
&& @current_entry.size == 0 && !@complete_entry | ||
raise GPFBit3Error, | ||
'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \ | ||
'Please use ::Zip::File instead of ::Zip::InputStream' | ||
end | ||
@decrypted_io = get_decrypted_io | ||
@decompressor = get_decompressor | ||
flush | ||
@current_entry | ||
end | ||
|
||
def get_decrypted_io | ||
header = @archive_io.read(@decrypter.header_bytesize) | ||
@decrypter.reset!(header) | ||
|
||
::Zip::DecryptedIo.new(@archive_io, @decrypter) | ||
end | ||
|
||
def get_decompressor | ||
if @current_entry.nil? | ||
::Zip::NullDecompressor | ||
elsif @current_entry.compression_method == ::Zip::Entry::STORED | ||
if @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry | ||
::Zip::PassThruDecompressor.new(@archive_io, @complete_entry.size) | ||
return ::Zip::NullDecompressor if @current_entry.nil? | ||
|
||
decompressed_size = | ||
if @current_entry.incomplete? && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry | ||
@complete_entry.size | ||
else | ||
::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size) | ||
@current_entry.size | ||
end | ||
elsif @current_entry.compression_method == ::Zip::Entry::DEFLATED | ||
header = @archive_io.read(@decrypter.header_bytesize) | ||
@decrypter.reset!(header) | ||
::Zip::Inflater.new(@archive_io, @decrypter) | ||
else | ||
|
||
decompressor_class = ::Zip::Decompressor.find_by_compression_method(@current_entry.compression_method) | ||
if decompressor_class.nil? | ||
raise ::Zip::CompressionMethodError, | ||
"Unsupported compression method #{@current_entry.compression_method}" | ||
end | ||
|
||
decompressor_class.new(@decrypted_io, decompressed_size) | ||
end | ||
|
||
def produce_input | ||
@decompressor.produce_input | ||
@decompressor.read(CHUNK_SIZE) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are quite a few There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the optimal CHUNK_SIZE for decompressors is dependant on compression method. Thus the Decompressor::CHUNK_SIZE should not exist and be replaced by an implementation dependant CHUCK_SIZE in Inflater. Therefore, in InputStream, I think it is better to not depend on this Decompressor::CHUNK_SIZE. |
||
end | ||
|
||
def input_finished? | ||
@decompressor.input_finished? | ||
@decompressor.eof | ||
end | ||
end | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.