rubyzip/lib/zip/input_stream.rb

181 lines
5.5 KiB
Ruby
Raw Normal View History

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
# 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.
#
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.
#
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
#
# io.put_next_entry("first_entry.txt")
# io.write "Hello world!"
2014-01-19 19:45:58 +08:00
#
# io.put_next_entry("adir/first_entry.txt")
# io.write "Hello again!"
2013-08-30 04:50:12 +08:00
# end
#
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
#
# while (entry = io.get_next_entry)
# puts "Contents of #{entry.name}: '#{io.read}'"
# end
2013-08-30 04:50:12 +08:00
# end
#
2014-01-19 19:45:58 +08:00
# java.util.zip.ZipInputStream is the original inspiration for this
# class.
class InputStream
CHUNK_SIZE = 32_768
2013-06-03 02:33:03 +08:00
include ::Zip::IOExtras::AbstractInputStream
# 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
def initialize(context, offset = 0, decrypter = nil)
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
@decrypter = decrypter || ::Zip::NullDecrypter.new
2013-08-30 04:50:12 +08:00
@current_entry = nil
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
# 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
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?
@lineno = 0
2013-08-30 04:50:12 +08:00
@pos = 0
@archive_io.seek(@current_entry.local_header_offset, IO::SEEK_SET)
open_entry
end
# Modeled after IO.sysread
2020-01-05 23:06:05 +08:00
def sysread(length = nil, outbuf = '')
@decompressor.read(length, outbuf)
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.
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?
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)
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
protected
2013-08-30 04:50:12 +08:00
def get_io(io_or_file, offset = 0)
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
else
2013-08-30 04:50:12 +08:00
file = ::File.open(io_or_file, 'rb')
file.seek(offset, ::IO::SEEK_SET)
file
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
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 \
&& @current_entry.size == 0 && !@complete_entry
2015-02-17 03:51:44 +08:00
raise GPFBit3Error,
'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
@decrypted_io = get_decrypted_io
2017-06-29 10:57:12 +08:00
@decompressor = get_decompressor
flush
2013-08-30 04:50:12 +08:00
@current_entry
end
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
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
else
2019-12-21 03:01:53 +08:00
@current_entry.size
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)
end
def produce_input
2020-01-05 23:06:05 +08:00
@decompressor.read(CHUNK_SIZE)
end
def input_finished?
@decompressor.eof
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.