/* soup-brotli-decompressor.c * * Copyright 2019 Igalia S.L. * * This file is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program. If not, see . * * SPDX-License-Identifier: LGPL-2.0-or-later */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "soup-brotli-decompressor.h" struct _SoupBrotliDecompressor { GObject parent_instance; BrotliDecoderState *state; GError *last_error; }; static void soup_brotli_decompressor_iface_init (GConverterIface *iface); G_DEFINE_TYPE_EXTENDED (SoupBrotliDecompressor, soup_brotli_decompressor, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER, soup_brotli_decompressor_iface_init)) SoupBrotliDecompressor * soup_brotli_decompressor_new (void) { return g_object_new (SOUP_TYPE_BROTLI_DECOMPRESSOR, NULL); } static GError * soup_brotli_decompressor_create_error (SoupBrotliDecompressor *self) { BrotliDecoderErrorCode code; const char *error_string; g_assert (self->state != NULL); code = BrotliDecoderGetErrorCode (self->state); error_string = BrotliDecoderErrorString (code); return g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, "SoupBrotliDecompressorError: %s", error_string); } static void soup_brotli_decompressor_set_error (SoupBrotliDecompressor *self, GError **error) { BrotliDecoderErrorCode code; const char *error_string; if (error == NULL) return; g_assert (self->state != NULL); code = BrotliDecoderGetErrorCode (self->state); error_string = BrotliDecoderErrorString (code); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "SoupBrotliDecompressorError: %s", error_string); } static GConverterResult soup_brotli_decompressor_convert (GConverter *converter, const void *inbuf, gsize inbuf_size, void *outbuf, gsize outbuf_size, GConverterFlags flags, gsize *bytes_read, gsize *bytes_written, GError **error) { SoupBrotliDecompressor *self = SOUP_BROTLI_DECOMPRESSOR (converter); BrotliDecoderResult result; gsize available_in = inbuf_size; const guint8 *next_in = inbuf; gsize available_out = outbuf_size; guchar *next_out = outbuf; g_return_val_if_fail (inbuf, G_CONVERTER_ERROR); if (self->last_error) { if (error) *error = g_steal_pointer (&self->last_error); g_clear_error (&self->last_error); return G_CONVERTER_ERROR; } /* NOTE: all error domains/codes must match GZlibDecompressor */ if (self->state == NULL) { self->state = BrotliDecoderCreateInstance (NULL, NULL, NULL); if (self->state == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "SoupBrotliDecompressorError: Failed to initialize state"); return G_CONVERTER_ERROR; } } result = BrotliDecoderDecompressStream (self->state, &available_in, &next_in, &available_out, &next_out, NULL); /* available_in is now set to *unread* input size */ *bytes_read = inbuf_size - available_in; /* available_out is now set to *unwritten* output size */ *bytes_written = outbuf_size - available_out; /* As per API docs: If any data was either produced or consumed, and then an error happens, then only * the successful conversion is reported and the error is returned on the next call. */ if (*bytes_read || *bytes_written) { switch (result) { case BROTLI_DECODER_RESULT_ERROR: self->last_error = soup_brotli_decompressor_create_error (self); break; case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: self->last_error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, "SoupBrotliDecompressorError: More input required (corrupt input)"); break; case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: /* Just continue with more output then */ break; case BROTLI_DECODER_RESULT_SUCCESS: /* Just continue returning finished next time */ break; } return G_CONVERTER_CONVERTED; } switch (result) { case BROTLI_DECODER_RESULT_SUCCESS: return G_CONVERTER_FINISHED; case BROTLI_DECODER_RESULT_ERROR: soup_brotli_decompressor_set_error (self, error); return G_CONVERTER_ERROR; case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, "SoupBrotliDecompressorError: More input required (corrupt input)"); return G_CONVERTER_ERROR; case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, "SoupBrotliDecompressorError: Larger output buffer required"); return G_CONVERTER_ERROR; } g_assert_not_reached (); return G_CONVERTER_ERROR; } static void soup_brotli_decompressor_reset (GConverter *converter) { SoupBrotliDecompressor *self = SOUP_BROTLI_DECOMPRESSOR (converter); if (self->state && BrotliDecoderIsUsed (self->state)) g_clear_pointer (&self->state, BrotliDecoderDestroyInstance); g_clear_error (&self->last_error); } static void soup_brotli_decompressor_finalize (GObject *object) { SoupBrotliDecompressor *self = (SoupBrotliDecompressor *)object; g_clear_pointer (&self->state, BrotliDecoderDestroyInstance); g_clear_error (&self->last_error); G_OBJECT_CLASS (soup_brotli_decompressor_parent_class)->finalize (object); } static void soup_brotli_decompressor_iface_init (GConverterIface *iface) { iface->convert = soup_brotli_decompressor_convert; iface->reset = soup_brotli_decompressor_reset; } static void soup_brotli_decompressor_class_init (SoupBrotliDecompressorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = soup_brotli_decompressor_finalize; } static void soup_brotli_decompressor_init (SoupBrotliDecompressor *self) { }