Skip to content

Commit

Permalink
Continue
Browse files Browse the repository at this point in the history
  • Loading branch information
julik committed Mar 31, 2024
1 parent 42a7aa7 commit 10207a0
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 67 deletions.
48 changes: 30 additions & 18 deletions lib/zip_kit/streamer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def simulate_write(num_bytes)
# @param use_data_descriptor [Boolean] whether the entry body will be followed by a data descriptor
# @param unix_permissions[Integer] which UNIX permissions to set, normally the default should be used
# @return [Integer] the offset the output IO is at after writing the entry header
def add_deflated_entry(filename:, modification_time: Time.now.utc, compressed_size: 0, uncompressed_size: 0, crc32: 0, unix_permissions: nil, use_data_descriptor: false, encryptor: NoEncryption)
def add_deflated_entry(filename:, modification_time: Time.now.utc, compressed_size: 0, uncompressed_size: 0, crc32: 0, unix_permissions: nil, use_data_descriptor: false, encryption: NoEncryption)
add_file_and_write_local_header(filename: filename,
modification_time: modification_time,
crc32: crc32,
Expand All @@ -182,7 +182,7 @@ def add_deflated_entry(filename:, modification_time: Time.now.utc, compressed_si
uncompressed_size: uncompressed_size,
unix_permissions: unix_permissions,
use_data_descriptor: use_data_descriptor,
encryptor: encryptor)
encryption: encryption)
@out.tell
end

Expand All @@ -198,7 +198,7 @@ def add_deflated_entry(filename:, modification_time: Time.now.utc, compressed_si
# @param use_data_descriptor [Boolean] whether the entry body will be followed by a data descriptor. When in use
# @param unix_permissions[Integer] which UNIX permissions to set, normally the default should be used
# @return [Integer] the offset the output IO is at after writing the entry header
def add_stored_entry(filename:, modification_time: Time.now.utc, size: 0, crc32: 0, unix_permissions: nil, use_data_descriptor: false, encryptor: NoEncryption)
def add_stored_entry(filename:, modification_time: Time.now.utc, size: 0, crc32: 0, unix_permissions: nil, use_data_descriptor: false, encryption: NoEncryption)
add_file_and_write_local_header(filename: filename,
modification_time: modification_time,
crc32: crc32,
Expand All @@ -207,7 +207,7 @@ def add_stored_entry(filename:, modification_time: Time.now.utc, size: 0, crc32:
uncompressed_size: size,
unix_permissions: unix_permissions,
use_data_descriptor: use_data_descriptor,
encryptor: encryptor)
encryption: encryption)
@out.tell
end

Expand All @@ -226,7 +226,7 @@ def add_empty_directory(dirname:, modification_time: Time.now.utc, unix_permissi
uncompressed_size: 0,
unix_permissions: unix_permissions,
use_data_descriptor: false,
encryptor: NoEncryption)
encryption: NoEncryption)
@out.tell
end

Expand Down Expand Up @@ -273,8 +273,8 @@ def add_empty_directory(dirname:, modification_time: Time.now.utc, unix_permissi
# Do not call `#close` on it - Streamer will do it for you. Write in chunks to achieve proper streaming
# output (using `IO.copy_stream` is a good approach).
# @return [ZipKit::Streamer::Writable] without a block - the Writable sink which has to be closed manually
def write_file(filename, modification_time: Time.now.utc, unix_permissions: nil, encryptor: NoEncryption, &blk)
writable = ZipKit::Streamer::Heuristic.new(self, filename, modification_time: modification_time, unix_permissions: unix_permissions, encryptor: encryptor)
def write_file(filename, modification_time: Time.now.utc, unix_permissions: nil, encryption: NoEncryption, &blk)
writable = ZipKit::Streamer::Heuristic.new(self, filename, modification_time: modification_time, unix_permissions: unix_permissions, encryption: encryption)
yield_or_return_writable(writable, &blk)
end

Expand Down Expand Up @@ -323,16 +323,22 @@ def write_file(filename, modification_time: Time.now.utc, unix_permissions: nil,
# Do not call `#close` on it - Streamer will do it for you. Write in chunks to achieve proper streaming
# output (using `IO.copy_stream` is a good approach).
# @return [ZipKit::Streamer::Writable] without a block - the Writable sink which has to be closed manually
def write_stored_file(filename, modification_time: Time.now.utc, unix_permissions: nil, encryptor: NoEncryption, &blk)
def write_stored_file(filename, modification_time: Time.now.utc, unix_permissions: nil, encryption: NoEncryption, &blk)
add_stored_entry(filename: filename,
modification_time: modification_time,
use_data_descriptor: true,
crc32: 0,
size: 0,
unix_permissions: unix_permissions,
encryptor: encryptor)
encryption: encryption)

tellable = ZipKit::WriteAndTell.new(@out)
encryptor = encryption.wrap_io(tellable)
compressor = StoredWriter.new(encryptor)
writable = Writable.new(compressor) do |bytes_received:, crc32:|
update_last_entry_and_write_data_descriptor(crc32: crc32, uncompressed_size: bytes_received, compressed_size: tellable.tell)
end

writable = Writable.new(self, StoredWriter.new(encryptor.wrap_io(@out)))
yield_or_return_writable(writable, &blk)
end

Expand Down Expand Up @@ -383,17 +389,22 @@ def write_stored_file(filename, modification_time: Time.now.utc, unix_permission
# Do not call `#close` on it - Streamer will do it for you. Write in chunks to achieve proper streaming
# output (using `IO.copy_stream` is a good approach).
# @return [ZipKit::Streamer::Writable] without a block - the Writable sink which has to be closed manually
def write_deflated_file(filename, modification_time: Time.now.utc, unix_permissions: nil, encryptor: NoEncryption, &blk)
def write_deflated_file(filename, modification_time: Time.now.utc, unix_permissions: nil, encryption: NoEncryption, &blk)
add_deflated_entry(filename: filename,
modification_time: modification_time,
use_data_descriptor: true,
crc32: 0,
compressed_size: 0,
uncompressed_size: 0,
unix_permissions: unix_permissions,
encryptor: encryptor)
encryption: encryption)

writable = Writable.new(self, DeflatedWriter.new(encryptor.wrap_io(@out)))
tellable = ZipKit::WriteAndTell.new(@out)
encryptor = encryption.wrap_io(tellable)
compressor = DeflatedWriter.new(encryptor)
writable = Writable.new(compressor) do |bytes_received:, crc32:|
update_last_entry_and_write_data_descriptor(crc32: crc32, uncompressed_size: bytes_received, compressed_size: tellable.tell)
end
yield_or_return_writable(writable, &blk)
end

Expand Down Expand Up @@ -424,7 +435,8 @@ def close
mtime: entry.mtime,
crc32: entry.crc32,
filename: entry.filename,
unix_permissions: entry.unix_permissions)
unix_permissions: entry.unix_permissions,
extra_field_bytes: entry.extra_field_bytes)
end

# Record the central directory size, for the EOCDR
Expand Down Expand Up @@ -560,7 +572,7 @@ def add_file_and_write_local_header(
uncompressed_size:,
use_data_descriptor:,
unix_permissions:,
encryptor:
encryption:
)

# Clean backslashes
Expand Down Expand Up @@ -592,15 +604,15 @@ def add_file_and_write_local_header(
crc32,
compressed_size,
uncompressed_size,
encryptor.override_storage_mode || storage_mode,
encryption.override_storage_mode || storage_mode,
modification_time,
use_data_descriptor,
_local_file_header_offset = local_header_starts_at,
_bytes_used_for_local_header = 0,
_bytes_used_for_data_descriptor = 0,
unix_permissions,
encryptor.set_gp_bit1?,
encryptor.extra_field_bytes)
encryption.set_gp_bit1?,
encryption.extra_field_bytes)

@writer.write_local_file_header(io: @out,
gp_flags: e.gp_flags,
Expand Down
4 changes: 2 additions & 2 deletions lib/zip_kit/streamer/aes_encryption.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def <<(bytes)
self
end

def finish
def close
_, remaining_bytes = @block_buffer.bytesize.divmod(BLOCK_SIZE_BYTES)
if remaining_bytes > 0
write_block(@block_buffer)
Expand Down Expand Up @@ -77,7 +77,7 @@ def initialize(password:, storage_mode:, encryption_strength: 3)
password,
@salt,
1000,
@key_length + @mac_length + VERIFIER_LENGTH_BYTES
@key_length + @mac_key_length + VERIFIER_LENGTH_BYTES
)
@encryption_key = key_mac_and_verification_bytes.byteslice(0, @key_length)
@mac_key = key_mac_and_verification_bytes.byteslice(@key_length, @mac_key_length)
Expand Down
23 changes: 5 additions & 18 deletions lib/zip_kit/streamer/deflated_writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,9 @@
# registers data passing through it in a CRC32 checksum calculator. Is made to be completely
# interchangeable with the StoredWriter in terms of interface.
class ZipKit::Streamer::DeflatedWriter
include ZipKit::WriteShovel

# The amount of bytes we will buffer before computing the intermediate
# CRC32 checksums. Benchmarks show that the optimum is 64KB (see
# `bench/buffered_crc32_bench.rb), if that is exceeded Zlib is going
# to perform internal CRC combine calls which will make the speed go down again.
CRC32_BUFFER_SIZE = 64 * 1024

def initialize(io)
@compressed_io = io
@io = io
@deflater = ::Zlib::Deflate.new(Zlib::DEFAULT_COMPRESSION, -::Zlib::MAX_WBITS)
@crc = ZipKit::StreamCRC32.new
@crc_buf = ZipKit::WriteBuffer.new(@crc, CRC32_BUFFER_SIZE)
end

# Writes the given data into the deflater, and flushes the deflater
Expand All @@ -25,8 +15,7 @@ def initialize(io)
# @param data[String] data to be written
# @return self
def <<(data)
@deflater.deflate(data) { |chunk| @compressed_io << chunk }
@crc_buf << data
@deflater.deflate(data) { |chunk| @io << chunk }
self
end

Expand All @@ -35,11 +24,9 @@ def <<(data)
# can be directly used as the argument to {Streamer#update_last_entry_and_write_data_descriptor}
#
# @return [Hash] a hash of `{crc32, compressed_size, uncompressed_size}`
def finish
@compressed_io << @deflater.finish until @deflater.finished?
@compressed_io.finish if @compressed_io.respond_to?(:finish)
@crc_buf.flush
{crc32: @crc.to_i, compressed_size: @deflater.total_out, uncompressed_size: @deflater.total_in}
def close
@io << @deflater.finish until @deflater.finished?
@io.close
ensure
@deflater.close
end
Expand Down
3 changes: 2 additions & 1 deletion lib/zip_kit/streamer/no_encryption.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ def initialize(io)

def <<(b)
@io << b
self
end

def finish
def close
end
end

Expand Down
18 changes: 2 additions & 16 deletions lib/zip_kit/streamer/stored_writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,8 @@
# through it in a CRC32 checksum calculator. Is made to be completely
# interchangeable with the DeflatedWriter in terms of interface.
class ZipKit::Streamer::StoredWriter
include ZipKit::WriteShovel

# The amount of bytes we will buffer before computing the intermediate
# CRC32 checksums. Benchmarks show that the optimum is 64KB (see
# `bench/buffered_crc32_bench.rb), if that is exceeded Zlib is going
# to perform internal CRC combine calls which will make the speed go down again.
CRC32_BUFFER_SIZE = 64 * 1024

def initialize(io)
@io = ZipKit::WriteAndTell.new(io)
@crc_compute = ZipKit::StreamCRC32.new
@crc = ZipKit::WriteBuffer.new(@crc_compute, CRC32_BUFFER_SIZE)
@io = io
end

# Writes the given data to the contained IO object.
Expand All @@ -24,17 +14,13 @@ def initialize(io)
# @return self
def <<(data)
@io << data
@crc << data
self
end

# Returns the amount of data written and the CRC32 checksum. The return value
# can be directly used as the argument to {Streamer#update_last_entry_and_write_data_descriptor}
#
# @return [Hash] a hash of `{crc32, compressed_size, uncompressed_size}`
def finish
@io.finish if @io.respond_to?(:finish)
@crc.flush
{crc32: @crc_compute.to_i, compressed_size: @io.tell, uncompressed_size: @io.tell}
def close
end
end
29 changes: 19 additions & 10 deletions lib/zip_kit/streamer/writable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,38 @@
class ZipKit::Streamer::Writable
include ZipKit::WriteShovel

# The amount of bytes we will buffer before computing the intermediate
# CRC32 checksums. Benchmarks show that the optimum is 64KB (see
# `bench/buffered_crc32_bench.rb), if that is exceeded Zlib is going
# to perform internal CRC combine calls which will make the speed go down again.
CRC32_BUFFER_SIZE = 64 * 1024

# Initializes a new Writable with the object it delegates the writes to.
# Normally you would not need to use this method directly
def initialize(streamer, writer)
@streamer = streamer
@writer = writer
@closed = false
def initialize(io, &at_close)
@crc = ZipKit::StreamCRC32.new
@crc_buf = ZipKit::WriteBuffer.new(@crc, CRC32_BUFFER_SIZE)
@io = io
@bytes_in = 0
@at_close = at_close
end

# Writes the given data to the output stream
#
# @param d[String] the binary string to write (part of the uncompressed file)
# @return [self]
def <<(d)
raise "Trying to write to a closed Writable" if @closed
@writer << d
def <<(bytes)
@crc_buf << bytes
@io << bytes
@bytes_in += bytes.bytesize
self
end

# Flushes the writer and recovers the CRC32/size values. It then calls
# `update_last_entry_and_write_data_descriptor` on the given Streamer.
def close
return if @closed
@streamer.update_last_entry_and_write_data_descriptor(**@writer.finish)
@closed = true
@crc_buf.flush
@io.close
@at_close.call(bytes_received: @bytes_in, crc32: @crc.to_i)
end
end
2 changes: 0 additions & 2 deletions spec/zip_kit/streamer/deflated_writer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,5 @@
zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS)
inflated = zlib_inflater.inflate(out.string)
expect(inflated).to eq(("a" * 256) + ("b" * 256) + ("b" * 256))

expect(finish_result).to eq(crc32: 234880044, compressed_size: out.size, uncompressed_size: 768)
end
end

0 comments on commit 10207a0

Please sign in to comment.