libutil: BrotliDecompression{Sink -> Source}

Change-Id: I9579dd08f7bd0f927bde9d3128515b0cee15f320
This commit is contained in:
eldritch horrors 2024-05-03 04:10:30 +02:00
parent 0b9a72524a
commit a960576f58

View file

@ -137,53 +137,55 @@ struct NoneSink : CompressionSink
void writeUnbuffered(std::string_view data) override { nextSink(data); } void writeUnbuffered(std::string_view data) override { nextSink(data); }
}; };
struct BrotliDecompressionSink : ChunkedCompressionSink struct BrotliDecompressionSource : Source
{ {
Sink & nextSink; static constexpr size_t BUF_SIZE = 32 * 1024;
BrotliDecoderState * state; std::unique_ptr<char[]> buf;
bool finished = false; size_t avail_in = 0;
const uint8_t * next_in;
BrotliDecompressionSink(Sink & nextSink) : nextSink(nextSink) Source * inner;
std::unique_ptr<BrotliDecoderState, void (*)(BrotliDecoderState *)> state;
BrotliDecompressionSource(Source & inner)
: buf(std::make_unique<char[]>(BUF_SIZE))
, inner(&inner)
, state{
BrotliDecoderCreateInstance(nullptr, nullptr, nullptr), BrotliDecoderDestroyInstance}
{ {
state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); if (!state) {
if (!state)
throw CompressionError("unable to initialize brotli decoder"); throw CompressionError("unable to initialize brotli decoder");
} }
~BrotliDecompressionSink()
{
BrotliDecoderDestroyInstance(state);
} }
void finish() override size_t read(char * data, size_t len) override
{ {
flush(); uint8_t * out = (uint8_t *) data;
writeInternal({}); const auto * begin = out;
}
void writeInternal(std::string_view data) override try {
{ while (len && !BrotliDecoderIsFinished(state.get())) {
auto next_in = (const uint8_t *) data.data();
size_t avail_in = data.size();
uint8_t * next_out = outbuf;
size_t avail_out = sizeof(outbuf);
while (!finished && (!data.data() || avail_in)) {
checkInterrupt(); checkInterrupt();
if (!BrotliDecoderDecompressStream(state, while (avail_in == 0) {
&avail_in, &next_in, avail_in = inner->read(buf.get(), BUF_SIZE);
&avail_out, &next_out, next_in = (const uint8_t *) buf.get();
nullptr))
throw CompressionError("error while decompressing brotli file");
if (avail_out < sizeof(outbuf) || avail_in == 0) {
nextSink({(char *) outbuf, sizeof(outbuf) - avail_out});
next_out = outbuf;
avail_out = sizeof(outbuf);
} }
finished = BrotliDecoderIsFinished(state); if (!BrotliDecoderDecompressStream(
state.get(), &avail_in, &next_in, &len, &out, nullptr
))
{
throw CompressionError("error while decompressing brotli file");
}
}
} catch (EndOfFile &) {
}
if (begin != out) {
return out - begin;
} else {
throw EndOfFile("brotli stream exhausted");
} }
} }
}; };
@ -202,7 +204,19 @@ std::unique_ptr<FinishSink> makeDecompressionSink(const std::string & method, Si
if (method == "none" || method == "") if (method == "none" || method == "")
return std::make_unique<NoneSink>(nextSink); return std::make_unique<NoneSink>(nextSink);
else if (method == "br") else if (method == "br")
return std::make_unique<BrotliDecompressionSink>(nextSink); return sourceToSink([&](Source & source) {
BrotliDecompressionSource wrapped{source};
wrapped.drainInto(nextSink);
// special handling because sourceToSink is screwy: try
// to read the source one final time and fail when that
// succeeds (to reject trailing garbage in input data).
try {
char buf;
source(&buf, 1);
throw Error("garbage at end of brotli stream detected");
} catch (EndOfFile &) {
}
});
else else
return sourceToSink([&](Source & source) { return sourceToSink([&](Source & source) {
auto decompressionSource = std::make_unique<ArchiveDecompressionSource>(source); auto decompressionSource = std::make_unique<ArchiveDecompressionSource>(source);