[libsoup/wip/tingping/brotli] Implement Brotli decompression support



commit c9ad424b90396eaabc7a465b690d90bce041e619
Author: Patrick Griffis <pgriffis igalia com>
Date:   Wed Feb 27 13:26:36 2019 -0500

    Implement Brotli decompression support

 libsoup/meson.build                |   9 +-
 libsoup/soup-brotli-decompressor.c | 181 +++++++++++++++++++++++++++++++++++++
 libsoup/soup-brotli-decompressor.h |  37 ++++++++
 libsoup/soup-content-decoder.c     |  21 ++++-
 meson.build                        |   8 ++
 meson_options.txt                  |   6 ++
 tests/brotli-data/compressed.br    | Bin 0 -> 1218 bytes
 tests/brotli-data/corrupt.br       | Bin 0 -> 1218 bytes
 tests/brotli-data/uncompressed.txt |   9 ++
 tests/brotli-decompressor-test.c   | 158 ++++++++++++++++++++++++++++++++
 tests/meson.build                  |   6 ++
 11 files changed, 433 insertions(+), 2 deletions(-)
---
diff --git a/libsoup/meson.build b/libsoup/meson.build
index 5f2a2156..4e18bce8 100644
--- a/libsoup/meson.build
+++ b/libsoup/meson.build
@@ -183,6 +183,12 @@ soup_gnome_installed_headers = soup_gnome_introspection_headers + [
   'soup-gnome.h'
 ]
 
+if brotlidec_dep.found()
+  soup_sources += 'soup-brotli-decompressor.c'
+  soup_headers += 'soup-brotli-decompressor.h'
+endif
+
+
 includedir = join_paths(libsoup_api_name, meson.project_name())
 install_headers(soup_installed_headers, subdir : includedir)
 
@@ -220,7 +226,8 @@ deps = [
   libxml_dep,
   sqlite_dep,
   libpsl_dep,
-  platform_deps
+  brotlidec_dep,
+  platform_deps,
 ]
 
 libsoup = library('soup-@0@'.format(apiversion),
diff --git a/libsoup/soup-brotli-decompressor.c b/libsoup/soup-brotli-decompressor.c
new file mode 100644
index 00000000..17017f72
--- /dev/null
+++ b/libsoup/soup-brotli-decompressor.c
@@ -0,0 +1,181 @@
+/* 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/quarks are designed to match GZlibDecompressor even if GConverter has its 
own */
+
+       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) {
+               if (result == BROTLI_DECODER_RESULT_ERROR)
+                       self->last_error = soup_brotli_decompressor_create_error (self);
+               else if (result == 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)");
+
+               /* BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT just means continue */
+               return G_CONVERTER_CONVERTED;
+       }
+
+       if (result == BROTLI_DECODER_RESULT_SUCCESS)
+               return G_CONVERTER_FINISHED;
+       else if (result == BROTLI_DECODER_RESULT_ERROR) {
+               soup_brotli_decompressor_set_error (self, error);
+               return G_CONVERTER_ERROR;
+       } else if (result == 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;
+       }
+
+       g_assert_not_reached ();
+       return G_CONVERTER_CONVERTED;
+}
+
+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)
+{
+}
diff --git a/libsoup/soup-brotli-decompressor.h b/libsoup/soup-brotli-decompressor.h
new file mode 100644
index 00000000..ddce4fe3
--- /dev/null
+++ b/libsoup/soup-brotli-decompressor.h
@@ -0,0 +1,37 @@
+/* soup-brotli-decompressor.h
+ *
+ * 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
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include "soup-version.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_BROTLI_DECOMPRESSOR (soup_brotli_decompressor_get_type())
+G_DECLARE_FINAL_TYPE (SoupBrotliDecompressor, soup_brotli_decompressor, SOUP, BROTLI_DECOMPRESSOR, GObject)
+
+SOUP_AVAILABLE_IN_2_66
+SoupBrotliDecompressor *soup_brotli_decompressor_new (void);
+
+SOUP_AVAILABLE_IN_2_66
+GType soup_brotli_decompressor_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
diff --git a/libsoup/soup-content-decoder.c b/libsoup/soup-content-decoder.c
index 080904aa..c485c6e6 100644
--- a/libsoup/soup-content-decoder.c
+++ b/libsoup/soup-content-decoder.c
@@ -13,6 +13,9 @@
 #include "soup-converter-wrapper.h"
 #include "soup.h"
 #include "soup-message-private.h"
+#ifdef WITH_BROTLI
+#include "soup-brotli-decompressor.h"
+#endif
 
 /**
  * SECTION:soup-content-decoder
@@ -20,7 +23,7 @@
  *
  * #SoupContentDecoder handles adding the "Accept-Encoding" header on
  * outgoing messages, and processing the "Content-Encoding" header on
- * incoming ones. Currently it supports the "gzip" and "deflate"
+ * incoming ones. Currently it supports the "gzip", "deflate", and "br"
  * content codings.
  *
  * If you are using a plain #SoupSession (ie, not #SoupSessionAsync or
@@ -169,7 +172,11 @@ soup_content_decoder_content_processor_init (SoupContentProcessorInterface *proc
 }
 
 /* This is constant for now */
+#ifdef WITH_BROTLI
+#define ACCEPT_ENCODING_HEADER "gzip, deflate, br"
+#else
 #define ACCEPT_ENCODING_HEADER "gzip, deflate"
+#endif
 
 static GConverter *
 gzip_decoder_creator (void)
@@ -183,6 +190,14 @@ zlib_decoder_creator (void)
        return (GConverter *)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_ZLIB);
 }
 
+#ifdef WITH_BROTLI
+static GConverter *
+brotli_decoder_creator (void)
+{
+       return (GConverter *)soup_brotli_decompressor_new ();
+}
+#endif
+
 static void
 soup_content_decoder_init (SoupContentDecoder *decoder)
 {
@@ -196,6 +211,10 @@ soup_content_decoder_init (SoupContentDecoder *decoder)
                             gzip_decoder_creator);
        g_hash_table_insert (decoder->priv->decoders, "deflate",
                             zlib_decoder_creator);
+#ifdef WITH_BROTLI
+       g_hash_table_insert (decoder->priv->decoders, "br",
+                            brotli_decoder_creator);
+#endif
 }
 
 static void
diff --git a/meson.build b/meson.build
index bb5ea925..65de51a3 100644
--- a/meson.build
+++ b/meson.build
@@ -64,6 +64,7 @@ glib_dep = [dependency('glib-2.0', version : glib_required_version,
 
 sqlite_dep = dependency('sqlite3', required: false)
 
+
 # Fallback check for sqlite, not all platforms ship pkg-config file
 if not sqlite_dep.found()
   cc.has_header('sqlite3.h')
@@ -83,6 +84,13 @@ endif
 
 cdata = configuration_data()
 
+if get_option('brotli')
+  brotlidec_dep = dependency('libbrotlidec')
+  cdata.set('WITH_BROTLI', true)
+else
+  brotlidec_dep = dependency('', required: false)
+endif
+
 platform_deps = []
 hidden_visibility_flag = []
 is_static_library = get_option('default_library') == 'static'
diff --git a/meson_options.txt b/meson_options.txt
index 774e8777..74f58da9 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -22,6 +22,12 @@ option('ntlm_auth',
   description : 'Where to look for ntlm_auth, path points to ntlm_auth installation (defaultly looking in 
PATH)'
 )
 
+option('brotli',
+  type : 'boolean',
+  value : true,
+  description : 'Build with Brotli decompression support'
+)
+
 option('tls_check',
   type : 'boolean',
   value : true,
diff --git a/tests/brotli-data/compressed.br b/tests/brotli-data/compressed.br
new file mode 100644
index 00000000..affb5ff7
Binary files /dev/null and b/tests/brotli-data/compressed.br differ
diff --git a/tests/brotli-data/corrupt.br b/tests/brotli-data/corrupt.br
new file mode 100644
index 00000000..2bbbeba5
Binary files /dev/null and b/tests/brotli-data/corrupt.br differ
diff --git a/tests/brotli-data/uncompressed.txt b/tests/brotli-data/uncompressed.txt
new file mode 100644
index 00000000..cd141c03
--- /dev/null
+++ b/tests/brotli-data/uncompressed.txt
@@ -0,0 +1,9 @@
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam facilisis imperdiet arcu, cursus feugiat 
velit ultricies vel. Sed consequat velit id purus finibus, ut semper felis tincidunt. Phasellus non lobortis 
justo. Duis et fermentum dui, id pharetra tellus. Aenean egestas est diam. Etiam lacinia eu diam et 
fringilla. Integer fringilla, neque non rhoncus venenatis, mauris leo lobortis dolor, id porta dui mi a nibh. 
Quisque libero orci, eleifend id ornare ut, tristique quis tellus. Nullam urna sem, sollicitudin sit amet 
urna id, elementum ornare lectus. Curabitur at luctus arcu, nec viverra odio. Donec luctus, ante ac imperdiet 
dictum, purus diam fringilla tortor, in posuere nisl nibh et ante. Mauris dapibus, est sed condimentum 
eleifend, sapien ante rhoncus dui, id porta ante libero at leo.
+
+Vivamus in ligula a mi mollis pellentesque eget sed nisl. Cras viverra semper diam. Donec consectetur 
placerat dignissim. Donec et porttitor urna. Pellentesque placerat at mi a blandit. Sed nec tellus ac sem 
semper mattis. Mauris mollis libero quam, vitae tincidunt nisi ullamcorper vel. Proin sapien purus, commodo 
at urna nec, viverra volutpat neque. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Duis consectetur, justo a consequat condimentum, lectus tortor ultricies justo, nec tincidunt turpis erat 
vitae nisi. Praesent at volutpat lacus. Orci varius natoque penatibus et magnis dis parturient montes, 
nascetur ridiculus mus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis 
egestas. Cras sodales libero vitae ultricies dictum. Integer sollicitudin eu arcu non hendrerit. Etiam vel 
maximus odio. Quisque ex diam, porta sit amet scelerisque vel, tempor nec purus. In fermentum lectus at risus 
ullamcorper rhoncus. Vestibulum nulla arcu, commodo a vestibulum vel, porttitor et metus. Phasellus facilisis 
justo vitae quam maximus, interdum fermentum risus dignissim.
+
+Phasellus tristique sollicitudin orci ac scelerisque. Integer vulputate laoreet rutrum. Nullam mauris elit, 
lobortis et elementum at, vestibulum at magna. Curabitur accumsan leo ut nisi scelerisque maximus. Cras risus 
metus, suscipit non volutpat eget, lobortis eu erat. Vestibulum sed dolor egestas, ornare est nec, molestie 
nisi. Integer laoreet, ipsum non finibus rhoncus, erat nisi lacinia dui, vulputate imperdiet odio nulla eget 
ipsum. Nam sed cursus metus. Quisque lacinia consectetur erat, et dictum mi interdum sit amet. Praesent 
eleifend luctus odio in faucibus.
+
+Donec in diam rhoncus, vehicula tortor at, molestie erat. Nulla tempor in justo ut gravida. Praesent ornare 
laoreet ante non faucibus. Donec cursus mi sit amet fringilla bibendum. Ut tincidunt, libero nec scelerisque 
lobortis, nisi nunc laoreet velit, vitae sodales est purus vehicula urna. Phasellus fringilla mi tellus, in 
convallis diam maximus et. Praesent iaculis id sem sit amet posuere. Integer ullamcorper, eros ultrices 
placerat finibus, turpis sem commodo augue, ac ornare massa ante nec nulla.
diff --git a/tests/brotli-decompressor-test.c b/tests/brotli-decompressor-test.c
new file mode 100644
index 00000000..df236677
--- /dev/null
+++ b/tests/brotli-decompressor-test.c
@@ -0,0 +1,158 @@
+/* brotli-decompressor-test.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
+ */
+
+#include "test-utils.h"
+#include "libsoup/soup-brotli-decompressor.h"
+
+static void
+test_brotli (void)
+{
+        SoupBrotliDecompressor *dec = soup_brotli_decompressor_new ();
+        char *compressed_filename = g_build_filename (g_test_get_dir (G_TEST_DIST), "brotli-data", 
"compressed.br", NULL);
+        char *uncompressed_filename = g_build_filename (g_test_get_dir (G_TEST_DIST), "brotli-data", 
"uncompressed.txt", NULL);
+        char *contents;
+        gsize length;
+        GByteArray *out_bytes = g_byte_array_new ();
+        char *in_buf;
+        GConverterResult result;
+
+        g_assert_true (g_file_get_contents (compressed_filename, &contents, &length, NULL));
+        in_buf = contents;
+
+        do {
+                GError *error = NULL;
+                guint8 out_buf[16]; /* This is stupidly small just to simulate common usage of converting in 
chunks */
+                gsize bytes_read, bytes_written;
+                result = g_converter_convert (G_CONVERTER (dec), in_buf, length, out_buf, sizeof out_buf, 0,
+                                              &bytes_read, &bytes_written, &error);
+
+                g_assert_no_error (error);
+                g_assert_cmpint (result, !=, G_CONVERTER_ERROR);
+
+                g_byte_array_append (out_bytes, out_buf, bytes_written);
+                in_buf += bytes_read;
+                length -= bytes_read;
+
+        } while (result == G_CONVERTER_CONVERTED);
+
+        g_assert_cmpint (result, ==, G_CONVERTER_FINISHED);
+
+        g_free (contents);
+        g_assert_true (g_file_get_contents (uncompressed_filename, &contents, &length, NULL));
+        g_assert_cmpstr ((char*)out_bytes->data, ==, contents);
+
+        g_byte_array_free (out_bytes, TRUE);
+        g_object_unref (dec);
+        g_free (compressed_filename);
+        g_free (contents);
+}
+
+static void
+test_brotli_corrupt (void)
+{
+        SoupBrotliDecompressor *dec = soup_brotli_decompressor_new ();
+        char *compressed_filename = g_build_filename (g_test_get_dir (G_TEST_DIST), "brotli-data", 
"corrupt.br", NULL);
+        GError *error = NULL;
+        char *contents;
+        gsize length;
+        char *in_buf;
+        GConverterResult result;
+
+        g_assert_true (g_file_get_contents (compressed_filename, &contents, &length, NULL));
+        in_buf = contents;
+
+        do {
+                guint8 out_buf[4096];
+                gsize bytes_read, bytes_written;
+                result = g_converter_convert (G_CONVERTER (dec), in_buf, length, out_buf, sizeof out_buf, 0,
+                                              &bytes_read, &bytes_written, &error);
+
+                in_buf += bytes_read;
+                length -= bytes_read;
+        } while (result == G_CONVERTER_CONVERTED);
+
+        g_assert_cmpint (result, ==, G_CONVERTER_ERROR);
+        g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
+
+        g_object_unref (dec);
+        g_free (compressed_filename);
+        g_free (contents);
+        g_error_free (error);
+}
+
+static void
+test_brotli_reset (void)
+{
+        SoupBrotliDecompressor *dec = soup_brotli_decompressor_new ();
+        char *compressed_filename = g_build_filename (g_test_get_dir (G_TEST_DIST), "brotli-data", 
"compressed.br", NULL);
+        char *contents;
+        gsize length, in_len;
+        char *in_buf;
+        GConverterResult result;
+        int iterations = 0;
+
+        g_assert_true (g_file_get_contents (compressed_filename, &contents, &length, NULL));
+        in_buf = contents;
+        in_len = length;
+
+        do {
+                GError *error = NULL;
+                guint8 out_buf[16];
+                gsize bytes_read, bytes_written;
+                result = g_converter_convert (G_CONVERTER (dec), in_buf, in_len, out_buf, sizeof out_buf, 0,
+                                              &bytes_read, &bytes_written, &error);
+
+                /* Just randomly reset in the middle and ensure everything keeps working */
+                if (iterations == 6) {
+                        g_converter_reset (G_CONVERTER (dec));
+                        in_buf = contents;
+                        in_len = length;
+                }
+
+                g_assert_no_error (error);
+                g_assert_cmpint (result, !=, G_CONVERTER_ERROR);
+                in_buf += bytes_read;
+                in_len -= bytes_read;
+                ++iterations;
+        } while (result == G_CONVERTER_CONVERTED);
+
+        g_assert_cmpint (result, ==, G_CONVERTER_FINISHED);
+
+        g_object_unref (dec);
+        g_free (compressed_filename);
+        g_free (contents);
+}
+
+int
+main (int argc, char **argv)
+{
+
+       int ret;
+
+       test_init (argc, argv, NULL);
+
+        g_test_add_func ("/brotli/basic", test_brotli);
+        g_test_add_func ("/brotli/corrupt", test_brotli_corrupt);
+        g_test_add_func ("/brotli/reset", test_brotli_reset);
+
+       ret = g_test_run ();
+       test_cleanup ();
+       return ret;
+}
diff --git a/tests/meson.build b/tests/meson.build
index 8176f29b..ea1cabd3 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -44,6 +44,12 @@ tests = [
   ['websocket', true]
 ]
 
+if brotlidec_dep.found()
+  tests += [
+    ['brotli-decompressor', true],
+  ]
+endif
+
 if have_apache
   tests += [
     ['auth', false],


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]