24ea751845a98718fa18f7525c916e59e4a7e4fb
[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-coding-gzip.h"
17 #include "soup-enum-types.h"
18 #include "soup-message.h"
19 #include "soup-message-private.h"
20 #include "soup-session-feature.h"
21 #include "soup-uri.h"
22
23 /**
24  * SECTION:soup-content-decoder
25  * @short_description: Content-Encoding handler
26  *
27  * #SoupContentDecoder handles the "Accept-Encoding" header on
28  * outgoing messages, and the "Content-Encoding" header on incoming
29  * ones. If you add it to a session with soup_session_add_feature() or
30  * soup_session_add_feature_by_type(), the session will automatically
31  * use Content-Encoding as appropriate.
32  *
33  * (Note that currently there is no way to (automatically) use
34  * Content-Encoding when sending a request body, or to pick specific
35  * encoding types to support.)
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  * Since: 2.28.2
50  **/
51
52 struct _SoupContentDecoderPrivate {
53         GHashTable *codings;
54 };
55
56 static void soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
57
58 static void request_queued (SoupSessionFeature *feature, SoupSession *session, SoupMessage *msg);
59 static void request_unqueued (SoupSessionFeature *feature, SoupSession *session, SoupMessage *msg);
60
61 static void finalize (GObject *object);
62
63 G_DEFINE_TYPE_WITH_CODE (SoupContentDecoder, soup_content_decoder, G_TYPE_OBJECT,
64                          G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
65                                                 soup_content_decoder_session_feature_init))
66
67 /* This is constant for now */
68 #define ACCEPT_ENCODING_HEADER "gzip"
69
70 static void
71 soup_content_decoder_init (SoupContentDecoder *decoder)
72 {
73         decoder->priv = G_TYPE_INSTANCE_GET_PRIVATE (decoder,
74                                                      SOUP_TYPE_CONTENT_DECODER,
75                                                      SoupContentDecoderPrivate);
76
77         decoder->priv->codings = g_hash_table_new (g_str_hash, g_str_equal);
78         /* Hardcoded for now */
79         g_hash_table_insert (decoder->priv->codings, "gzip",
80                              GSIZE_TO_POINTER (SOUP_TYPE_CODING_GZIP));
81         g_hash_table_insert (decoder->priv->codings, "x-gzip",
82                              GSIZE_TO_POINTER (SOUP_TYPE_CODING_GZIP));
83 }
84
85 static void
86 soup_content_decoder_class_init (SoupContentDecoderClass *decoder_class)
87 {
88         GObjectClass *object_class = G_OBJECT_CLASS (decoder_class);
89
90         g_type_class_add_private (decoder_class, sizeof (SoupContentDecoderPrivate));
91
92         object_class->finalize = finalize;
93 }
94
95 static void
96 soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface,
97                                            gpointer interface_data)
98 {
99         feature_interface->request_queued = request_queued;
100         feature_interface->request_unqueued = request_unqueued;
101 }
102
103 static void
104 finalize (GObject *object)
105 {
106         SoupContentDecoder *decoder = SOUP_CONTENT_DECODER (object);
107
108         g_hash_table_destroy (decoder->priv->codings);
109
110         G_OBJECT_CLASS (soup_content_decoder_parent_class)->finalize (object);
111 }
112
113 static void
114 soup_content_decoder_got_headers_cb (SoupMessage *msg, SoupContentDecoder *decoder)
115 {
116         SoupMessagePrivate *msgpriv = SOUP_MESSAGE_GET_PRIVATE (msg);
117         const char *header;
118         GSList *encodings, *e;
119         GType coding_type;
120         SoupCoding *coding; 
121
122         header = soup_message_headers_get_list (msg->response_headers,
123                                                 "Content-Encoding");
124         if (!header)
125                 return;
126
127         /* OK, really, no one is ever going to use more than one
128          * encoding, but we'll be robust.
129          */
130         encodings = soup_header_parse_list (header);
131         if (!encodings)
132                 return;
133
134         for (e = encodings; e; e = e->next) {
135                 if (!g_hash_table_lookup (decoder->priv->codings, e->data)) {
136                         soup_header_free_list (encodings);
137                         return;
138                 }
139         }
140
141         msgpriv->decoders = NULL;
142         for (e = encodings; e; e = e->next) {
143                 coding_type = (GType) GPOINTER_TO_SIZE (g_hash_table_lookup (decoder->priv->codings, e->data));
144                 coding = g_object_new (coding_type,
145                                        SOUP_CODING_DIRECTION, SOUP_CODING_DECODE,
146                                        NULL);
147
148                 /* Content-Encoding lists the codings in the order
149                  * they were applied in, so we put decoders in reverse
150                  * order so the last-applied will be the first
151                  * decoded.
152                  */
153                 msgpriv->decoders = g_slist_prepend (msgpriv->decoders, coding);
154         }
155         soup_header_free_list (encodings);
156
157         soup_message_set_flags (msg, msgpriv->msg_flags | SOUP_MESSAGE_CONTENT_DECODED);
158 }
159
160 static void
161 request_queued (SoupSessionFeature *feature, SoupSession *session,
162                 SoupMessage *msg)
163 {
164         SoupContentDecoder *decoder = SOUP_CONTENT_DECODER (feature);
165
166         if (!soup_message_headers_get_one (msg->request_headers,
167                                            "Accept-Encoding")) {
168                 soup_message_headers_append (msg->request_headers,
169                                              "Accept-Encoding",
170                                              ACCEPT_ENCODING_HEADER);
171         }
172
173         g_signal_connect (msg, "got-headers",
174                           G_CALLBACK (soup_content_decoder_got_headers_cb),
175                           decoder);
176 }
177
178 static void
179 request_unqueued (SoupSessionFeature *feature, SoupSession *session,
180                   SoupMessage *msg)
181 {
182         g_signal_handlers_disconnect_by_func (msg, soup_content_decoder_got_headers_cb, feature);
183 }