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 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
|
/* -*- 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.h"
#include "soup-message-private.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.
*
* If you are using a plain #SoupSession (ie, not #SoupSessionAsync or
* #SoupSessionSync), then 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,
* it will set the %SOUP_MESSAGE_CONTENT_DECODED flag on the message,
* and the message body and the chunks in the #SoupMessage::got_chunk
* signals 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 (and the %SOUP_MESSAGE_CONTENT_DECODED flag will
* not be set).
*
* (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
**/
struct _SoupContentDecoderPrivate {
GHashTable *decoders;
};
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)
{
const char *header;
GSList *encodings, *e, *decoders = NULL;
SoupContentDecoderCreator converter_creator;
GConverter *converter;
header = soup_message_headers_get_list (msg->response_headers,
"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 (msg->response_headers, 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 (decoder->priv->decoders, e->data)) {
soup_header_free_list (encodings);
return NULL;
}
}
for (e = encodings; e; e = e->next) {
converter_creator = g_hash_table_lookup (decoder->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)
{
decoder->priv = soup_content_decoder_get_instance_private (decoder);
decoder->priv->decoders = g_hash_table_new (g_str_hash, g_str_equal);
/* Hardcoded for now */
g_hash_table_insert (decoder->priv->decoders, "gzip",
gzip_decoder_creator);
g_hash_table_insert (decoder->priv->decoders, "x-gzip",
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
soup_content_decoder_finalize (GObject *object)
{
SoupContentDecoder *decoder = SOUP_CONTENT_DECODER (object);
g_hash_table_destroy (decoder->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,
SoupSession *session,
SoupMessage *msg)
{
if (!soup_message_headers_get_one (msg->request_headers,
"Accept-Encoding")) {
soup_message_headers_append (msg->request_headers,
"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;
}
|