2010-11-30 16:27:59 +08:00
|
|
|
module Zip
|
2013-08-30 04:50:12 +08:00
|
|
|
# InputStream is the basic class for reading zip entries in a
|
|
|
|
# zip file. It is possible to create a InputStream object directly,
|
2014-01-19 19:45:58 +08:00
|
|
|
# passing the zip file name to the constructor, but more often than not
|
2013-08-30 04:50:12 +08:00
|
|
|
# the InputStream will be obtained from a File (perhaps using the
|
2014-01-19 19:45:58 +08:00
|
|
|
# ZipFileSystem interface) object for a particular entry in the zip
|
2010-11-30 16:27:59 +08:00
|
|
|
# archive.
|
|
|
|
#
|
2013-08-30 04:50:12 +08:00
|
|
|
# A InputStream inherits IOExtras::AbstractInputStream in order
|
2014-01-19 19:45:58 +08:00
|
|
|
# to provide an IO-like interface for reading from a single zip
|
|
|
|
# entry. Beyond methods for mimicking an IO-object it contains
|
|
|
|
# the method get_next_entry for iterating through the entries of
|
2013-08-30 04:50:12 +08:00
|
|
|
# an archive. get_next_entry returns a Entry object that describes
|
|
|
|
# the zip entry the InputStream is currently reading from.
|
2010-11-30 16:27:59 +08:00
|
|
|
#
|
2014-01-19 19:45:58 +08:00
|
|
|
# Example that creates a zip archive with ZipOutputStream and reads it
|
2013-08-30 04:50:12 +08:00
|
|
|
# back again with a InputStream.
|
2010-11-30 16:27:59 +08:00
|
|
|
#
|
2013-08-30 04:50:12 +08:00
|
|
|
# require 'zip'
|
2014-01-19 19:45:58 +08:00
|
|
|
#
|
2013-08-30 04:50:12 +08:00
|
|
|
# Zip::OutputStream.open("my.zip") do |io|
|
2014-01-19 19:45:58 +08:00
|
|
|
#
|
2010-11-30 16:27:59 +08:00
|
|
|
# io.put_next_entry("first_entry.txt")
|
|
|
|
# io.write "Hello world!"
|
2014-01-19 19:45:58 +08:00
|
|
|
#
|
2010-11-30 16:27:59 +08:00
|
|
|
# io.put_next_entry("adir/first_entry.txt")
|
|
|
|
# io.write "Hello again!"
|
2013-08-30 04:50:12 +08:00
|
|
|
# end
|
2010-11-30 16:27:59 +08:00
|
|
|
#
|
2014-01-19 19:45:58 +08:00
|
|
|
#
|
2013-08-30 04:50:12 +08:00
|
|
|
# Zip::InputStream.open("my.zip") do |io|
|
2014-01-19 19:45:58 +08:00
|
|
|
#
|
2010-11-30 16:27:59 +08:00
|
|
|
# while (entry = io.get_next_entry)
|
|
|
|
# puts "Contents of #{entry.name}: '#{io.read}'"
|
|
|
|
# end
|
2013-08-30 04:50:12 +08:00
|
|
|
# end
|
2010-11-30 16:27:59 +08:00
|
|
|
#
|
2014-01-19 19:45:58 +08:00
|
|
|
# java.util.zip.ZipInputStream is the original inspiration for this
|
2010-11-30 16:27:59 +08:00
|
|
|
# class.
|
|
|
|
|
2013-06-03 15:56:24 +08:00
|
|
|
class InputStream
|
2019-12-24 02:56:34 +08:00
|
|
|
CHUNK_SIZE = 32_768
|
|
|
|
|
2013-06-03 02:33:03 +08:00
|
|
|
include ::Zip::IOExtras::AbstractInputStream
|
2010-11-30 16:27:59 +08:00
|
|
|
|
|
|
|
# Opens the indicated zip file. An exception is thrown
|
|
|
|
# if the specified offset in the specified filename is
|
|
|
|
# not a local zip entry header.
|
2013-08-30 04:50:12 +08:00
|
|
|
#
|
|
|
|
# @param context [String||IO||StringIO] file path or IO/StringIO object
|
|
|
|
# @param offset [Integer] offset in the IO/StringIO
|
2015-01-04 03:03:46 +08:00
|
|
|
def initialize(context, offset = 0, decrypter = nil)
|
2010-11-30 16:27:59 +08:00
|
|
|
super()
|
2020-02-09 18:52:06 +08:00
|
|
|
@archive_io = get_io(context, offset)
|
2013-08-30 04:50:12 +08:00
|
|
|
@decompressor = ::Zip::NullDecompressor
|
2015-01-04 03:03:46 +08:00
|
|
|
@decrypter = decrypter || ::Zip::NullDecrypter.new
|
2013-08-30 04:50:12 +08:00
|
|
|
@current_entry = nil
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-08-30 04:50:12 +08:00
|
|
|
def close
|
|
|
|
@archive_io.close
|
2012-01-26 21:50:25 +08:00
|
|
|
end
|
|
|
|
|
2013-08-30 04:50:12 +08:00
|
|
|
# Returns a Entry object. It is necessary to call this
|
|
|
|
# method on a newly created InputStream before reading from
|
2014-01-19 19:45:58 +08:00
|
|
|
# the first entry in the archive. Returns nil when there are
|
2010-11-30 16:27:59 +08:00
|
|
|
# no more entries.
|
|
|
|
def get_next_entry
|
2013-08-30 04:50:12 +08:00
|
|
|
@archive_io.seek(@current_entry.next_header_offset, IO::SEEK_SET) if @current_entry
|
2010-11-30 16:27:59 +08:00
|
|
|
open_entry
|
|
|
|
end
|
|
|
|
|
|
|
|
# Rewinds the stream to the beginning of the current entry
|
|
|
|
def rewind
|
2013-08-30 04:50:12 +08:00
|
|
|
return if @current_entry.nil?
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
@lineno = 0
|
2013-08-30 04:50:12 +08:00
|
|
|
@pos = 0
|
|
|
|
@archive_io.seek(@current_entry.local_header_offset, IO::SEEK_SET)
|
2010-11-30 16:27:59 +08:00
|
|
|
open_entry
|
|
|
|
end
|
|
|
|
|
|
|
|
# Modeled after IO.sysread
|
2020-01-05 23:06:05 +08:00
|
|
|
def sysread(length = nil, outbuf = '')
|
|
|
|
@decompressor.read(length, outbuf)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2013-08-30 04:50:12 +08:00
|
|
|
class << self
|
|
|
|
# Same as #initialize but if a block is passed the opened
|
|
|
|
# stream is passed to the block and closed when the block
|
|
|
|
# returns.
|
2015-01-04 03:03:46 +08:00
|
|
|
def open(filename_or_io, offset = 0, decrypter = nil)
|
2015-03-23 00:30:24 +08:00
|
|
|
zio = new(filename_or_io, offset, decrypter)
|
2013-08-30 04:50:12 +08:00
|
|
|
return zio unless block_given?
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2013-08-30 04:50:12 +08:00
|
|
|
begin
|
|
|
|
yield zio
|
|
|
|
ensure
|
|
|
|
zio.close if zio
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def open_buffer(filename_or_io, offset = 0)
|
2019-10-12 02:31:42 +08:00
|
|
|
warn 'open_buffer is deprecated!!! Use open instead!'
|
2019-09-14 22:56:02 +08:00
|
|
|
::Zip::InputStream.open(filename_or_io, offset)
|
2013-08-30 04:50:12 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
protected
|
|
|
|
|
2013-08-30 04:50:12 +08:00
|
|
|
def get_io(io_or_file, offset = 0)
|
2015-09-03 21:16:32 +08:00
|
|
|
if io_or_file.respond_to?(:seek)
|
2014-01-19 19:45:58 +08:00
|
|
|
io = io_or_file.dup
|
|
|
|
io.seek(offset, ::IO::SEEK_SET)
|
|
|
|
io
|
2010-11-30 16:27:59 +08:00
|
|
|
else
|
2013-08-30 04:50:12 +08:00
|
|
|
file = ::File.open(io_or_file, 'rb')
|
|
|
|
file.seek(offset, ::IO::SEEK_SET)
|
|
|
|
file
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2013-08-30 04:50:12 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def open_entry
|
|
|
|
@current_entry = ::Zip::Entry.read_local_entry(@archive_io)
|
2019-12-21 17:38:26 +08:00
|
|
|
if @current_entry && @current_entry.encrypted? && @decrypter.is_a?(NullEncrypter)
|
2014-12-31 00:03:17 +08:00
|
|
|
raise Error, 'password required to decode zip file'
|
|
|
|
end
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2019-12-21 17:41:24 +08:00
|
|
|
if @current_entry && @current_entry.incomplete? && @current_entry.crc == 0 \
|
2015-02-17 03:51:44 +08:00
|
|
|
&& @current_entry.compressed_size == 0 \
|
2018-04-04 04:07:18 +08:00
|
|
|
&& @current_entry.size == 0 && !@complete_entry
|
2015-02-17 03:51:44 +08:00
|
|
|
raise GPFBit3Error,
|
2015-03-24 00:03:28 +08:00
|
|
|
'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \
|
2015-03-21 15:44:56 +08:00
|
|
|
'Please use ::Zip::File instead of ::Zip::InputStream'
|
2015-02-17 03:51:44 +08:00
|
|
|
end
|
2019-12-21 22:04:43 +08:00
|
|
|
@decrypted_io = get_decrypted_io
|
2017-06-29 10:57:12 +08:00
|
|
|
@decompressor = get_decompressor
|
2010-11-30 16:27:59 +08:00
|
|
|
flush
|
2013-08-30 04:50:12 +08:00
|
|
|
@current_entry
|
|
|
|
end
|
|
|
|
|
2019-12-21 22:04:43 +08:00
|
|
|
def get_decrypted_io
|
|
|
|
header = @archive_io.read(@decrypter.header_bytesize)
|
|
|
|
@decrypter.reset!(header)
|
|
|
|
|
|
|
|
::Zip::DecryptedIo.new(@archive_io, @decrypter)
|
|
|
|
end
|
|
|
|
|
2013-08-30 04:50:12 +08:00
|
|
|
def get_decompressor
|
2019-12-21 22:04:43 +08:00
|
|
|
return ::Zip::NullDecompressor if @current_entry.nil?
|
|
|
|
|
2019-12-21 03:01:53 +08:00
|
|
|
decompressed_size =
|
2019-12-21 17:41:24 +08:00
|
|
|
if @current_entry.incomplete? && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry
|
2019-12-21 03:01:53 +08:00
|
|
|
@complete_entry.size
|
2018-04-04 04:07:18 +08:00
|
|
|
else
|
2019-12-21 03:01:53 +08:00
|
|
|
@current_entry.size
|
2018-04-04 04:07:18 +08:00
|
|
|
end
|
2019-12-21 03:01:53 +08:00
|
|
|
|
|
|
|
decompressor_class = ::Zip::Decompressor.find_by_compression_method(@current_entry.compression_method)
|
|
|
|
if decompressor_class.nil?
|
2014-01-24 17:37:38 +08:00
|
|
|
raise ::Zip::CompressionMethodError,
|
2013-08-30 04:50:12 +08:00
|
|
|
"Unsupported compression method #{@current_entry.compression_method}"
|
|
|
|
end
|
2019-12-21 03:01:53 +08:00
|
|
|
|
|
|
|
decompressor_class.new(@decrypted_io, decompressed_size)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def produce_input
|
2020-01-05 23:06:05 +08:00
|
|
|
@decompressor.read(CHUNK_SIZE)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def input_finished?
|
2019-12-23 23:54:56 +08:00
|
|
|
@decompressor.eof
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
|
|
|
# rubyzip is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the ruby license.
|