ladybird/Libraries/LibCompress/GenericZlib.cpp
Tete17 7a235537e8 LibCompress: Error out when encounters and incomplete stream
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 :)
2025-06-14 18:26:56 -04:00

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);
}
}
}
}