1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-content-decoder.c
5 * Copyright (C) 2009 Red Hat, Inc.
12 #include "soup-content-decoder.h"
13 #include "soup-converter-wrapper.h"
15 #include "soup-message-private.h"
17 #include "soup-brotli-decompressor.h"
21 * SECTION:soup-content-decoder
22 * @short_description: Content-Encoding handler
24 * #SoupContentDecoder handles adding the "Accept-Encoding" header on
25 * outgoing messages, and processing the "Content-Encoding" header on
26 * incoming ones. Currently it supports the "gzip", "deflate", and "br"
29 * If you are using a plain #SoupSession (ie, not #SoupSessionAsync or
30 * #SoupSessionSync), then a #SoupContentDecoder will automatically be
31 * added to the session by default. (You can use
32 * %SOUP_SESSION_REMOVE_FEATURE_BY_TYPE at construct time if you don't
33 * want this.) If you are using one of the deprecated #SoupSession
34 * subclasses, you can add a #SoupContentDecoder to your session with
35 * soup_session_add_feature() or soup_session_add_feature_by_type().
37 * If #SoupContentDecoder successfully decodes the Content-Encoding,
38 * it will set the %SOUP_MESSAGE_CONTENT_DECODED flag on the message,
39 * and the message body and the chunks in the #SoupMessage::got_chunk
40 * signals will contain the decoded data; however, the message headers
41 * will be unchanged (and so "Content-Encoding" will still be present,
42 * "Content-Length" will describe the original encoded length, etc).
44 * If "Content-Encoding" contains any encoding types that
45 * #SoupContentDecoder doesn't recognize, then none of the encodings
46 * will be decoded (and the %SOUP_MESSAGE_CONTENT_DECODED flag will
49 * (Note that currently there is no way to (automatically) use
50 * Content-Encoding when sending a request body, or to pick specific
51 * encoding types to support.)
56 struct _SoupContentDecoderPrivate {
60 typedef GConverter * (*SoupContentDecoderCreator) (void);
62 static void soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
64 static SoupContentProcessorInterface *soup_content_decoder_default_content_processor_interface;
65 static void soup_content_decoder_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data);
68 G_DEFINE_TYPE_WITH_CODE (SoupContentDecoder, soup_content_decoder, G_TYPE_OBJECT,
69 G_ADD_PRIVATE (SoupContentDecoder)
70 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
71 soup_content_decoder_session_feature_init)
72 G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR,
73 soup_content_decoder_content_processor_init))
76 soup_content_decoder_get_decoders_for_msg (SoupContentDecoder *decoder, SoupMessage *msg)
79 GSList *encodings, *e, *decoders = NULL;
80 SoupContentDecoderCreator converter_creator;
81 GConverter *converter;
83 header = soup_message_headers_get_list (msg->response_headers,
88 /* Workaround for an apache bug (bgo 613361) */
89 if (!g_ascii_strcasecmp (header, "gzip") ||
90 !g_ascii_strcasecmp (header, "x-gzip")) {
91 const char *content_type = soup_message_headers_get_content_type (msg->response_headers, NULL);
94 (!g_ascii_strcasecmp (content_type, "application/gzip") ||
95 !g_ascii_strcasecmp (content_type, "application/x-gzip")))
99 /* OK, really, no one is ever going to use more than one
100 * encoding, but we'll be robust.
102 encodings = soup_header_parse_list (header);
106 for (e = encodings; e; e = e->next) {
107 if (!g_hash_table_lookup (decoder->priv->decoders, e->data)) {
108 soup_header_free_list (encodings);
113 for (e = encodings; e; e = e->next) {
114 converter_creator = g_hash_table_lookup (decoder->priv->decoders, e->data);
115 converter = converter_creator ();
117 /* Content-Encoding lists the codings in the order
118 * they were applied in, so we put decoders in reverse
119 * order so the last-applied will be the first
122 decoders = g_slist_prepend (decoders, converter);
124 soup_header_free_list (encodings);
130 soup_content_decoder_content_processor_wrap_input (SoupContentProcessor *processor,
131 GInputStream *base_stream,
135 GSList *decoders, *d;
136 GInputStream *istream;
138 decoders = soup_content_decoder_get_decoders_for_msg (SOUP_CONTENT_DECODER (processor), msg);
142 istream = g_object_ref (base_stream);
143 for (d = decoders; d; d = d->next) {
144 GConverter *decoder, *wrapper;
145 GInputStream *filter;
148 wrapper = soup_converter_wrapper_new (decoder, msg);
149 filter = g_object_new (G_TYPE_CONVERTER_INPUT_STREAM,
150 "base-stream", istream,
151 "converter", wrapper,
153 g_object_unref (istream);
154 g_object_unref (wrapper);
158 g_slist_free_full (decoders, g_object_unref);
164 soup_content_decoder_content_processor_init (SoupContentProcessorInterface *processor_interface,
165 gpointer interface_data)
167 soup_content_decoder_default_content_processor_interface =
168 g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR);
170 processor_interface->processing_stage = SOUP_STAGE_CONTENT_ENCODING;
171 processor_interface->wrap_input = soup_content_decoder_content_processor_wrap_input;
174 /* This is constant for now */
176 /* Don't advertise br support atm until some edge cases are resolved:
177 https://gitlab.gnome.org/GNOME/libsoup/issues/146 */
178 /* #define ACCEPT_ENCODING_HEADER "gzip, deflate, br" */
179 #define ACCEPT_ENCODING_HEADER "gzip, deflate"
181 #define ACCEPT_ENCODING_HEADER "gzip, deflate"
185 gzip_decoder_creator (void)
187 return (GConverter *)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
191 zlib_decoder_creator (void)
193 return (GConverter *)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_ZLIB);
198 brotli_decoder_creator (void)
200 return (GConverter *)soup_brotli_decompressor_new ();
205 soup_content_decoder_init (SoupContentDecoder *decoder)
207 decoder->priv = soup_content_decoder_get_instance_private (decoder);
209 decoder->priv->decoders = g_hash_table_new (g_str_hash, g_str_equal);
210 /* Hardcoded for now */
211 g_hash_table_insert (decoder->priv->decoders, "gzip",
212 gzip_decoder_creator);
213 g_hash_table_insert (decoder->priv->decoders, "x-gzip",
214 gzip_decoder_creator);
215 g_hash_table_insert (decoder->priv->decoders, "deflate",
216 zlib_decoder_creator);
218 g_hash_table_insert (decoder->priv->decoders, "br",
219 brotli_decoder_creator);
224 soup_content_decoder_finalize (GObject *object)
226 SoupContentDecoder *decoder = SOUP_CONTENT_DECODER (object);
228 g_hash_table_destroy (decoder->priv->decoders);
230 G_OBJECT_CLASS (soup_content_decoder_parent_class)->finalize (object);
234 soup_content_decoder_class_init (SoupContentDecoderClass *decoder_class)
236 GObjectClass *object_class = G_OBJECT_CLASS (decoder_class);
238 object_class->finalize = soup_content_decoder_finalize;
242 soup_content_decoder_request_queued (SoupSessionFeature *feature,
243 SoupSession *session,
246 if (!soup_message_headers_get_one (msg->request_headers,
247 "Accept-Encoding")) {
248 soup_message_headers_append (msg->request_headers,
250 ACCEPT_ENCODING_HEADER);
255 soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface,
256 gpointer interface_data)
258 feature_interface->request_queued = soup_content_decoder_request_queued;