Reorganize files to need fewer forward declarations
[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 <string.h>
13 #include <gio/gio.h>
14
15 #include "soup-content-decoder.h"
16 #include "soup-enum-types.h"
17 #include "soup-message.h"
18 #include "soup-message-private.h"
19 #include "soup-session-feature.h"
20 #include "soup-uri.h"
21
22 /**
23  * SECTION:soup-content-decoder
24  * @short_description: Content-Encoding handler
25  *
26  * #SoupContentDecoder handles the "Accept-Encoding" header on
27  * outgoing messages, and the "Content-Encoding" header on incoming
28  * ones. If you add it to a session with soup_session_add_feature() or
29  * soup_session_add_feature_by_type(), the session will automatically
30  * use Content-Encoding as appropriate.
31  *
32  * (Note that currently there is no way to (automatically) use
33  * Content-Encoding when sending a request body, or to pick specific
34  * encoding types to support.)
35  *
36  * If #SoupContentDecoder successfully decodes the Content-Encoding,
37  * it will set the %SOUP_MESSAGE_CONTENT_DECODED flag on the message,
38  * and the message body and the chunks in the #SoupMessage::got_chunk
39  * signals 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  * #SoupContentDecoder doesn't recognize, then none of the encodings
45  * will be decoded (and the %SOUP_MESSAGE_CONTENT_DECODED flag will
46  * not be set).
47  *
48  * Since: 2.28.2
49  **/
50
51 struct _SoupContentDecoderPrivate {
52         GHashTable *decoders;
53 };
54
55 typedef GConverter * (*SoupContentDecoderCreator) (void);
56
57 static void soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
58
59 G_DEFINE_TYPE_WITH_CODE (SoupContentDecoder, soup_content_decoder, G_TYPE_OBJECT,
60                          G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
61                                                 soup_content_decoder_session_feature_init))
62
63 /* This is constant for now */
64 #define ACCEPT_ENCODING_HEADER "gzip, deflate"
65
66 static GConverter *
67 gzip_decoder_creator (void)
68 {
69         return (GConverter *)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
70 }
71
72 static GConverter *
73 zlib_decoder_creator (void)
74 {
75         return (GConverter *)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_ZLIB);
76 }
77
78 static void
79 soup_content_decoder_init (SoupContentDecoder *decoder)
80 {
81         decoder->priv = G_TYPE_INSTANCE_GET_PRIVATE (decoder,
82                                                      SOUP_TYPE_CONTENT_DECODER,
83                                                      SoupContentDecoderPrivate);
84
85         decoder->priv->decoders = g_hash_table_new (g_str_hash, g_str_equal);
86         /* Hardcoded for now */
87         g_hash_table_insert (decoder->priv->decoders, "gzip",
88                              gzip_decoder_creator);
89         g_hash_table_insert (decoder->priv->decoders, "x-gzip",
90                              gzip_decoder_creator);
91         g_hash_table_insert (decoder->priv->decoders, "deflate",
92                              zlib_decoder_creator);
93 }
94
95 static void
96 soup_content_decoder_finalize (GObject *object)
97 {
98         SoupContentDecoder *decoder = SOUP_CONTENT_DECODER (object);
99
100         g_hash_table_destroy (decoder->priv->decoders);
101
102         G_OBJECT_CLASS (soup_content_decoder_parent_class)->finalize (object);
103 }
104
105 static void
106 soup_content_decoder_class_init (SoupContentDecoderClass *decoder_class)
107 {
108         GObjectClass *object_class = G_OBJECT_CLASS (decoder_class);
109
110         g_type_class_add_private (decoder_class, sizeof (SoupContentDecoderPrivate));
111
112         object_class->finalize = soup_content_decoder_finalize;
113 }
114
115 static void
116 soup_content_decoder_got_headers_cb (SoupMessage *msg, SoupContentDecoder *decoder)
117 {
118         SoupMessagePrivate *msgpriv = SOUP_MESSAGE_GET_PRIVATE (msg);
119         const char *header;
120         GSList *encodings, *e;
121         SoupContentDecoderCreator converter_creator;
122         GConverter *converter;
123
124         header = soup_message_headers_get_list (msg->response_headers,
125                                                 "Content-Encoding");
126         if (!header)
127                 return;
128
129         /* Workaround for an apache bug (bgo 613361) */
130         if (!g_ascii_strcasecmp (header, "gzip") ||
131             !g_ascii_strcasecmp (header, "x-gzip")) {
132                 const char *content_type = soup_message_headers_get_content_type (msg->response_headers, NULL);
133
134                 if (content_type &&
135                     (!g_ascii_strcasecmp (content_type, "application/gzip") ||
136                      !g_ascii_strcasecmp (content_type, "application/x-gzip")))
137                         return;
138         }
139
140         /* OK, really, no one is ever going to use more than one
141          * encoding, but we'll be robust.
142          */
143         encodings = soup_header_parse_list (header);
144         if (!encodings)
145                 return;
146
147         for (e = encodings; e; e = e->next) {
148                 if (!g_hash_table_lookup (decoder->priv->decoders, e->data)) {
149                         soup_header_free_list (encodings);
150                         return;
151                 }
152         }
153
154         /* msgpriv->decoders should be empty at this point anyway, but
155          * clean it up if it's not.
156          */
157         g_slist_free_full (msgpriv->decoders, g_object_unref);
158
159         for (e = encodings; e; e = e->next) {
160                 converter_creator = g_hash_table_lookup (decoder->priv->decoders, e->data);
161                 converter = converter_creator ();
162
163                 /* Content-Encoding lists the codings in the order
164                  * they were applied in, so we put decoders in reverse
165                  * order so the last-applied will be the first
166                  * decoded.
167                  */
168                 msgpriv->decoders = g_slist_prepend (msgpriv->decoders, converter);
169         }
170         soup_header_free_list (encodings);
171 }
172
173 static void
174 soup_content_decoder_request_queued (SoupSessionFeature *feature,
175                                      SoupSession *session,
176                                      SoupMessage *msg)
177 {
178         SoupContentDecoder *decoder = SOUP_CONTENT_DECODER (feature);
179
180         if (!soup_message_headers_get_one (msg->request_headers,
181                                            "Accept-Encoding")) {
182                 soup_message_headers_append (msg->request_headers,
183                                              "Accept-Encoding",
184                                              ACCEPT_ENCODING_HEADER);
185         }
186
187         g_signal_connect (msg, "got-headers",
188                           G_CALLBACK (soup_content_decoder_got_headers_cb),
189                           decoder);
190 }
191
192 static void
193 soup_content_decoder_request_unqueued (SoupSessionFeature *feature,
194                                        SoupSession *session,
195                                        SoupMessage *msg)
196 {
197         g_signal_handlers_disconnect_by_func (msg, soup_content_decoder_got_headers_cb, feature);
198 }
199
200 static void
201 soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface,
202                                            gpointer interface_data)
203 {
204         feature_interface->request_queued = soup_content_decoder_request_queued;
205         feature_interface->request_unqueued = soup_content_decoder_request_unqueued;
206 }