Coverage Report

Created: 2025-07-09 06:18

/src/libsoup/libsoup/content-decoder/soup-content-decoder.c
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2
/*
3
 * soup-content-decoder.c
4
 *
5
 * Copyright (C) 2009 Red Hat, Inc.
6
 */
7
8
#ifdef HAVE_CONFIG_H
9
#include <config.h>
10
#endif
11
12
#include "soup-content-decoder.h"
13
#include "soup-converter-wrapper.h"
14
#include "soup-session-feature-private.h"
15
#include "soup-message-private.h"
16
#include "soup-message-headers-private.h"
17
#include "soup-headers.h"
18
#include "soup-uri-utils-private.h"
19
#ifdef WITH_BROTLI
20
#include "soup-brotli-decompressor.h"
21
#endif
22
23
/**
24
 * SoupContentDecoder:
25
 *
26
 * Handles decoding of HTTP messages.
27
 *
28
 * [class@ContentDecoder] handles adding the "Accept-Encoding" header on
29
 * outgoing messages, and processing the "Content-Encoding" header on
30
 * incoming ones. Currently it supports the "gzip", "deflate", and "br"
31
 * content codings.
32
 *
33
 * A [class@ContentDecoder] will automatically be
34
 * added to the session by default. (You can use
35
 * [method@Session.remove_feature_by_type] if you don't
36
 * want this.)
37
 *
38
 * If [class@ContentDecoder] successfully decodes the Content-Encoding,
39
 * the message body will contain the decoded data; however, the message headers
40
 * will be unchanged (and so "Content-Encoding" will still be present,
41
 * "Content-Length" will describe the original encoded length, etc).
42
 *
43
 * If "Content-Encoding" contains any encoding types that
44
 * [class@ContentDecoder] doesn't recognize, then none of the encodings
45
 * will be decoded.
46
 *
47
 * (Note that currently there is no way to (automatically) use
48
 * Content-Encoding when sending a request body, or to pick specific
49
 * encoding types to support.)
50
 **/
51
52
struct _SoupContentDecoder {
53
  GObject parent;
54
};
55
56
typedef struct {
57
  GHashTable *decoders;
58
} SoupContentDecoderPrivate;
59
60
typedef GConverter * (*SoupContentDecoderCreator) (void);
61
62
static void soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
63
64
static SoupContentProcessorInterface *soup_content_decoder_default_content_processor_interface;
65
static void soup_content_decoder_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data);
66
67
68
G_DEFINE_FINAL_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))
74
75
static GSList *
76
soup_content_decoder_get_decoders_for_msg (SoupContentDecoder *decoder, SoupMessage *msg)
77
0
{
78
0
        SoupContentDecoderPrivate *priv = soup_content_decoder_get_instance_private (decoder);
79
0
  const char *header;
80
0
  GSList *encodings, *e, *decoders = NULL;
81
0
  SoupContentDecoderCreator converter_creator;
82
0
  GConverter *converter;
83
84
0
  header = soup_message_headers_get_list_common (soup_message_get_response_headers (msg),
85
0
                                                       SOUP_HEADER_CONTENT_ENCODING);
86
0
  if (!header)
87
0
    return NULL;
88
89
  /* Workaround for an apache bug (bgo 613361) */
90
0
  if (!g_ascii_strcasecmp (header, "gzip") ||
91
0
      !g_ascii_strcasecmp (header, "x-gzip")) {
92
0
    const char *content_type = soup_message_headers_get_content_type (soup_message_get_response_headers (msg), NULL);
93
94
0
    if (content_type &&
95
0
        (!g_ascii_strcasecmp (content_type, "application/gzip") ||
96
0
         !g_ascii_strcasecmp (content_type, "application/x-gzip")))
97
0
      return NULL;
98
0
  }
99
100
  /* OK, really, no one is ever going to use more than one
101
   * encoding, but we'll be robust.
102
   */
103
0
  encodings = soup_header_parse_list (header);
104
0
  if (!encodings)
105
0
    return NULL;
106
107
0
  for (e = encodings; e; e = e->next) {
108
0
    if (!g_hash_table_lookup (priv->decoders, e->data)) {
109
0
      soup_header_free_list (encodings);
110
0
      return NULL;
111
0
    }
112
0
  }
113
114
0
  for (e = encodings; e; e = e->next) {
115
0
    converter_creator = g_hash_table_lookup (priv->decoders, e->data);
116
0
    converter = converter_creator ();
117
118
    /* Content-Encoding lists the codings in the order
119
     * they were applied in, so we put decoders in reverse
120
     * order so the last-applied will be the first
121
     * decoded.
122
     */
123
0
    decoders = g_slist_prepend (decoders, converter);
124
0
  }
125
0
  soup_header_free_list (encodings);
126
127
0
  return decoders;
128
0
}
129
130
static GInputStream*
131
soup_content_decoder_content_processor_wrap_input (SoupContentProcessor *processor,
132
               GInputStream *base_stream,
133
               SoupMessage *msg,
134
               GError **error)
135
0
{
136
0
  GSList *decoders, *d;
137
0
  GInputStream *istream;
138
139
0
  decoders = soup_content_decoder_get_decoders_for_msg (SOUP_CONTENT_DECODER (processor), msg);
140
0
  if (!decoders)
141
0
    return NULL;
142
143
0
  istream = g_object_ref (base_stream);
144
0
  for (d = decoders; d; d = d->next) {
145
0
    GConverter *decoder, *wrapper;
146
0
    GInputStream *filter;
147
148
0
    decoder = d->data;
149
0
    wrapper = soup_converter_wrapper_new (decoder, msg);
150
0
    filter = g_object_new (G_TYPE_CONVERTER_INPUT_STREAM,
151
0
               "base-stream", istream,
152
0
               "converter", wrapper,
153
0
               NULL);
154
0
    g_object_unref (istream);
155
0
    g_object_unref (wrapper);
156
0
    istream = filter;
157
0
  }
158
159
0
  g_slist_free_full (decoders, g_object_unref);
160
161
0
  return istream;
162
0
}
163
164
static void
165
soup_content_decoder_content_processor_init (SoupContentProcessorInterface *processor_interface,
166
               gpointer interface_data)
167
0
{
168
0
  soup_content_decoder_default_content_processor_interface =
169
0
    g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR);
170
171
0
  processor_interface->processing_stage = SOUP_STAGE_CONTENT_ENCODING;
172
0
  processor_interface->wrap_input = soup_content_decoder_content_processor_wrap_input;
173
0
}
174
175
static GConverter *
176
gzip_decoder_creator (void)
177
0
{
178
0
  return (GConverter *)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
179
0
}
180
181
static GConverter *
182
zlib_decoder_creator (void)
183
0
{
184
0
  return (GConverter *)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_ZLIB);
185
0
}
186
187
#ifdef WITH_BROTLI
188
static GConverter *
189
brotli_decoder_creator (void)
190
0
{
191
0
  return (GConverter *)soup_brotli_decompressor_new ();
192
0
}
193
#endif
194
195
static void
196
soup_content_decoder_init (SoupContentDecoder *decoder)
197
0
{
198
0
        SoupContentDecoderPrivate *priv = soup_content_decoder_get_instance_private (decoder);
199
200
0
  priv->decoders = g_hash_table_new (g_str_hash, g_str_equal);
201
  /* Hardcoded for now */
202
0
  g_hash_table_insert (priv->decoders, "gzip",
203
0
           gzip_decoder_creator);
204
0
  g_hash_table_insert (priv->decoders, "x-gzip",
205
0
           gzip_decoder_creator);
206
0
  g_hash_table_insert (priv->decoders, "deflate",
207
0
           zlib_decoder_creator);
208
0
#ifdef WITH_BROTLI
209
0
  g_hash_table_insert (priv->decoders, "br",
210
0
           brotli_decoder_creator);
211
0
#endif
212
0
}
213
214
static void
215
soup_content_decoder_finalize (GObject *object)
216
0
{
217
0
  SoupContentDecoder *decoder = SOUP_CONTENT_DECODER (object);
218
0
        SoupContentDecoderPrivate *priv = soup_content_decoder_get_instance_private (decoder);
219
220
0
  g_hash_table_destroy (priv->decoders);
221
222
0
  G_OBJECT_CLASS (soup_content_decoder_parent_class)->finalize (object);
223
0
}
224
225
static void
226
soup_content_decoder_class_init (SoupContentDecoderClass *decoder_class)
227
0
{
228
0
  GObjectClass *object_class = G_OBJECT_CLASS (decoder_class);
229
230
0
  object_class->finalize = soup_content_decoder_finalize;
231
0
}
232
233
static void
234
soup_content_decoder_request_queued (SoupSessionFeature *feature,
235
             SoupMessage        *msg)
236
0
{
237
0
  if (!soup_message_headers_get_one_common (soup_message_get_request_headers (msg),
238
0
                                                  SOUP_HEADER_ACCEPT_ENCODING)) {
239
0
                const char *header = "gzip, deflate";
240
241
0
#ifdef WITH_BROTLI
242
                /* brotli is only enabled over TLS connections
243
                 * as other browsers have found that some networks have expectations
244
                 * regarding the encoding of HTTP messages and this may break those
245
                 * expectations. Firefox and Chromium behave similarly.
246
                 */
247
0
                if (soup_uri_is_https (soup_message_get_uri (msg)))
248
0
                        header = "gzip, deflate, br";
249
0
#endif
250
251
0
    soup_message_headers_append_common (soup_message_get_request_headers (msg),
252
0
                                                    SOUP_HEADER_ACCEPT_ENCODING, header);
253
0
  }
254
0
}
255
256
static void
257
soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface,
258
             gpointer interface_data)
259
0
{
260
0
  feature_interface->request_queued = soup_content_decoder_request_queued;
261
0
}