1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
|
/* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <brotli/decode.h>
#include <gio/gio.h>
#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)
{
}
|