mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-23 17:33:12 +00:00
If we find ourselves in a situation where zlib can't make any progress, we don't have any more data to feed in and no output has been produced, we need to raise an error as the compressed data is incomplete. This used to lead to an infinite busy loop where we keep calling zlib to decompressed but is not able. This causes the promise on the read side of the transformer to never fulfill. This gives us at least 24 more WPT tests :)
225 lines
6.6 KiB
C++
225 lines
6.6 KiB
C++
/*
|
|
* 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);
|
|
|
|
// We got Z_BUF_ERROR (no progress was possible), no more input, stream is EOF and no output was produced.
|
|
// There is no way to get out of this loop, error out.
|
|
if (ret == Z_BUF_ERROR && m_zstream->avail_in == 0 && m_stream->is_eof() && bytes.size() == m_zstream->avail_out)
|
|
return Error::from_string_literal("No decompression progress on EOF stream");
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|