Fix for proxies that close the connection when 407'ing a CONNECT
[platform/upstream/libsoup.git] / libsoup / soup-session-sync.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-session-sync.c
4  *
5  * Copyright (C) 2000-2003, Ximian, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #define LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY
13
14 #include "soup-address.h"
15 #include "soup-session-sync.h"
16 #include "soup-session-private.h"
17 #include "soup-address.h"
18 #include "soup-message-private.h"
19 #include "soup-misc.h"
20 #include "soup-password-manager.h"
21 #include "soup-proxy-uri-resolver.h"
22 #include "soup-uri.h"
23
24 /**
25  * SECTION:soup-session-sync
26  * @short_description: Soup session for blocking I/O in multithreaded
27  * programs.
28  *
29  * #SoupSessionSync is an implementation of #SoupSession that uses
30  * synchronous I/O, intended for use in multi-threaded programs.
31  *
32  * You can use #SoupSessionSync from multiple threads concurrently.
33  * Eg, you can send a #SoupMessage in one thread, and then while
34  * waiting for the response, send another #SoupMessage from another
35  * thread. You can also send a message from one thread and then call
36  * soup_session_cancel_message() on it from any other thread (although
37  * you need to be careful to avoid race conditions, where the message
38  * finishes and is then unreffed by the sending thread just before you
39  * cancel it).
40  *
41  * However, the majority of other types and methods in libsoup are not
42  * MT-safe. In particular, you <emphasis>cannot</emphasis> modify or
43  * examine a #SoupMessage while it is being transmitted by
44  * #SoupSessionSync in another thread. Once a message has been handed
45  * off to #SoupSessionSync, it can only be manipulated from its signal
46  * handler callbacks, until I/O is complete.
47  **/
48
49 typedef struct {
50         GMutex *lock;
51         GCond *cond;
52 } SoupSessionSyncPrivate;
53 #define SOUP_SESSION_SYNC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION_SYNC, SoupSessionSyncPrivate))
54
55 static void  queue_message  (SoupSession *session, SoupMessage *msg,
56                              SoupSessionCallback callback, gpointer user_data);
57 static guint send_message   (SoupSession *session, SoupMessage *msg);
58 static void  cancel_message (SoupSession *session, SoupMessage *msg,
59                              guint status_code);
60 static void  auth_required  (SoupSession *session, SoupMessage *msg,
61                              SoupAuth *auth, gboolean retrying);
62
63 G_DEFINE_TYPE (SoupSessionSync, soup_session_sync, SOUP_TYPE_SESSION)
64
65 static void
66 soup_session_sync_init (SoupSessionSync *ss)
67 {
68         SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (ss);
69
70         priv->lock = g_mutex_new ();
71         priv->cond = g_cond_new ();
72 }
73
74 static void
75 finalize (GObject *object)
76 {
77         SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (object);
78
79         g_mutex_free (priv->lock);
80         g_cond_free (priv->cond);
81
82         G_OBJECT_CLASS (soup_session_sync_parent_class)->finalize (object);
83 }
84
85 static void
86 soup_session_sync_class_init (SoupSessionSyncClass *session_sync_class)
87 {
88         GObjectClass *object_class = G_OBJECT_CLASS (session_sync_class);
89         SoupSessionClass *session_class = SOUP_SESSION_CLASS (session_sync_class);
90
91         g_type_class_add_private (session_sync_class, sizeof (SoupSessionSyncPrivate));
92
93         /* virtual method override */
94         session_class->queue_message = queue_message;
95         session_class->send_message = send_message;
96         session_class->cancel_message = cancel_message;
97         session_class->auth_required = auth_required;
98         object_class->finalize = finalize;
99 }
100
101
102 /**
103  * soup_session_sync_new:
104  *
105  * Creates an synchronous #SoupSession with the default options.
106  *
107  * Return value: the new session.
108  **/
109 SoupSession *
110 soup_session_sync_new (void)
111 {
112         return g_object_new (SOUP_TYPE_SESSION_SYNC, NULL);
113 }
114
115 /**
116  * soup_session_sync_new_with_options:
117  * @optname1: name of first property to set
118  * @...: value of @optname1, followed by additional property/value pairs
119  *
120  * Creates an synchronous #SoupSession with the specified options.
121  *
122  * Return value: the new session.
123  **/
124 SoupSession *
125 soup_session_sync_new_with_options (const char *optname1, ...)
126 {
127         SoupSession *session;
128         va_list ap;
129
130         va_start (ap, optname1);
131         session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION_SYNC,
132                                                       optname1, ap);
133         va_end (ap);
134
135         return session;
136 }
137
138 static guint
139 tunnel_connect (SoupSession *session, SoupConnection *conn,
140                 SoupAddress *tunnel_addr)
141 {
142         SoupMessageQueueItem *item;
143         guint status;
144
145         g_object_ref (conn);
146
147         g_signal_emit_by_name (session, "tunneling", conn);
148         item = soup_session_make_connect_message (session, tunnel_addr);
149         do
150                 soup_session_send_queue_item (session, item, conn);
151         while (SOUP_MESSAGE_IS_STARTING (item->msg));
152
153         status = item->msg->status_code;
154         soup_message_queue_item_unref (item);
155
156         if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
157                 if (!soup_connection_start_ssl (conn))
158                         status = SOUP_STATUS_SSL_FAILED;
159         }
160
161         if (!SOUP_STATUS_IS_SUCCESSFUL (status))
162                 soup_session_connection_failed (session, conn, status);
163
164         g_object_unref (conn);
165         return status;
166 }
167
168 static SoupConnection *
169 wait_for_connection (SoupMessageQueueItem *item)
170 {
171         SoupSession *session = item->session;
172         SoupMessage *msg = item->msg;
173         SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session);
174         gboolean try_pruning = FALSE;
175         SoupProxyURIResolver *proxy_resolver;
176         SoupAddress *tunnel_addr;
177         SoupConnection *conn;
178         guint status;
179
180         proxy_resolver = (SoupProxyURIResolver *)soup_session_get_feature_for_message (session, SOUP_TYPE_PROXY_URI_RESOLVER, msg);
181         if (proxy_resolver && !item->resolved_proxy_addr) {
182                 status = soup_proxy_uri_resolver_get_proxy_uri_sync (
183                         proxy_resolver, soup_message_get_uri (msg),
184                         item->cancellable, &item->proxy_uri);
185                 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
186                         if (status != SOUP_STATUS_CANCELLED)
187                                 soup_session_cancel_message (session, msg, status);
188                         return NULL;
189                 }
190
191                 if (item->proxy_uri) {
192                         item->proxy_addr = soup_address_new (
193                                 item->proxy_uri->host, item->proxy_uri->port);
194                         status = soup_address_resolve_sync (item->proxy_addr,
195                                                             item->cancellable);
196                         if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
197                                 if (status != SOUP_STATUS_CANCELLED)
198                                         soup_session_cancel_message (session, msg, status);
199                                 return NULL;
200                         }
201                 }
202
203                 item->resolved_proxy_addr = TRUE;
204         }
205
206         g_mutex_lock (priv->lock);
207
208         soup_session_cleanup_connections (session, FALSE);
209
210  try_again:
211         conn = soup_session_get_connection (session, item, &try_pruning);
212         if (conn) {
213                 if (soup_connection_get_state (conn) == SOUP_CONNECTION_NEW) {
214                         status = soup_connection_connect_sync (conn);
215
216                         if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
217                                 soup_session_connection_failed (session, conn, status);
218                                 conn = NULL;
219                         } else if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED) {
220                                 /* Message was cancelled while we were
221                                  * connecting.
222                                  */
223                                 soup_connection_disconnect (conn);
224                                 conn = NULL;
225                         } else if ((tunnel_addr = soup_connection_get_tunnel_addr (conn))) {
226                                 status = tunnel_connect (session, conn, tunnel_addr);
227                                 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
228                                         conn = NULL;
229                                         goto try_again;
230                                 }
231                         }
232                 }
233
234                 g_mutex_unlock (priv->lock);
235                 return conn;
236         }
237
238         if (try_pruning) {
239                 try_pruning = FALSE;
240                 if (soup_session_cleanup_connections (session, TRUE))
241                         goto try_again;
242         }
243
244         /* Wait... */
245         g_cond_wait (priv->cond, priv->lock);
246
247         /* See if something bad happened */
248         if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED) {
249                 g_mutex_unlock (priv->lock);
250                 return NULL;
251         }
252
253         goto try_again;
254 }
255
256 static void
257 process_queue_item (SoupMessageQueueItem *item)
258 {
259         SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (item->session);
260         SoupConnection *conn;
261
262         do {
263                 conn = wait_for_connection (item);
264                 if (!conn)
265                         break;
266
267                 soup_session_send_queue_item (item->session, item, conn);
268                 g_cond_broadcast (priv->cond);
269         } while (soup_message_get_io_status (item->msg) !=
270                  SOUP_MESSAGE_IO_STATUS_FINISHED);
271 }
272
273 static gboolean
274 queue_message_callback (gpointer data)
275 {
276         SoupMessageQueueItem *item = data;
277
278         item->callback (item->session, item->msg, item->callback_data);
279         g_object_unref (item->session);
280         soup_message_queue_item_unref (item);
281         return FALSE;
282 }
283
284 static gpointer
285 queue_message_thread (gpointer data)
286 {
287         SoupMessageQueueItem *item = data;
288
289         process_queue_item (item);
290         if (item->callback) {
291                 soup_add_completion (soup_session_get_async_context (item->session),
292                                      queue_message_callback, item);
293         } else {
294                 g_object_unref (item->session);
295                 soup_message_queue_item_unref (item);
296         }
297
298         return NULL;
299 }
300
301 static void
302 queue_message (SoupSession *session, SoupMessage *msg,
303                SoupSessionCallback callback, gpointer user_data)
304 {
305         SoupMessageQueueItem *item;
306
307         SOUP_SESSION_CLASS (soup_session_sync_parent_class)->
308                 queue_message (g_object_ref (session), msg, callback, user_data);
309
310         item = soup_message_queue_lookup (soup_session_get_queue (session), msg);
311         g_return_if_fail (item != NULL);
312
313         g_thread_create (queue_message_thread, item, FALSE, NULL);
314 }
315
316 static guint
317 send_message (SoupSession *session, SoupMessage *msg)
318 {
319         SoupMessageQueueItem *item;
320         guint status;
321
322         SOUP_SESSION_CLASS (soup_session_sync_parent_class)->queue_message (session, msg, NULL, NULL);
323
324         item = soup_message_queue_lookup (soup_session_get_queue (session), msg);
325         g_return_val_if_fail (item != NULL, SOUP_STATUS_MALFORMED);
326
327         process_queue_item (item);
328         status = msg->status_code;
329         soup_message_queue_item_unref (item);
330         return status;
331 }
332
333 static void
334 cancel_message (SoupSession *session, SoupMessage *msg, guint status_code)
335 {
336         SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session);
337
338         SOUP_SESSION_CLASS (soup_session_sync_parent_class)->cancel_message (session, msg, status_code);
339         g_cond_broadcast (priv->cond);
340 }
341
342 static void
343 auth_required (SoupSession *session, SoupMessage *msg,
344                SoupAuth *auth, gboolean retrying)
345 {
346         SoupSessionFeature *password_manager;
347
348         password_manager = soup_session_get_feature_for_message (
349                 session, SOUP_TYPE_PASSWORD_MANAGER, msg);
350         if (password_manager) {
351                 soup_password_manager_get_passwords_sync (
352                         SOUP_PASSWORD_MANAGER (password_manager),
353                         msg, auth, NULL); /* FIXME cancellable */
354         }
355
356         SOUP_SESSION_CLASS (soup_session_sync_parent_class)->
357                 auth_required (session, msg, auth, retrying);
358 }