mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 19:45:12 +00:00
LibCompress: Introduce generic de/compressor using zlib
This compressor decompressor is capable of handling zlib, gzip and deflate all in one.
This commit is contained in:
parent
1c836588d9
commit
c4b76bea13
Notes:
github-actions[bot]
2025-03-19 12:48:58 +00:00
Author: https://github.com/devgianlu Commit: https://github.com/LadybirdBrowser/ladybird/commit/c4b76bea13e Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3755 Reviewed-by: https://github.com/gmta ✅ Reviewed-by: https://github.com/trflynn89
3 changed files with 322 additions and 1 deletions
|
@ -1,8 +1,9 @@
|
|||
set(SOURCES
|
||||
Deflate.cpp
|
||||
GenericZlib.cpp
|
||||
Gzip.cpp
|
||||
PackBitsDecoder.cpp
|
||||
Zlib.cpp
|
||||
Gzip.cpp
|
||||
)
|
||||
|
||||
serenity_lib(LibCompress compress)
|
||||
|
|
219
Libraries/LibCompress/GenericZlib.cpp
Normal file
219
Libraries/LibCompress/GenericZlib.cpp
Normal file
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCompress/GenericZlib.h>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
namespace Compress {
|
||||
|
||||
static Error handle_zlib_error(int ret)
|
||||
{
|
||||
switch (ret) {
|
||||
case Z_ERRNO:
|
||||
return Error::from_errno(errno);
|
||||
case Z_DATA_ERROR:
|
||||
// Z_DATA_ERROR if the input data was corrupted
|
||||
return Error::from_string_literal("zlib data error");
|
||||
case Z_STREAM_ERROR:
|
||||
// Z_STREAM_ERROR if the parameters are invalid, such as a null pointer to the structure
|
||||
return Error::from_string_literal("zlib stream error");
|
||||
case Z_VERSION_ERROR:
|
||||
// Z_VERSION_ERROR if the zlib library version is incompatible with the version assumed by the caller
|
||||
return Error::from_string_literal("zlib version mismatch");
|
||||
case Z_MEM_ERROR:
|
||||
// Z_MEM_ERROR if there was not enough memory
|
||||
return Error::from_errno(ENOMEM);
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
GenericZlibDecompressor::GenericZlibDecompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
|
||||
: m_stream(move(stream))
|
||||
, m_zstream(zstream)
|
||||
, m_buffer(move(buffer))
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<z_stream*> GenericZlibDecompressor::new_z_stream(int window_bits)
|
||||
{
|
||||
auto zstream = new (nothrow) z_stream {};
|
||||
if (!zstream)
|
||||
return Error::from_errno(ENOMEM);
|
||||
|
||||
// The fields next_in, avail_in, zalloc, zfree and opaque must be initialized before by the caller.
|
||||
zstream->next_in = nullptr;
|
||||
zstream->avail_in = 0;
|
||||
zstream->zalloc = nullptr;
|
||||
zstream->zfree = nullptr;
|
||||
zstream->opaque = nullptr;
|
||||
|
||||
if (auto ret = inflateInit2(zstream, window_bits); ret != Z_OK)
|
||||
return handle_zlib_error(ret);
|
||||
|
||||
return zstream;
|
||||
}
|
||||
|
||||
GenericZlibDecompressor::~GenericZlibDecompressor()
|
||||
{
|
||||
inflateEnd(m_zstream);
|
||||
delete m_zstream;
|
||||
}
|
||||
|
||||
ErrorOr<Bytes> GenericZlibDecompressor::read_some(Bytes bytes)
|
||||
{
|
||||
m_zstream->avail_out = bytes.size();
|
||||
m_zstream->next_out = bytes.data();
|
||||
|
||||
if (m_zstream->avail_in == 0) {
|
||||
auto in = TRY(m_stream->read_some(m_buffer.span()));
|
||||
m_zstream->avail_in = in.size();
|
||||
m_zstream->next_in = m_buffer.data();
|
||||
}
|
||||
|
||||
auto ret = inflate(m_zstream, Z_NO_FLUSH);
|
||||
if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR)
|
||||
return handle_zlib_error(ret);
|
||||
|
||||
if (ret == Z_STREAM_END) {
|
||||
inflateReset(m_zstream);
|
||||
if (m_zstream->avail_in == 0)
|
||||
m_eof = true;
|
||||
}
|
||||
|
||||
return bytes.slice(0, bytes.size() - m_zstream->avail_out);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> GenericZlibDecompressor::write_some(ReadonlyBytes)
|
||||
{
|
||||
return Error::from_errno(EBADF);
|
||||
}
|
||||
|
||||
bool GenericZlibDecompressor::is_eof() const
|
||||
{
|
||||
return m_eof;
|
||||
}
|
||||
|
||||
bool GenericZlibDecompressor::is_open() const
|
||||
{
|
||||
return m_stream->is_open();
|
||||
}
|
||||
|
||||
void GenericZlibDecompressor::close()
|
||||
{
|
||||
}
|
||||
|
||||
GenericZlibCompressor::GenericZlibCompressor(AK::FixedArray<u8> buffer, MaybeOwned<Stream> stream, z_stream* zstream)
|
||||
: m_stream(move(stream))
|
||||
, m_zstream(zstream)
|
||||
, m_buffer(move(buffer))
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<z_stream*> GenericZlibCompressor::new_z_stream(int window_bits, GenericZlibCompressionLevel compression_level)
|
||||
{
|
||||
auto zstream = new (nothrow) z_stream {};
|
||||
if (!zstream)
|
||||
return Error::from_errno(ENOMEM);
|
||||
|
||||
// The fields zalloc, zfree and opaque must be initialized before by the caller.
|
||||
zstream->zalloc = nullptr;
|
||||
zstream->zfree = nullptr;
|
||||
zstream->opaque = nullptr;
|
||||
|
||||
int level = [&] {
|
||||
switch (compression_level) {
|
||||
case GenericZlibCompressionLevel::Fastest:
|
||||
return Z_BEST_SPEED;
|
||||
case GenericZlibCompressionLevel::Default:
|
||||
return Z_DEFAULT_COMPRESSION;
|
||||
case GenericZlibCompressionLevel::Best:
|
||||
return Z_BEST_COMPRESSION;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}();
|
||||
|
||||
if (auto ret = deflateInit2(zstream, level, Z_DEFLATED, window_bits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); ret != Z_OK)
|
||||
return handle_zlib_error(ret);
|
||||
|
||||
return zstream;
|
||||
}
|
||||
|
||||
GenericZlibCompressor::~GenericZlibCompressor()
|
||||
{
|
||||
deflateEnd(m_zstream);
|
||||
delete m_zstream;
|
||||
}
|
||||
|
||||
ErrorOr<Bytes> GenericZlibCompressor::read_some(Bytes)
|
||||
{
|
||||
return Error::from_errno(EBADF);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> GenericZlibCompressor::write_some(ReadonlyBytes bytes)
|
||||
{
|
||||
m_zstream->avail_in = bytes.size();
|
||||
m_zstream->next_in = const_cast<u8*>(bytes.data());
|
||||
|
||||
// If deflate returns with avail_out == 0, this function must be called again with the same value of the flush parameter
|
||||
// and more output space (updated avail_out), until the flush is complete (deflate returns with non-zero avail_out).
|
||||
do {
|
||||
m_zstream->avail_out = m_buffer.size();
|
||||
m_zstream->next_out = m_buffer.data();
|
||||
|
||||
auto ret = deflate(m_zstream, Z_NO_FLUSH);
|
||||
if (ret != Z_OK && ret != Z_BUF_ERROR)
|
||||
return handle_zlib_error(ret);
|
||||
|
||||
auto have = m_buffer.size() - m_zstream->avail_out;
|
||||
TRY(m_stream->write_until_depleted(m_buffer.span().slice(0, have)));
|
||||
} while (m_zstream->avail_out == 0);
|
||||
|
||||
VERIFY(m_zstream->avail_in == 0);
|
||||
return bytes.size();
|
||||
}
|
||||
|
||||
bool GenericZlibCompressor::is_eof() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GenericZlibCompressor::is_open() const
|
||||
{
|
||||
return m_stream->is_open();
|
||||
}
|
||||
|
||||
void GenericZlibCompressor::close()
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<void> GenericZlibCompressor::finish()
|
||||
{
|
||||
VERIFY(m_zstream->avail_in == 0);
|
||||
|
||||
// If the parameter flush is set to Z_FINISH, pending input is processed, pending output is flushed and deflate returns with Z_STREAM_END
|
||||
// if there was enough output space. If deflate returns with Z_OK or Z_BUF_ERROR, this function must be called again with Z_FINISH
|
||||
// and more output space (updated avail_out) but no more input data, until it returns with Z_STREAM_END or an error.
|
||||
while (true) {
|
||||
m_zstream->avail_out = m_buffer.size();
|
||||
m_zstream->next_out = m_buffer.data();
|
||||
|
||||
auto ret = deflate(m_zstream, Z_FINISH);
|
||||
if (ret == Z_STREAM_END || ret == Z_BUF_ERROR || ret == Z_OK) {
|
||||
auto have = m_buffer.size() - m_zstream->avail_out;
|
||||
TRY(m_stream->write_until_depleted(m_buffer.span().slice(0, have)));
|
||||
|
||||
if (ret == Z_STREAM_END)
|
||||
return {};
|
||||
} else {
|
||||
return handle_zlib_error(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
101
Libraries/LibCompress/GenericZlib.h
Normal file
101
Libraries/LibCompress/GenericZlib.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/FixedArray.h>
|
||||
#include <AK/MaybeOwned.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/Stream.h>
|
||||
|
||||
extern "C" {
|
||||
typedef struct z_stream_s z_stream;
|
||||
}
|
||||
|
||||
namespace Compress {
|
||||
|
||||
enum class GenericZlibCompressionLevel : u8 {
|
||||
Fastest,
|
||||
Default,
|
||||
Best,
|
||||
};
|
||||
|
||||
class GenericZlibDecompressor : public Stream {
|
||||
AK_MAKE_NONCOPYABLE(GenericZlibDecompressor);
|
||||
|
||||
public:
|
||||
~GenericZlibDecompressor() override;
|
||||
|
||||
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||
virtual bool is_eof() const override;
|
||||
virtual bool is_open() const override;
|
||||
virtual void close() override;
|
||||
|
||||
protected:
|
||||
GenericZlibDecompressor(AK::FixedArray<u8>, MaybeOwned<Stream>, z_stream*);
|
||||
|
||||
static ErrorOr<z_stream*> new_z_stream(int window_bits);
|
||||
|
||||
private:
|
||||
MaybeOwned<Stream> m_stream;
|
||||
z_stream* m_zstream;
|
||||
|
||||
bool m_eof { false };
|
||||
|
||||
AK::FixedArray<u8> m_buffer;
|
||||
};
|
||||
|
||||
class GenericZlibCompressor : public Stream {
|
||||
AK_MAKE_NONCOPYABLE(GenericZlibCompressor);
|
||||
|
||||
public:
|
||||
~GenericZlibCompressor() override;
|
||||
|
||||
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||
virtual bool is_eof() const override;
|
||||
virtual bool is_open() const override;
|
||||
virtual void close() override;
|
||||
ErrorOr<void> finish();
|
||||
|
||||
protected:
|
||||
GenericZlibCompressor(AK::FixedArray<u8>, MaybeOwned<Stream>, z_stream*);
|
||||
|
||||
static ErrorOr<z_stream*> new_z_stream(int window_bits, GenericZlibCompressionLevel compression_level);
|
||||
|
||||
private:
|
||||
MaybeOwned<Stream> m_stream;
|
||||
z_stream* m_zstream;
|
||||
|
||||
AK::FixedArray<u8> m_buffer;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
ErrorOr<ByteBuffer> decompress_all(ReadonlyBytes bytes)
|
||||
{
|
||||
auto input_stream = make<AK::FixedMemoryStream>(bytes);
|
||||
auto deflate_stream = TRY(T::create(MaybeOwned<Stream>(move(input_stream))));
|
||||
return TRY(deflate_stream->read_until_eof(4096));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
ErrorOr<ByteBuffer> compress_all(ReadonlyBytes bytes, GenericZlibCompressionLevel compression_level)
|
||||
{
|
||||
auto output_stream = TRY(try_make<AllocatingMemoryStream>());
|
||||
auto gzip_stream = TRY(T::create(MaybeOwned { *output_stream }, compression_level));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue