LibCompress: Allow using GzipCompressor in a streaming fashion

GzipCompressor is currently written assuming that it's write_some method
is only called once. When we use this class for LibWeb, we may very well
receive data to compress in small chunks. So this patch makes us write
the gzip header and footer only once, which now resembles the zlib and
deflate compressors.
This commit is contained in:
Timothy Flynn 2024-11-15 15:39:02 -05:00 committed by Andreas Kling
parent b11fdea175
commit 355ce72c06
Notes: github-actions[bot] 2024-11-17 22:22:36 +00:00
2 changed files with 52 additions and 22 deletions

View file

@ -189,8 +189,25 @@ ErrorOr<size_t> GzipDecompressor::write_some(ReadonlyBytes)
return Error::from_errno(EBADF);
}
GzipCompressor::GzipCompressor(MaybeOwned<Stream> stream)
: m_output_stream(move(stream))
ErrorOr<NonnullOwnPtr<GzipCompressor>> GzipCompressor::create(MaybeOwned<Stream> output_stream)
{
BlockHeader header;
header.identification_1 = 0x1f;
header.identification_2 = 0x8b;
header.compression_method = 0x08;
header.flags = 0;
header.modification_time = 0;
header.extra_flags = 3; // DEFLATE sets 2 for maximum compression and 4 for minimum compression
header.operating_system = 3; // unix
TRY(output_stream->write_until_depleted({ &header, sizeof(header) }));
auto deflate_compressor = TRY(DeflateCompressor::construct(MaybeOwned(*output_stream)));
return adopt_own(*new GzipCompressor { move(output_stream), move(deflate_compressor) });
}
GzipCompressor::GzipCompressor(MaybeOwned<Stream> output_stream, NonnullOwnPtr<DeflateCompressor> deflate_compressor)
: m_output_stream(move(output_stream))
, m_deflate_compressor(move(deflate_compressor))
{
}
@ -201,25 +218,27 @@ ErrorOr<Bytes> GzipCompressor::read_some(Bytes)
ErrorOr<size_t> GzipCompressor::write_some(ReadonlyBytes bytes)
{
BlockHeader header;
header.identification_1 = 0x1f;
header.identification_2 = 0x8b;
header.compression_method = 0x08;
header.flags = 0;
header.modification_time = 0;
header.extra_flags = 3; // DEFLATE sets 2 for maximum compression and 4 for minimum compression
header.operating_system = 3; // unix
TRY(m_output_stream->write_until_depleted({ &header, sizeof(header) }));
auto compressed_stream = TRY(DeflateCompressor::construct(MaybeOwned(*m_output_stream)));
TRY(compressed_stream->write_until_depleted(bytes));
TRY(compressed_stream->final_flush());
Crypto::Checksum::CRC32 crc32;
crc32.update(bytes);
TRY(m_output_stream->write_value<LittleEndian<u32>>(crc32.digest()));
TRY(m_output_stream->write_value<LittleEndian<u32>>(bytes.size()));
VERIFY(!m_finished);
TRY(m_deflate_compressor->write_until_depleted(bytes));
m_total_bytes += bytes.size();
m_crc32.update(bytes);
return bytes.size();
}
ErrorOr<void> GzipCompressor::finish()
{
VERIFY(!m_finished);
m_finished = true;
TRY(m_deflate_compressor->final_flush());
TRY(m_output_stream->write_value<LittleEndian<u32>>(m_crc32.digest()));
TRY(m_output_stream->write_value<LittleEndian<u32>>(m_total_bytes));
return {};
}
bool GzipCompressor::is_eof() const
{
return true;
@ -237,12 +256,14 @@ void GzipCompressor::close()
ErrorOr<ByteBuffer> GzipCompressor::compress_all(ReadonlyBytes bytes)
{
auto output_stream = TRY(try_make<AllocatingMemoryStream>());
GzipCompressor gzip_stream { MaybeOwned<Stream>(*output_stream) };
auto gzip_stream = TRY(GzipCompressor::create(MaybeOwned { *output_stream }));
TRY(gzip_stream.write_until_depleted(bytes));
TRY(gzip_stream->write_until_depleted(bytes));
TRY(gzip_stream->finish());
auto buffer = TRY(ByteBuffer::create_uninitialized(output_stream->used_buffer_size()));
TRY(output_stream->read_until_filled(buffer.bytes()));
return buffer;
}

View file

@ -49,7 +49,7 @@ public:
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override;
virtual bool is_open() const override { return true; }
virtual void close() override {};
virtual void close() override { }
static ErrorOr<ByteBuffer> decompress_all(ReadonlyBytes);
@ -83,7 +83,7 @@ private:
class GzipCompressor final : public Stream {
public:
GzipCompressor(MaybeOwned<Stream>);
static ErrorOr<NonnullOwnPtr<GzipCompressor>> create(MaybeOwned<Stream>);
virtual ErrorOr<Bytes> read_some(Bytes) override;
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
@ -93,8 +93,17 @@ public:
static ErrorOr<ByteBuffer> compress_all(ReadonlyBytes bytes);
ErrorOr<void> finish();
private:
GzipCompressor(MaybeOwned<Stream>, NonnullOwnPtr<DeflateCompressor>);
MaybeOwned<Stream> m_output_stream;
NonnullOwnPtr<DeflateCompressor> m_deflate_compressor;
Crypto::Checksum::CRC32 m_crc32;
size_t m_total_bytes { 0 };
bool m_finished { false };
};
}