mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-08 09:09:43 +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: c4b76bea13
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
|
set(SOURCES
|
||||||
Deflate.cpp
|
Deflate.cpp
|
||||||
|
GenericZlib.cpp
|
||||||
|
Gzip.cpp
|
||||||
PackBitsDecoder.cpp
|
PackBitsDecoder.cpp
|
||||||
Zlib.cpp
|
Zlib.cpp
|
||||||
Gzip.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_lib(LibCompress compress)
|
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
Add a link
Reference in a new issue