2021-05-24 01:24:22 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
module Zip
|
2011-01-07 21:23:22 +08:00
|
|
|
# ZipOutputStream is the basic class for writing zip files. It is
|
|
|
|
# possible to create a ZipOutputStream object directly, passing
|
|
|
|
# the zip file name to the constructor, but more often than not
|
|
|
|
# the ZipOutputStream will be obtained from a ZipFile (perhaps using the
|
|
|
|
# ZipFileSystem interface) object for a particular entry in the zip
|
2010-11-30 16:27:59 +08:00
|
|
|
# archive.
|
|
|
|
#
|
2011-01-07 21:23:22 +08:00
|
|
|
# A ZipOutputStream inherits IOExtras::AbstractOutputStream in order
|
|
|
|
# to provide an IO-like interface for writing to a single zip
|
|
|
|
# entry. Beyond methods for mimicking an IO-object it contains
|
|
|
|
# the method put_next_entry that closes the current entry
|
2010-11-30 16:27:59 +08:00
|
|
|
# and creates a new.
|
|
|
|
#
|
|
|
|
# Please refer to ZipInputStream for example code.
|
|
|
|
#
|
2011-01-07 21:23:22 +08:00
|
|
|
# java.util.zip.ZipOutputStream is the original inspiration for this
|
2010-11-30 16:27:59 +08:00
|
|
|
# class.
|
|
|
|
|
2013-06-03 15:56:24 +08:00
|
|
|
class OutputStream
|
2013-06-03 02:33:03 +08:00
|
|
|
include ::Zip::IOExtras::AbstractOutputStream
|
2010-11-30 16:27:59 +08:00
|
|
|
|
|
|
|
attr_accessor :comment
|
|
|
|
|
|
|
|
# Opens the indicated zip file. If a file with that name already
|
|
|
|
# exists it will be overwritten.
|
2021-06-09 00:25:22 +08:00
|
|
|
def initialize(file_name, stream: false, encrypter: nil)
|
2010-11-30 16:27:59 +08:00
|
|
|
super()
|
2014-01-19 19:45:58 +08:00
|
|
|
@file_name = file_name
|
|
|
|
@output_stream = if stream
|
2021-06-04 23:51:09 +08:00
|
|
|
iostream = Zip::RUNNING_ON_WINDOWS ? @file_name : @file_name.dup
|
2014-12-18 05:16:02 +08:00
|
|
|
iostream.reopen(@file_name)
|
2014-01-19 20:06:54 +08:00
|
|
|
iostream.rewind
|
|
|
|
iostream
|
2014-01-19 19:45:58 +08:00
|
|
|
else
|
2015-03-21 16:27:44 +08:00
|
|
|
::File.new(@file_name, 'wb')
|
2014-01-19 19:45:58 +08:00
|
|
|
end
|
2013-06-03 15:56:24 +08:00
|
|
|
@entry_set = ::Zip::EntrySet.new
|
|
|
|
@compressor = ::Zip::NullCompressor.instance
|
2015-01-04 03:03:46 +08:00
|
|
|
@encrypter = encrypter || ::Zip::NullEncrypter.new
|
2010-11-30 16:27:59 +08:00
|
|
|
@closed = false
|
2013-08-30 04:50:12 +08:00
|
|
|
@current_entry = nil
|
2010-11-30 16:27:59 +08:00
|
|
|
@comment = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
# Same as #initialize but if a block is passed the opened
|
|
|
|
# stream is passed to the block and closed when the block
|
2011-01-07 21:23:22 +08:00
|
|
|
# returns.
|
2013-06-03 15:56:24 +08:00
|
|
|
class << self
|
2021-06-09 00:32:29 +08:00
|
|
|
def open(file_name, encrypter: nil)
|
2014-01-19 19:45:58 +08:00
|
|
|
return new(file_name) unless block_given?
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2021-06-09 00:25:22 +08:00
|
|
|
zos = new(file_name, stream: false, encrypter: encrypter)
|
2013-06-03 15:56:24 +08:00
|
|
|
yield zos
|
|
|
|
ensure
|
|
|
|
zos.close if zos
|
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
|
2014-01-19 19:45:58 +08:00
|
|
|
# Same as #open but writes to a filestream instead
|
2021-06-09 01:09:22 +08:00
|
|
|
def write_buffer(io = ::StringIO.new, encrypter: nil)
|
2020-02-20 01:48:13 +08:00
|
|
|
io.binmode if io.respond_to?(:binmode)
|
2021-06-09 00:25:22 +08:00
|
|
|
zos = new(io, stream: true, encrypter: encrypter)
|
2013-06-03 15:56:24 +08:00
|
|
|
yield zos
|
2014-01-19 19:45:58 +08:00
|
|
|
zos.close_buffer
|
2013-06-03 15:56:24 +08:00
|
|
|
end
|
2011-01-07 21:23:22 +08:00
|
|
|
end
|
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
# Closes the stream and writes the central directory to the zip file
|
|
|
|
def close
|
|
|
|
return if @closed
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
finalize_current_entry
|
|
|
|
update_local_headers
|
|
|
|
write_central_directory
|
2013-06-03 02:33:03 +08:00
|
|
|
@output_stream.close
|
2010-11-30 16:27:59 +08:00
|
|
|
@closed = true
|
|
|
|
end
|
|
|
|
|
2011-01-07 21:23:22 +08:00
|
|
|
# Closes the stream and writes the central directory to the zip file
|
|
|
|
def close_buffer
|
2013-06-03 02:33:03 +08:00
|
|
|
return @output_stream if @closed
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2011-01-07 21:23:22 +08:00
|
|
|
finalize_current_entry
|
|
|
|
update_local_headers
|
|
|
|
write_central_directory
|
|
|
|
@closed = true
|
2021-06-24 05:24:44 +08:00
|
|
|
@output_stream.flush
|
2013-06-03 02:33:03 +08:00
|
|
|
@output_stream
|
2011-01-07 21:23:22 +08:00
|
|
|
end
|
|
|
|
|
2014-01-19 19:45:58 +08:00
|
|
|
# Closes the current entry and opens a new for writing.
|
2010-11-30 16:27:59 +08:00
|
|
|
# +entry+ can be a ZipEntry object or a string.
|
2020-08-03 00:51:08 +08:00
|
|
|
def put_next_entry(
|
|
|
|
entry_name, comment = '', extra = ExtraField.new,
|
|
|
|
compression_method = Entry::DEFLATED, level = Zip.default_compression
|
|
|
|
)
|
2015-03-21 16:27:44 +08:00
|
|
|
raise Error, 'zip stream is closed' if @closed
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2017-06-29 10:57:12 +08:00
|
|
|
new_entry = if entry_name.kind_of?(Entry)
|
|
|
|
entry_name
|
|
|
|
else
|
2020-08-03 00:51:08 +08:00
|
|
|
Entry.new(
|
|
|
|
@file_name, entry_name.to_s, comment: comment,
|
|
|
|
extra: extra, compression_method: compression_method,
|
|
|
|
compression_level: level
|
|
|
|
)
|
2017-06-29 10:57:12 +08:00
|
|
|
end
|
2020-05-25 01:50:18 +08:00
|
|
|
|
|
|
|
init_next_entry(new_entry)
|
2013-08-30 04:50:12 +08:00
|
|
|
@current_entry = new_entry
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def copy_raw_entry(entry)
|
|
|
|
entry = entry.dup
|
2015-03-21 16:27:44 +08:00
|
|
|
raise Error, 'zip stream is closed' if @closed
|
2019-10-01 02:32:25 +08:00
|
|
|
raise Error, 'entry is not a ZipEntry' unless entry.kind_of?(Entry)
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
finalize_current_entry
|
2013-06-03 02:33:03 +08:00
|
|
|
@entry_set << entry
|
2014-03-13 05:42:53 +08:00
|
|
|
src_pos = entry.local_header_offset
|
2013-06-03 02:33:03 +08:00
|
|
|
entry.write_local_entry(@output_stream)
|
2010-11-30 16:27:59 +08:00
|
|
|
@compressor = NullCompressor.instance
|
2012-02-06 08:59:11 +08:00
|
|
|
entry.get_raw_input_stream do |is|
|
2011-11-17 17:48:42 +08:00
|
|
|
is.seek(src_pos, IO::SEEK_SET)
|
2014-03-13 05:42:53 +08:00
|
|
|
::Zip::Entry.read_local_entry(is)
|
2013-06-03 02:33:03 +08:00
|
|
|
IOExtras.copy_stream_n(@output_stream, is, entry.compressed_size)
|
2012-02-06 08:59:11 +08:00
|
|
|
end
|
2010-11-30 16:27:59 +08:00
|
|
|
@compressor = NullCompressor.instance
|
2013-08-30 04:50:12 +08:00
|
|
|
@current_entry = nil
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
2012-02-14 06:03:34 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
def finalize_current_entry
|
2013-08-30 04:50:12 +08:00
|
|
|
return unless @current_entry
|
2020-02-09 21:13:21 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
finish
|
2019-09-27 05:00:46 +08:00
|
|
|
@current_entry.compressed_size = @output_stream.tell - \
|
|
|
|
@current_entry.local_header_offset - \
|
|
|
|
@current_entry.calculate_local_header_size
|
2013-08-30 04:50:12 +08:00
|
|
|
@current_entry.size = @compressor.size
|
|
|
|
@current_entry.crc = @compressor.crc
|
2021-06-26 05:31:34 +08:00
|
|
|
@output_stream << @encrypter.data_descriptor(
|
|
|
|
@current_entry.crc,
|
|
|
|
@current_entry.compressed_size,
|
|
|
|
@current_entry.size
|
|
|
|
)
|
2014-12-31 00:03:17 +08:00
|
|
|
@current_entry.gp_flags |= @encrypter.gp_flags
|
2013-08-30 04:50:12 +08:00
|
|
|
@current_entry = nil
|
2014-01-24 17:37:38 +08:00
|
|
|
@compressor = ::Zip::NullCompressor.instance
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
2011-01-07 21:23:22 +08:00
|
|
|
|
2020-05-25 01:50:18 +08:00
|
|
|
def init_next_entry(entry)
|
2010-11-30 16:27:59 +08:00
|
|
|
finalize_current_entry
|
2013-06-03 02:33:03 +08:00
|
|
|
@entry_set << entry
|
|
|
|
entry.write_local_entry(@output_stream)
|
2014-12-31 00:03:17 +08:00
|
|
|
@encrypter.reset!
|
2015-10-17 23:06:41 +08:00
|
|
|
@output_stream << @encrypter.header(entry.mtime)
|
2020-05-25 01:50:18 +08:00
|
|
|
@compressor = get_compressor(entry)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
2020-05-25 01:50:18 +08:00
|
|
|
def get_compressor(entry)
|
2010-11-30 16:27:59 +08:00
|
|
|
case entry.compression_method
|
2019-09-22 21:23:57 +08:00
|
|
|
when Entry::DEFLATED
|
2020-05-25 01:50:18 +08:00
|
|
|
::Zip::Deflater.new(@output_stream, entry.compression_level, @encrypter)
|
2019-09-22 21:23:57 +08:00
|
|
|
when Entry::STORED
|
2014-01-24 17:37:38 +08:00
|
|
|
::Zip::PassThruCompressor.new(@output_stream)
|
2014-01-19 19:45:58 +08:00
|
|
|
else
|
2014-01-24 17:37:38 +08:00
|
|
|
raise ::Zip::CompressionMethodError,
|
2014-01-19 19:45:58 +08:00
|
|
|
"Invalid compression method: '#{entry.compression_method}'"
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def update_local_headers
|
2013-06-03 02:33:03 +08:00
|
|
|
pos = @output_stream.pos
|
|
|
|
@entry_set.each do |entry|
|
|
|
|
@output_stream.pos = entry.local_header_offset
|
2021-06-09 00:13:20 +08:00
|
|
|
entry.write_local_entry(@output_stream, rewrite: true)
|
2012-02-14 06:03:34 +08:00
|
|
|
end
|
2013-06-03 02:33:03 +08:00
|
|
|
@output_stream.pos = pos
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def write_central_directory
|
2013-06-03 15:56:24 +08:00
|
|
|
cdir = CentralDirectory.new(@entry_set, @comment)
|
2013-06-03 02:33:03 +08:00
|
|
|
cdir.write_to_stream(@output_stream)
|
2010-11-30 16:27:59 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
|
|
|
def finish
|
|
|
|
@compressor.finish
|
|
|
|
end
|
|
|
|
|
|
|
|
public
|
2014-03-01 06:31:10 +08:00
|
|
|
|
2010-11-30 16:27:59 +08:00
|
|
|
# Modeled after IO.<<
|
2015-03-23 00:43:44 +08:00
|
|
|
def <<(data)
|
2010-11-30 16:27:59 +08:00
|
|
|
@compressor << data
|
2014-03-01 06:31:10 +08:00
|
|
|
self
|
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.
|