/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 | } |