Remove extern "C" wrapping other includes
[platform/upstream/libsoup.git] / libsoup / soup-content-decoder.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; 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.h"
15 #include "soup-message-private.h"
16 #ifdef WITH_BROTLI
17 #include "soup-brotli-decompressor.h"
18 #endif
19
20 /**
21  * SECTION:soup-content-decoder
22  * @short_description: Content-Encoding handler
23  *
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"
27  * content codings.
28  *
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().
36  *
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).
43  *
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
47  * not be set).
48  *
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.)
52  *
53  * Since: 2.30
54  **/
55
56 struct _SoupContentDecoderPrivate {
57         GHashTable *decoders;
58 };
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_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 {
78         const char *header;
79         GSList *encodings, *e, *decoders = NULL;
80         SoupContentDecoderCreator converter_creator;
81         GConverter *converter;
82
83         header = soup_message_headers_get_list (msg->response_headers,
84                                                 "Content-Encoding");
85         if (!header)
86                 return NULL;
87
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);
92
93                 if (content_type &&
94                     (!g_ascii_strcasecmp (content_type, "application/gzip") ||
95                      !g_ascii_strcasecmp (content_type, "application/x-gzip")))
96                         return NULL;
97         }
98
99         /* OK, really, no one is ever going to use more than one
100          * encoding, but we'll be robust.
101          */
102         encodings = soup_header_parse_list (header);
103         if (!encodings)
104                 return NULL;
105
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);
109                         return NULL;
110                 }
111         }
112
113         for (e = encodings; e; e = e->next) {
114                 converter_creator = g_hash_table_lookup (decoder->priv->decoders, e->data);
115                 converter = converter_creator ();
116
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
120                  * decoded.
121                  */
122                 decoders = g_slist_prepend (decoders, converter);
123         }
124         soup_header_free_list (encodings);
125
126         return decoders;
127 }
128
129 static GInputStream*
130 soup_content_decoder_content_processor_wrap_input (SoupContentProcessor *processor,
131                                                    GInputStream *base_stream,
132                                                    SoupMessage *msg,
133                                                    GError **error)
134 {
135         GSList *decoders, *d;
136         GInputStream *istream;
137
138         decoders = soup_content_decoder_get_decoders_for_msg (SOUP_CONTENT_DECODER (processor), msg);
139         if (!decoders)
140                 return NULL;
141
142         istream = g_object_ref (base_stream);
143         for (d = decoders; d; d = d->next) {
144                 GConverter *decoder, *wrapper;
145                 GInputStream *filter;
146
147                 decoder = d->data;
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,
152                                        NULL);
153                 g_object_unref (istream);
154                 g_object_unref (wrapper);
155                 istream = filter;
156         }
157
158         g_slist_free_full (decoders, g_object_unref);
159
160         return istream;
161 }
162
163 static void
164 soup_content_decoder_content_processor_init (SoupContentProcessorInterface *processor_interface,
165                                              gpointer interface_data)
166 {
167         soup_content_decoder_default_content_processor_interface =
168                 g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR);
169
170         processor_interface->processing_stage = SOUP_STAGE_CONTENT_ENCODING;
171         processor_interface->wrap_input = soup_content_decoder_content_processor_wrap_input;
172 }
173
174 /* This is constant for now */
175 #ifdef WITH_BROTLI
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"
180 #else
181 #define ACCEPT_ENCODING_HEADER "gzip, deflate"
182 #endif
183
184 static GConverter *
185 gzip_decoder_creator (void)
186 {
187         return (GConverter *)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
188 }
189
190 static GConverter *
191 zlib_decoder_creator (void)
192 {
193         return (GConverter *)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_ZLIB);
194 }
195
196 #ifdef WITH_BROTLI
197 static GConverter *
198 brotli_decoder_creator (void)
199 {
200         return (GConverter *)soup_brotli_decompressor_new ();
201 }
202 #endif
203
204 static void
205 soup_content_decoder_init (SoupContentDecoder *decoder)
206 {
207         decoder->priv = soup_content_decoder_get_instance_private (decoder);
208
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);
217 #ifdef WITH_BROTLI
218         g_hash_table_insert (decoder->priv->decoders, "br",
219                              brotli_decoder_creator);
220 #endif
221 }
222
223 static void
224 soup_content_decoder_finalize (GObject *object)
225 {
226         SoupContentDecoder *decoder = SOUP_CONTENT_DECODER (object);
227
228         g_hash_table_destroy (decoder->priv->decoders);
229
230         G_OBJECT_CLASS (soup_content_decoder_parent_class)->finalize (object);
231 }
232
233 static void
234 soup_content_decoder_class_init (SoupContentDecoderClass *decoder_class)
235 {
236         GObjectClass *object_class = G_OBJECT_CLASS (decoder_class);
237
238         object_class->finalize = soup_content_decoder_finalize;
239 }
240
241 static void
242 soup_content_decoder_request_queued (SoupSessionFeature *feature,
243                                      SoupSession *session,
244                                      SoupMessage *msg)
245 {
246         if (!soup_message_headers_get_one (msg->request_headers,
247                                            "Accept-Encoding")) {
248                 soup_message_headers_append (msg->request_headers,
249                                              "Accept-Encoding",
250                                              ACCEPT_ENCODING_HEADER);
251         }
252 }
253
254 static void
255 soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface,
256                                            gpointer interface_data)
257 {
258         feature_interface->request_queued = soup_content_decoder_request_queued;
259 }