soup-request-file: plug leak
[platform/upstream/libsoup.git] / libsoup / soup-request-http.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-request-http.c: http: URI request object
4  *
5  * Copyright (C) 2009, 2010 Red Hat, Inc.
6  * Copyright (C) 2010 Igalia, S.L.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <glib/gi18n.h>
29
30 #define LIBSOUP_USE_UNSTABLE_REQUEST_API
31
32 #include "soup-request-http.h"
33 #include "soup-cache.h"
34 #include "soup-cache-private.h"
35 #include "soup-content-sniffer.h"
36 #include "soup-http-input-stream.h"
37 #include "soup-message.h"
38 #include "soup-session.h"
39 #include "soup-uri.h"
40
41 G_DEFINE_TYPE (SoupRequestHTTP, soup_request_http, SOUP_TYPE_REQUEST)
42
43 struct _SoupRequestHTTPPrivate {
44         SoupMessage *msg;
45 };
46
47 static void
48 soup_request_http_init (SoupRequestHTTP *http)
49 {
50         http->priv = G_TYPE_INSTANCE_GET_PRIVATE (http, SOUP_TYPE_REQUEST_HTTP, SoupRequestHTTPPrivate);
51 }
52
53 static gboolean
54 soup_request_http_check_uri (SoupRequest  *request,
55                              SoupURI      *uri,
56                              GError      **error)
57 {
58         SoupRequestHTTP *http = SOUP_REQUEST_HTTP (request);
59
60         if (!SOUP_URI_VALID_FOR_HTTP (uri))
61                 return FALSE;
62
63         http->priv->msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
64         return TRUE;
65 }
66
67 static void
68 soup_request_http_finalize (GObject *object)
69 {
70         SoupRequestHTTP *http = SOUP_REQUEST_HTTP (object);
71
72         if (http->priv->msg)
73                 g_object_unref (http->priv->msg);
74
75         G_OBJECT_CLASS (soup_request_http_parent_class)->finalize (object);
76 }
77
78 static GInputStream *
79 soup_request_http_send (SoupRequest          *request,
80                         GCancellable         *cancellable,
81                         GError              **error)
82 {
83         SoupHTTPInputStream *httpstream;
84         SoupRequestHTTP *http = SOUP_REQUEST_HTTP (request);
85
86         httpstream = soup_http_input_stream_new (soup_request_get_session (request), http->priv->msg);
87         if (!soup_http_input_stream_send (httpstream, cancellable, error)) {
88                 g_object_unref (httpstream);
89                 return NULL;
90         }
91         return (GInputStream *)httpstream;
92 }
93
94
95 static void
96 sent_async (GObject *source, GAsyncResult *result, gpointer user_data)
97 {
98         SoupHTTPInputStream *httpstream = SOUP_HTTP_INPUT_STREAM (source);
99         GSimpleAsyncResult *simple = user_data;
100         GError *error = NULL;
101
102         if (soup_http_input_stream_send_finish (httpstream, result, &error)) {
103                 g_simple_async_result_set_op_res_gpointer (simple, httpstream, g_object_unref);
104         } else {
105                 g_simple_async_result_set_from_error (simple, error);
106                 g_error_free (error);
107                 g_object_unref (httpstream);
108         }
109         g_simple_async_result_complete (simple);
110         g_object_unref (simple);
111 }
112
113
114 typedef struct {
115         SoupRequestHTTP *req;
116         SoupMessage *original;
117         GCancellable *cancellable;
118         GAsyncReadyCallback callback;
119         gpointer user_data;
120 } ConditionalHelper;
121
122 static void
123 conditional_get_ready_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
124 {
125         ConditionalHelper *helper = (ConditionalHelper *)user_data;
126         GSimpleAsyncResult *simple;
127         SoupHTTPInputStream *httpstream;
128
129         simple = g_simple_async_result_new (G_OBJECT (helper->req),
130                                             helper->callback, helper->user_data,
131                                             conditional_get_ready_cb);
132
133         if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) {
134                 SoupCache *cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE);
135
136                 httpstream = (SoupHTTPInputStream *)soup_cache_send_response (cache, msg);
137                 if (httpstream) {
138                         g_simple_async_result_set_op_res_gpointer (simple, httpstream, g_object_unref);
139
140                         soup_message_got_headers (helper->original);
141
142                         if (soup_session_get_feature_for_message (session, SOUP_TYPE_CONTENT_SNIFFER, helper->original)) {
143                                 const char *content_type = soup_message_headers_get_content_type (msg->response_headers, NULL);
144                                 soup_message_content_sniffed (helper->original, content_type, NULL);
145                         }
146
147                         g_simple_async_result_complete (simple);
148
149                         soup_message_finished (helper->original);
150
151                         g_object_unref (simple);
152                 } else {
153                         /* Ask again for the resource, somehow the cache cannot locate it */
154                         httpstream = soup_http_input_stream_new (session, helper->original);
155                         soup_http_input_stream_send_async (httpstream, G_PRIORITY_DEFAULT,
156                                                            helper->cancellable, sent_async, simple);
157                 }
158         } else {
159                 /* It is in the cache but it was modified remotely */
160                 httpstream = soup_http_input_stream_new (session, helper->original);
161                 soup_http_input_stream_send_async (httpstream, G_PRIORITY_DEFAULT,
162                                                    helper->cancellable, sent_async, simple);
163         }
164
165         g_object_unref (helper->req);
166         g_object_unref (helper->original);
167         g_slice_free (ConditionalHelper, helper);
168 }
169
170 typedef struct {
171         SoupRequestHTTP *http;
172         GAsyncReadyCallback callback;
173         gpointer user_data;
174         SoupHTTPInputStream *httpstream;
175 } SendAsyncHelper;
176
177 static void soup_request_http_send_async (SoupRequest          *request,
178                                           GCancellable         *cancellable,
179                                           GAsyncReadyCallback callback,
180                                           gpointer user_data);
181
182 static gboolean
183 send_async_cb (gpointer data)
184 {
185         GSimpleAsyncResult *simple;
186         SoupSession *session;
187         SendAsyncHelper *helper = (SendAsyncHelper *)data;
188
189         session = soup_request_get_session (SOUP_REQUEST (helper->http));
190         simple = g_simple_async_result_new (G_OBJECT (helper->http),
191                                             helper->callback, helper->user_data,
192                                             soup_request_http_send_async);
193
194         g_simple_async_result_set_op_res_gpointer (simple, helper->httpstream, g_object_unref);
195
196         /* Update message status */
197         soup_message_set_status (helper->http->priv->msg, SOUP_STATUS_OK);
198
199         /* Issue signals  */
200         soup_message_got_headers (helper->http->priv->msg);
201
202         if (soup_session_get_feature_for_message (session, SOUP_TYPE_CONTENT_SNIFFER, helper->http->priv->msg)) {
203                 const char *content_type = soup_message_headers_get_content_type (helper->http->priv->msg->response_headers, NULL);
204                 soup_message_content_sniffed (helper->http->priv->msg, content_type, NULL);
205         }
206
207         g_simple_async_result_complete (simple);
208
209         soup_message_finished (helper->http->priv->msg);
210
211         g_object_unref (simple);
212
213         g_object_unref (helper->http);
214         g_slice_free (SendAsyncHelper, helper);
215
216         return FALSE;
217 }
218
219 static void
220 soup_request_http_send_async (SoupRequest          *request,
221                               GCancellable         *cancellable,
222                               GAsyncReadyCallback   callback,
223                               gpointer              user_data)
224 {
225         SoupRequestHTTP *http = SOUP_REQUEST_HTTP (request);
226         SoupHTTPInputStream *httpstream;
227         GSimpleAsyncResult *simple;
228         SoupSession *session;
229         SoupCache *cache;
230
231         session = soup_request_get_session (request);
232         cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE);
233
234         if (cache) {
235                 SoupCacheResponse response;
236
237                 response = soup_cache_has_response (cache, http->priv->msg);
238                 if (response == SOUP_CACHE_RESPONSE_FRESH) {
239                         SoupHTTPInputStream *httpstream;
240
241                         httpstream = (SoupHTTPInputStream *)
242                                 soup_cache_send_response (cache, http->priv->msg);
243
244                         /* Cached resource file could have been deleted outside
245                          */
246                         if (httpstream) {
247                                 /* Do return the stream asynchronously as in
248                                  * the other cases. It's not enough to use
249                                  * g_simple_async_result_complete_in_idle as
250                                  * the signals must be also emitted
251                                  * asynchronously
252                                  */
253                                 SendAsyncHelper *helper = g_slice_new (SendAsyncHelper);
254                                 helper->http = g_object_ref (http);
255                                 helper->callback = callback;
256                                 helper->user_data = user_data;
257                                 helper->httpstream = httpstream;
258                                 g_timeout_add (0, send_async_cb, helper);
259                                 return;
260                         }
261                 } else if (response == SOUP_CACHE_RESPONSE_NEEDS_VALIDATION) {
262                         SoupMessage *conditional_msg;
263                         ConditionalHelper *helper;
264
265                         conditional_msg = soup_cache_generate_conditional_request (cache, http->priv->msg);
266
267                         helper = g_slice_new0 (ConditionalHelper);
268                         helper->req = g_object_ref (http);
269                         helper->original = g_object_ref (http->priv->msg);
270                         helper->cancellable = cancellable;
271                         helper->callback = callback;
272                         helper->user_data = user_data;
273                         soup_session_queue_message (session, conditional_msg,
274                                                     conditional_get_ready_cb,
275                                                     helper);
276                         return;
277                 }
278         }
279
280         simple = g_simple_async_result_new (G_OBJECT (http),
281                                             callback, user_data,
282                                             soup_request_http_send_async);
283         httpstream = soup_http_input_stream_new (soup_request_get_session (request),
284                                                         http->priv->msg);
285         soup_http_input_stream_send_async (httpstream, G_PRIORITY_DEFAULT,
286                                                   cancellable, sent_async, simple);
287 }
288
289 static GInputStream *
290 soup_request_http_send_finish (SoupRequest          *request,
291                                GAsyncResult         *result,
292                                GError              **error)
293 {
294         GSimpleAsyncResult *simple;
295
296         g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (request), soup_request_http_send_async) || g_simple_async_result_is_valid (result, G_OBJECT (request), conditional_get_ready_cb), NULL);
297
298         simple = G_SIMPLE_ASYNC_RESULT (result);
299         if (g_simple_async_result_propagate_error (simple, error))
300                 return NULL;
301         return g_object_ref (g_simple_async_result_get_op_res_gpointer (simple));
302 }
303
304 static goffset
305 soup_request_http_get_content_length (SoupRequest *request)
306 {
307         SoupRequestHTTP *http = SOUP_REQUEST_HTTP (request);
308
309         return soup_message_headers_get_content_length (http->priv->msg->response_headers);
310 }
311
312 static const char *
313 soup_request_http_get_content_type (SoupRequest *request)
314 {
315         SoupRequestHTTP *http = SOUP_REQUEST_HTTP (request);
316
317         return soup_message_headers_get_content_type (http->priv->msg->response_headers, NULL);
318 }
319
320 static const char *http_schemes[] = { "http", "https", NULL };
321
322 static void
323 soup_request_http_class_init (SoupRequestHTTPClass *request_http_class)
324 {
325         GObjectClass *object_class = G_OBJECT_CLASS (request_http_class);
326         SoupRequestClass *request_class =
327                 SOUP_REQUEST_CLASS (request_http_class);
328
329         g_type_class_add_private (request_http_class, sizeof (SoupRequestHTTPPrivate));
330
331         request_class->schemes = http_schemes;
332
333         object_class->finalize = soup_request_http_finalize;
334
335         request_class->check_uri = soup_request_http_check_uri;
336         request_class->send = soup_request_http_send;
337         request_class->send_async = soup_request_http_send_async;
338         request_class->send_finish = soup_request_http_send_finish;
339         request_class->get_content_length = soup_request_http_get_content_length;
340         request_class->get_content_type = soup_request_http_get_content_type;
341 }
342
343 /**
344  * soup_request_http_get_message:
345  * @http: a #SoupRequestHTTP object
346  *
347  * Gets a new reference to the #SoupMessage associated to this SoupRequest
348  *
349  * Returns: a new reference to the #SoupMessage
350  **/
351 SoupMessage *
352 soup_request_http_get_message (SoupRequestHTTP *http)
353 {
354         g_return_val_if_fail (SOUP_IS_REQUEST_HTTP (http), NULL);
355
356         return g_object_ref (http->priv->msg);
357 }