summaryrefslogtreecommitdiff
path: root/libsoup/content-decoder/soup-content-decoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'libsoup/content-decoder/soup-content-decoder.c')
-rw-r--r--libsoup/content-decoder/soup-content-decoder.c267
1 files changed, 267 insertions, 0 deletions
diff --git a/libsoup/content-decoder/soup-content-decoder.c b/libsoup/content-decoder/soup-content-decoder.c
new file mode 100644
index 00000000..5be8cc38
--- /dev/null
+++ b/libsoup/content-decoder/soup-content-decoder.c
@@ -0,0 +1,267 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-content-decoder.c
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "soup-content-decoder.h"
+#include "soup-converter-wrapper.h"
+#include "soup-session-feature.h"
+#include "soup-message-private.h"
+#include "soup-headers.h"
+#ifdef WITH_BROTLI
+#include "soup-brotli-decompressor.h"
+#endif
+
+/**
+ * SECTION:soup-content-decoder
+ * @short_description: Content-Encoding handler
+ *
+ * #SoupContentDecoder handles adding the "Accept-Encoding" header on
+ * outgoing messages, and processing the "Content-Encoding" header on
+ * incoming ones. Currently it supports the "gzip", "deflate", and "br"
+ * content codings.
+ *
+ * A #SoupContentDecoder will automatically be
+ * added to the session by default. (You can use
+ * %SOUP_SESSION_REMOVE_FEATURE_BY_TYPE at construct time if you don't
+ * want this.) If you are using one of the deprecated #SoupSession
+ * subclasses, you can add a #SoupContentDecoder to your session with
+ * soup_session_add_feature() or soup_session_add_feature_by_type().
+ *
+ * If #SoupContentDecoder successfully decodes the Content-Encoding,
+ * the message body will contain the decoded data; however, the message headers
+ * will be unchanged (and so "Content-Encoding" will still be present,
+ * "Content-Length" will describe the original encoded length, etc).
+ *
+ * If "Content-Encoding" contains any encoding types that
+ * #SoupContentDecoder doesn't recognize, then none of the encodings
+ * will be decoded.
+ *
+ * (Note that currently there is no way to (automatically) use
+ * Content-Encoding when sending a request body, or to pick specific
+ * encoding types to support.)
+ *
+ * Since: 2.30
+ **/
+
+/**
+ * SoupContentDecoder:
+ *
+ * Class handling decoding of HTTP messages.
+ */
+
+struct _SoupContentDecoder {
+ GObject parent;
+};
+
+typedef struct {
+ GHashTable *decoders;
+} SoupContentDecoderPrivate;
+
+typedef GConverter * (*SoupContentDecoderCreator) (void);
+
+static void soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
+
+static SoupContentProcessorInterface *soup_content_decoder_default_content_processor_interface;
+static void soup_content_decoder_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data);
+
+
+G_DEFINE_TYPE_WITH_CODE (SoupContentDecoder, soup_content_decoder, G_TYPE_OBJECT,
+ G_ADD_PRIVATE (SoupContentDecoder)
+ G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
+ soup_content_decoder_session_feature_init)
+ G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR,
+ soup_content_decoder_content_processor_init))
+
+static GSList *
+soup_content_decoder_get_decoders_for_msg (SoupContentDecoder *decoder, SoupMessage *msg)
+{
+ SoupContentDecoderPrivate *priv = soup_content_decoder_get_instance_private (decoder);
+ const char *header;
+ GSList *encodings, *e, *decoders = NULL;
+ SoupContentDecoderCreator converter_creator;
+ GConverter *converter;
+
+ header = soup_message_headers_get_list (soup_message_get_response_headers (msg),
+ "Content-Encoding");
+ if (!header)
+ return NULL;
+
+ /* Workaround for an apache bug (bgo 613361) */
+ if (!g_ascii_strcasecmp (header, "gzip") ||
+ !g_ascii_strcasecmp (header, "x-gzip")) {
+ const char *content_type = soup_message_headers_get_content_type (soup_message_get_response_headers (msg), NULL);
+
+ if (content_type &&
+ (!g_ascii_strcasecmp (content_type, "application/gzip") ||
+ !g_ascii_strcasecmp (content_type, "application/x-gzip")))
+ return NULL;
+ }
+
+ /* OK, really, no one is ever going to use more than one
+ * encoding, but we'll be robust.
+ */
+ encodings = soup_header_parse_list (header);
+ if (!encodings)
+ return NULL;
+
+ for (e = encodings; e; e = e->next) {
+ if (!g_hash_table_lookup (priv->decoders, e->data)) {
+ soup_header_free_list (encodings);
+ return NULL;
+ }
+ }
+
+ for (e = encodings; e; e = e->next) {
+ converter_creator = g_hash_table_lookup (priv->decoders, e->data);
+ converter = converter_creator ();
+
+ /* Content-Encoding lists the codings in the order
+ * they were applied in, so we put decoders in reverse
+ * order so the last-applied will be the first
+ * decoded.
+ */
+ decoders = g_slist_prepend (decoders, converter);
+ }
+ soup_header_free_list (encodings);
+
+ return decoders;
+}
+
+static GInputStream*
+soup_content_decoder_content_processor_wrap_input (SoupContentProcessor *processor,
+ GInputStream *base_stream,
+ SoupMessage *msg,
+ GError **error)
+{
+ GSList *decoders, *d;
+ GInputStream *istream;
+
+ decoders = soup_content_decoder_get_decoders_for_msg (SOUP_CONTENT_DECODER (processor), msg);
+ if (!decoders)
+ return NULL;
+
+ istream = g_object_ref (base_stream);
+ for (d = decoders; d; d = d->next) {
+ GConverter *decoder, *wrapper;
+ GInputStream *filter;
+
+ decoder = d->data;
+ wrapper = soup_converter_wrapper_new (decoder, msg);
+ filter = g_object_new (G_TYPE_CONVERTER_INPUT_STREAM,
+ "base-stream", istream,
+ "converter", wrapper,
+ NULL);
+ g_object_unref (istream);
+ g_object_unref (wrapper);
+ istream = filter;
+ }
+
+ g_slist_free_full (decoders, g_object_unref);
+
+ return istream;
+}
+
+static void
+soup_content_decoder_content_processor_init (SoupContentProcessorInterface *processor_interface,
+ gpointer interface_data)
+{
+ soup_content_decoder_default_content_processor_interface =
+ g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR);
+
+ processor_interface->processing_stage = SOUP_STAGE_CONTENT_ENCODING;
+ processor_interface->wrap_input = soup_content_decoder_content_processor_wrap_input;
+}
+
+/* This is constant for now */
+#ifdef WITH_BROTLI
+/* Don't advertise br support atm until some edge cases are resolved:
+ https://gitlab.gnome.org/GNOME/libsoup/issues/146 */
+/* #define ACCEPT_ENCODING_HEADER "gzip, deflate, br" */
+#define ACCEPT_ENCODING_HEADER "gzip, deflate"
+#else
+#define ACCEPT_ENCODING_HEADER "gzip, deflate"
+#endif
+
+static GConverter *
+gzip_decoder_creator (void)
+{
+ return (GConverter *)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
+}
+
+static GConverter *
+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)
+{
+ SoupContentDecoderPrivate *priv = soup_content_decoder_get_instance_private (decoder);
+
+ priv->decoders = g_hash_table_new (g_str_hash, g_str_equal);
+ /* Hardcoded for now */
+ g_hash_table_insert (priv->decoders, "gzip",
+ gzip_decoder_creator);
+ g_hash_table_insert (priv->decoders, "x-gzip",
+ gzip_decoder_creator);
+ g_hash_table_insert (priv->decoders, "deflate",
+ zlib_decoder_creator);
+#ifdef WITH_BROTLI
+ g_hash_table_insert (priv->decoders, "br",
+ brotli_decoder_creator);
+#endif
+}
+
+static void
+soup_content_decoder_finalize (GObject *object)
+{
+ SoupContentDecoder *decoder = SOUP_CONTENT_DECODER (object);
+ SoupContentDecoderPrivate *priv = soup_content_decoder_get_instance_private (decoder);
+
+ g_hash_table_destroy (priv->decoders);
+
+ G_OBJECT_CLASS (soup_content_decoder_parent_class)->finalize (object);
+}
+
+static void
+soup_content_decoder_class_init (SoupContentDecoderClass *decoder_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (decoder_class);
+
+ object_class->finalize = soup_content_decoder_finalize;
+}
+
+static void
+soup_content_decoder_request_queued (SoupSessionFeature *feature,
+ SoupMessage *msg)
+{
+ if (!soup_message_headers_get_one (soup_message_get_request_headers (msg),
+ "Accept-Encoding")) {
+ soup_message_headers_append (soup_message_get_request_headers (msg),
+ "Accept-Encoding",
+ ACCEPT_ENCODING_HEADER);
+ }
+}
+
+static void
+soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface,
+ gpointer interface_data)
+{
+ feature_interface->request_queued = soup_content_decoder_request_queued;
+}