Renamed from soup-error.h, with types and defines renamed accordingly.
[platform/upstream/libsoup.git] / libsoup / soup-session.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-session.c
4  *
5  * Copyright (C) 2000-2003, Ximian, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <unistd.h>
13 #include <string.h>
14 #include <stdlib.h>
15
16 #include "soup-session.h"
17 #include "soup-connection.h"
18 #include "soup-message-private.h"
19 #include "soup-message-queue.h"
20 #include "soup-private.h"
21
22 struct SoupSessionPrivate {
23         SoupMessageQueue *queue;
24         guint queue_idle_tag;
25
26 };
27
28 #define PARENT_TYPE G_TYPE_OBJECT
29 static GObjectClass *parent_class;
30
31 static void
32 init (GObject *object)
33 {
34         SoupSession *session = SOUP_SESSION (object);
35
36         session->priv = g_new0 (SoupSessionPrivate, 1);
37         session->priv->queue = soup_message_queue_new ();
38 }
39
40 static void
41 finalize (GObject *object)
42 {
43         SoupSession *session = SOUP_SESSION (object);
44         SoupMessageQueueIter iter;
45         SoupMessage *msg;
46
47         if (session->priv->queue_idle_tag)
48                 g_source_remove (session->priv->queue_idle_tag);
49
50         for (msg = soup_message_queue_first (session->priv->queue, &iter); msg;
51              msg = soup_message_queue_next (session->priv->queue, &iter)) {
52                 soup_message_queue_remove (session->priv->queue, &iter);
53                 soup_message_cancel (msg);
54         }
55         soup_message_queue_destroy (session->priv->queue);
56
57         g_free (session->priv);
58
59         G_OBJECT_CLASS (parent_class)->finalize (object);
60 }
61
62 static void
63 class_init (GObjectClass *object_class)
64 {
65         parent_class = g_type_class_ref (PARENT_TYPE);
66
67         /* virtual method override */
68         object_class->finalize = finalize;
69 }
70
71 SOUP_MAKE_TYPE (soup_session, SoupSession, class_init, init, PARENT_TYPE)
72
73
74 SoupSession *
75 soup_session_new (void)
76 {
77         return g_object_new (SOUP_TYPE_SESSION, NULL);
78 }
79
80
81 /* Default handlers */
82
83 static void
84 authorize_handler (SoupMessage *msg, gpointer user_data)
85 {
86         SoupSession *session = user_data;
87         SoupContext *ctx;
88
89         if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED)
90                 ctx = soup_get_proxy ();
91         else
92                 ctx = msg->priv->context;
93
94         if (soup_context_update_auth (ctx, msg))
95                 soup_session_requeue_message (session, msg);
96 }
97
98 static void
99 redirect_handler (SoupMessage *msg, gpointer user_data)
100 {
101         SoupSession *session = user_data;
102         const char *new_loc;
103         const SoupUri *old_uri;
104         SoupUri *new_uri;
105         SoupContext *new_ctx;
106
107         new_loc = soup_message_get_header (msg->response_headers, "Location");
108         if (!new_loc)
109                 return;
110         new_uri = soup_uri_new (new_loc);
111         if (!new_uri)
112                 goto INVALID_REDIRECT;
113
114         old_uri = soup_message_get_uri (msg);
115
116         /* Copy auth info from original URI. */
117         if (old_uri->user && !new_uri->user)
118                 soup_uri_set_auth (new_uri,
119                                    old_uri->user,
120                                    old_uri->passwd,
121                                    old_uri->authmech);
122
123         new_ctx = soup_context_from_uri (new_uri);
124         soup_uri_free (new_uri);
125         if (!new_ctx)
126                 goto INVALID_REDIRECT;
127
128         soup_message_set_context (msg, new_ctx);
129         g_object_unref (new_ctx);
130
131         soup_session_requeue_message (session, msg);
132         return;
133
134  INVALID_REDIRECT:
135         soup_message_set_status_full (msg,
136                                       SOUP_STATUS_MALFORMED,
137                                       "Invalid Redirect URL");
138 }
139
140 static void
141 request_finished (SoupMessage *req, gpointer user_data)
142 {
143         SoupSession *session = user_data;
144
145         soup_message_queue_remove_message (session->priv->queue, req);
146         req->priv->status = SOUP_MESSAGE_STATUS_FINISHED;
147 }
148
149 static void
150 final_finished (SoupMessage *req, gpointer session)
151 {
152         if (!SOUP_MESSAGE_IS_STARTING (req)) {
153                 g_signal_handlers_disconnect_by_func (req, request_finished, session);
154                 g_signal_handlers_disconnect_by_func (req, final_finished, session);
155                 g_object_unref (req);
156         }
157 }
158
159 static void
160 start_request (SoupConnection *conn, SoupMessage *req)
161 {
162         req->priv->status = SOUP_MESSAGE_STATUS_RUNNING;
163         soup_connection_send_request (conn, req);
164 }
165
166 static void
167 got_connection (SoupContext *ctx, guint status,
168                 SoupConnection *conn, gpointer user_data)
169 {
170         SoupMessage *req = user_data;
171
172         req->priv->connect_tag = NULL;
173         soup_message_set_connection (req, conn);
174
175         if (status != SOUP_STATUS_OK) {
176                 soup_message_set_status (req, status);
177                 soup_message_finished (req);
178                 return;
179         }
180
181         start_request (conn, req);
182 }
183
184 static gboolean
185 idle_run_queue (gpointer user_data)
186 {
187         SoupSession *session = user_data;
188         SoupMessageQueueIter iter;
189         SoupMessage *req;
190         SoupConnection *conn;
191
192         session->priv->queue_idle_tag = 0;
193
194         for (req = soup_message_queue_first (session->priv->queue, &iter); req;
195              req = soup_message_queue_next (session->priv->queue, &iter)) {
196
197                 if (req->priv->status != SOUP_MESSAGE_STATUS_QUEUED)
198                         continue;
199
200                 conn = soup_message_get_connection (req);
201                 if (conn && soup_connection_is_connected (conn)) {
202                         start_request (conn, req);
203                 } else {
204                         gpointer connect_tag;
205
206                         req->priv->status = SOUP_MESSAGE_STATUS_CONNECTING;
207                         connect_tag = 
208                                 soup_context_get_connection (
209                                         req->priv->context,
210                                         got_connection, req);
211
212                         if (connect_tag)
213                                 req->priv->connect_tag = connect_tag;
214                 }
215         }
216
217         return FALSE;
218 }
219
220 static void
221 queue_message (SoupSession *session, SoupMessage *req, gboolean requeue)
222 {
223         soup_message_prepare (req);
224
225         req->priv->status = SOUP_MESSAGE_STATUS_QUEUED;
226         if (!requeue)
227                 soup_message_queue_append (session->priv->queue, req);
228
229         if (!session->priv->queue_idle_tag) {
230                 session->priv->queue_idle_tag =
231                         g_idle_add (idle_run_queue, session);
232         }
233 }
234
235 /**
236  * soup_session_queue_message:
237  * @session: a #SoupSession
238  * @req: the message to queue
239  * @callback: a #SoupCallbackFn which will be called after the message
240  * completes or when an unrecoverable error occurs.
241  * @user_data: a pointer passed to @callback.
242  * 
243  * Queues the message @req for sending. All messages are processed
244  * while the glib main loop runs. If @req has been processed before,
245  * any resources related to the time it was last sent are freed.
246  *
247  * Upon message completion, the callback specified in @callback will
248  * be invoked. If after returning from this callback the message has
249  * not been requeued, @req will be unreffed.
250  */
251 void
252 soup_session_queue_message (SoupSession *session, SoupMessage *req,
253                             SoupCallbackFn callback, gpointer user_data)
254 {
255         g_return_if_fail (SOUP_IS_SESSION (session));
256         g_return_if_fail (SOUP_IS_MESSAGE (req));
257
258         g_signal_connect (req, "finished",
259                           G_CALLBACK (request_finished), session);
260         if (callback) {
261                 g_signal_connect (req, "finished",
262                                   G_CALLBACK (callback), user_data);
263         }
264         g_signal_connect_after (req, "finished",
265                                 G_CALLBACK (final_finished), session);
266
267         soup_message_add_status_code_handler  (req, SOUP_STATUS_UNAUTHORIZED,
268                                                SOUP_HANDLER_POST_BODY,
269                                                authorize_handler, session);
270         soup_message_add_status_code_handler  (req,
271                                                SOUP_STATUS_PROXY_UNAUTHORIZED,
272                                                SOUP_HANDLER_POST_BODY,
273                                                authorize_handler, session);
274
275         if (!(req->priv->msg_flags & SOUP_MESSAGE_NO_REDIRECT)) {
276                 soup_message_add_status_class_handler (
277                         req, SOUP_STATUS_CLASS_REDIRECT,
278                         SOUP_HANDLER_POST_BODY,
279                         redirect_handler, session);
280         }
281
282         queue_message (session, req, FALSE);
283 }
284
285 /**
286  * soup_session_requeue_message:
287  * @session: a #SoupSession
288  * @req: the message to requeue
289  *
290  * This causes @req to be placed back on the queue to be attempted
291  * again.
292  **/
293 void
294 soup_session_requeue_message (SoupSession *session, SoupMessage *req)
295 {
296         g_return_if_fail (SOUP_IS_SESSION (session));
297         g_return_if_fail (SOUP_IS_MESSAGE (req));
298
299         queue_message (session, req, TRUE);
300 }
301
302
303 /**
304  * soup_session_send_message:
305  * @session: a #SoupSession
306  * @req: the message to send
307  * 
308  * Synchronously send @req. This call will not return until the
309  * transfer is finished successfully or there is an unrecoverable
310  * error.
311  *
312  * @req is not freed upon return.
313  *
314  * Return value: the HTTP status code of the response
315  */
316 guint
317 soup_session_send_message (SoupSession *session, SoupMessage *req)
318 {
319         g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
320         g_return_val_if_fail (SOUP_IS_MESSAGE (req), SOUP_STATUS_MALFORMED);
321
322         /* Balance out the unref that final_finished will do */
323         g_object_ref (req);
324
325         soup_session_queue_message (session, req, NULL, NULL);
326
327         while (1) {
328                 g_main_iteration (TRUE);
329
330                 if (req->priv->status == SOUP_MESSAGE_STATUS_FINISHED ||
331                     SOUP_STATUS_IS_TRANSPORT (req->status_code))
332                         break;
333         }
334
335         return req->status_code;
336 }