if we receive an HTTP/1.0 response to an HTTP/1.1 request, downgrade the
[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-auth.h"
17 #include "soup-session.h"
18 #include "soup-connection.h"
19 #include "soup-connection-ntlm.h"
20 #include "soup-marshal.h"
21 #include "soup-message-queue.h"
22 #include "soup-ssl.h"
23 #include "soup-uri.h"
24
25 typedef struct {
26         SoupUri    *root_uri;
27         guint       error;
28
29         GSList     *connections;      /* CONTAINS: SoupConnection */
30         guint       num_conns;
31
32         GHashTable *auth_realms;      /* path -> scheme:realm */
33         GHashTable *auths;            /* scheme:realm -> SoupAuth */
34 } SoupSessionHost;
35
36 struct SoupSessionPrivate {
37         SoupUri *proxy_uri;
38         guint max_conns, max_conns_per_host;
39         gboolean use_ntlm;
40
41         char *ssl_ca_file;
42         gpointer ssl_creds;
43
44         SoupMessageQueue *queue;
45
46         GHashTable *hosts; /* SoupUri -> SoupSessionHost */
47         GHashTable *conns; /* SoupConnection -> SoupSessionHost */
48         guint num_conns;
49
50         SoupSessionHost *proxy_host;
51 };
52
53 static guint    host_uri_hash  (gconstpointer key);
54 static gboolean host_uri_equal (gconstpointer v1, gconstpointer v2);
55 static void     free_host      (SoupSessionHost *host, SoupSession *session);
56
57 static gboolean run_queue (SoupSession *session, gboolean try_pruning);
58
59 #define SOUP_SESSION_MAX_CONNS_DEFAULT 10
60 #define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 4
61
62 #define PARENT_TYPE G_TYPE_OBJECT
63 static GObjectClass *parent_class;
64
65 enum {
66         AUTHENTICATE,
67         REAUTHENTICATE,
68         LAST_SIGNAL
69 };
70
71 static guint signals[LAST_SIGNAL] = { 0 };
72
73 enum {
74   PROP_0,
75
76   PROP_PROXY_URI,
77   PROP_MAX_CONNS,
78   PROP_MAX_CONNS_PER_HOST,
79   PROP_USE_NTLM,
80   PROP_SSL_CA_FILE,
81
82   LAST_PROP
83 };
84
85 static void set_property (GObject *object, guint prop_id,
86                           const GValue *value, GParamSpec *pspec);
87 static void get_property (GObject *object, guint prop_id,
88                           GValue *value, GParamSpec *pspec);
89
90 static void
91 init (GObject *object)
92 {
93         SoupSession *session = SOUP_SESSION (object);
94
95         session->priv = g_new0 (SoupSessionPrivate, 1);
96         session->priv->queue = soup_message_queue_new ();
97         session->priv->hosts = g_hash_table_new (host_uri_hash,
98                                                  host_uri_equal);
99         session->priv->conns = g_hash_table_new (NULL, NULL);
100
101         session->priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT;
102         session->priv->max_conns_per_host = SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT;
103 }
104
105 static gboolean
106 foreach_free_host (gpointer key, gpointer host, gpointer session)
107 {
108         free_host (host, session);
109         return TRUE;
110 }
111
112 static void
113 cleanup_hosts (SoupSession *session)
114 {
115         g_hash_table_foreach_remove (session->priv->hosts,
116                                      foreach_free_host, session);
117 }
118
119 static void
120 dispose (GObject *object)
121 {
122         SoupSession *session = SOUP_SESSION (object);
123
124         soup_session_abort (session);
125         cleanup_hosts (session);
126
127         G_OBJECT_CLASS (parent_class)->dispose (object);
128 }
129
130 static void
131 finalize (GObject *object)
132 {
133         SoupSession *session = SOUP_SESSION (object);
134
135         soup_message_queue_destroy (session->priv->queue);
136         g_hash_table_destroy (session->priv->hosts);
137         g_hash_table_destroy (session->priv->conns);
138         g_free (session->priv);
139
140         G_OBJECT_CLASS (parent_class)->finalize (object);
141 }
142
143 static void
144 class_init (GObjectClass *object_class)
145 {
146         parent_class = g_type_class_ref (PARENT_TYPE);
147
148         /* virtual method override */
149         object_class->dispose = dispose;
150         object_class->finalize = finalize;
151         object_class->set_property = set_property;
152         object_class->get_property = get_property;
153
154         /* signals */
155         signals[AUTHENTICATE] =
156                 g_signal_new ("authenticate",
157                               G_OBJECT_CLASS_TYPE (object_class),
158                               G_SIGNAL_RUN_FIRST,
159                               G_STRUCT_OFFSET (SoupSessionClass, authenticate),
160                               NULL, NULL,
161                               soup_marshal_NONE__OBJECT_STRING_STRING_POINTER_POINTER,
162                               G_TYPE_NONE, 5,
163                               SOUP_TYPE_MESSAGE,
164                               G_TYPE_STRING,
165                               G_TYPE_STRING,
166                               G_TYPE_POINTER,
167                               G_TYPE_POINTER);
168         signals[REAUTHENTICATE] =
169                 g_signal_new ("reauthenticate",
170                               G_OBJECT_CLASS_TYPE (object_class),
171                               G_SIGNAL_RUN_FIRST,
172                               G_STRUCT_OFFSET (SoupSessionClass, reauthenticate),
173                               NULL, NULL,
174                               soup_marshal_NONE__OBJECT_STRING_STRING_POINTER_POINTER,
175                               G_TYPE_NONE, 5,
176                               SOUP_TYPE_MESSAGE,
177                               G_TYPE_STRING,
178                               G_TYPE_STRING,
179                               G_TYPE_POINTER,
180                               G_TYPE_POINTER);
181
182         /* properties */
183         g_object_class_install_property (
184                 object_class, PROP_PROXY_URI,
185                 g_param_spec_pointer (SOUP_SESSION_PROXY_URI,
186                                       "Proxy URI",
187                                       "The HTTP Proxy to use for this session",
188                                       G_PARAM_READWRITE));
189         g_object_class_install_property (
190                 object_class, PROP_MAX_CONNS,
191                 g_param_spec_int (SOUP_SESSION_MAX_CONNS,
192                                   "Max Connection Count",
193                                   "The maximum number of connections that the session can open at once",
194                                   1,
195                                   G_MAXINT,
196                                   10,
197                                   G_PARAM_READWRITE));
198         g_object_class_install_property (
199                 object_class, PROP_MAX_CONNS_PER_HOST,
200                 g_param_spec_int (SOUP_SESSION_MAX_CONNS_PER_HOST,
201                                   "Max Per-Host Connection Count",
202                                   "The maximum number of connections that the session can open at once to a given host",
203                                   1,
204                                   G_MAXINT,
205                                   4,
206                                   G_PARAM_READWRITE));
207         g_object_class_install_property (
208                 object_class, PROP_USE_NTLM,
209                 g_param_spec_boolean (SOUP_SESSION_USE_NTLM,
210                                       "Use NTLM",
211                                       "Whether or not to use NTLM authentication",
212                                       FALSE,
213                                       G_PARAM_READWRITE));
214         g_object_class_install_property (
215                 object_class, PROP_SSL_CA_FILE,
216                 g_param_spec_string (SOUP_SESSION_SSL_CA_FILE,
217                                       "SSL CA file",
218                                       "File containing SSL CA certificates",
219                                       NULL,
220                                       G_PARAM_READWRITE));
221 }
222
223 SOUP_MAKE_TYPE (soup_session, SoupSession, class_init, init, PARENT_TYPE)
224
225 SoupSession *
226 soup_session_new (void)
227 {
228         return g_object_new (SOUP_TYPE_SESSION, NULL);
229 }
230
231 SoupSession *
232 soup_session_new_with_options (const char *optname1, ...)
233 {
234         SoupSession *session;
235         va_list ap;
236
237         va_start (ap, optname1);
238         session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION, optname1, ap);
239         va_end (ap);
240
241         return session;
242 }
243
244 static void
245 set_property (GObject *object, guint prop_id,
246               const GValue *value, GParamSpec *pspec)
247 {
248         SoupSession *session = SOUP_SESSION (object);
249         gpointer pval;
250
251         switch (prop_id) {
252         case PROP_PROXY_URI:
253                 if (session->priv->proxy_uri)
254                         soup_uri_free (session->priv->proxy_uri);
255                 pval = g_value_get_pointer (value);
256                 session->priv->proxy_uri = pval ? soup_uri_copy (pval) : NULL;
257
258                 soup_session_abort (session);
259                 cleanup_hosts (session);
260                 break;
261         case PROP_MAX_CONNS:
262                 session->priv->max_conns = g_value_get_int (value);
263                 break;
264         case PROP_MAX_CONNS_PER_HOST:
265                 session->priv->max_conns_per_host = g_value_get_int (value);
266                 break;
267         case PROP_USE_NTLM:
268                 session->priv->use_ntlm = g_value_get_boolean (value);
269                 break;
270         case PROP_SSL_CA_FILE:
271                 g_free (session->priv->ssl_ca_file);
272                 session->priv->ssl_ca_file = g_strdup (g_value_get_string (value));
273                 break;
274         default:
275                 break;
276         }
277 }
278
279 static void
280 get_property (GObject *object, guint prop_id,
281               GValue *value, GParamSpec *pspec)
282 {
283         SoupSession *session = SOUP_SESSION (object);
284
285         switch (prop_id) {
286         case PROP_PROXY_URI:
287                 g_value_set_pointer (value, session->priv->proxy_uri ?
288                                      soup_uri_copy (session->priv->proxy_uri) :
289                                      NULL);
290                 break;
291         case PROP_MAX_CONNS:
292                 g_value_set_int (value, session->priv->max_conns);
293                 break;
294         case PROP_MAX_CONNS_PER_HOST:
295                 g_value_set_int (value, session->priv->max_conns_per_host);
296                 break;
297         case PROP_USE_NTLM:
298                 g_value_set_boolean (value, session->priv->use_ntlm);
299                 break;
300         case PROP_SSL_CA_FILE:
301                 g_value_set_string (value, session->priv->ssl_ca_file);
302                 break;
303         default:
304                 break;
305         }
306 }
307
308
309 /* Hosts */
310 static guint
311 host_uri_hash (gconstpointer key)
312 {
313         const SoupUri *uri = key;
314
315         return (uri->protocol << 16) + uri->port + g_str_hash (uri->host);
316 }
317
318 static gboolean
319 host_uri_equal (gconstpointer v1, gconstpointer v2)
320 {
321         const SoupUri *one = v1;
322         const SoupUri *two = v2;
323
324         if (one->protocol != two->protocol)
325                 return FALSE;
326         if (one->port != two->port)
327                 return FALSE;
328
329         return strcmp (one->host, two->host) == 0;
330 }
331
332 static SoupSessionHost *
333 get_host_for_message (SoupSession *session, SoupMessage *msg)
334 {
335         SoupSessionHost *host;
336         const SoupUri *source = soup_message_get_uri (msg);
337
338         host = g_hash_table_lookup (session->priv->hosts, source);
339         if (host)
340                 return host;
341
342         host = g_new0 (SoupSessionHost, 1);
343         host->root_uri = soup_uri_copy_root (source);
344
345         g_hash_table_insert (session->priv->hosts, host->root_uri, host);
346
347         if (host->root_uri->protocol == SOUP_PROTOCOL_HTTPS &&
348             !session->priv->ssl_creds) {
349                 session->priv->ssl_creds =
350                         soup_ssl_get_client_credentials (session->priv->ssl_ca_file);
351         }
352
353         return host;
354 }
355
356 static void
357 free_realm (gpointer path, gpointer scheme_realm, gpointer data)
358 {
359         g_free (path);
360         g_free (scheme_realm);
361 }
362
363 static void
364 free_auth (gpointer scheme_realm, gpointer auth, gpointer data)
365 {
366         g_free (scheme_realm);
367         g_object_unref (auth);
368 }
369
370 static void
371 free_host (SoupSessionHost *host, SoupSession *session)
372 {
373         while (host->connections) {
374                 SoupConnection *conn = host->connections->data;
375
376                 host->connections = g_slist_remove (host->connections, conn);
377                 soup_connection_disconnect (conn);
378         }
379
380         if (host->auth_realms) {
381                 g_hash_table_foreach (host->auth_realms, free_realm, NULL);
382                 g_hash_table_destroy (host->auth_realms);
383         }
384         if (host->auths) {
385                 g_hash_table_foreach (host->auths, free_auth, NULL);
386                 g_hash_table_destroy (host->auths);
387         }
388
389         soup_uri_free (host->root_uri);
390 }       
391
392 /* Authentication */
393
394 static SoupAuth *
395 lookup_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
396 {
397         SoupSessionHost *host;
398         char *path, *dir;
399         const char *realm, *const_path;
400
401         if (proxy) {
402                 host = session->priv->proxy_host;
403                 const_path = "/";
404         } else {
405                 host = get_host_for_message (session, msg);
406                 const_path = soup_message_get_uri (msg)->path;
407         }
408         g_return_val_if_fail (host != NULL, NULL);
409
410         if (!host->auth_realms)
411                 return NULL;
412
413         path = g_strdup (const_path);
414         dir = path;
415         do {
416                 realm = g_hash_table_lookup (host->auth_realms, path);
417                 if (realm)
418                         break;
419
420                 dir = strrchr (path, '/');
421                 if (dir)
422                         *dir = '\0';
423         } while (dir);
424
425         g_free (path);
426         if (realm)
427                 return g_hash_table_lookup (host->auths, realm);
428         else
429                 return NULL;
430 }
431
432 static void
433 invalidate_auth (SoupSessionHost *host, SoupAuth *auth)
434 {
435         char *realm;
436         gpointer key, value;
437
438         realm = g_strdup_printf ("%s:%s",
439                                  soup_auth_get_scheme_name (auth),
440                                  soup_auth_get_realm (auth));
441
442         if (g_hash_table_lookup_extended (host->auths, realm, &key, &value) &&
443             auth == (SoupAuth *)value) {
444                 g_hash_table_remove (host->auths, realm);
445                 g_free (key);
446                 g_object_unref (auth);
447         }
448         g_free (realm);
449 }
450
451 static gboolean
452 authenticate_auth (SoupSession *session, SoupAuth *auth,
453                    SoupMessage *msg, gboolean prior_auth_failed)
454 {
455         const SoupUri *uri = soup_message_get_uri (msg);
456         char *username = NULL, *password = NULL;
457
458         if (uri->passwd && !prior_auth_failed) {
459                 soup_auth_authenticate (auth, uri->user, uri->passwd);
460                 return TRUE;
461         }
462
463         g_signal_emit (session, signals[prior_auth_failed ? REAUTHENTICATE : AUTHENTICATE], 0,
464                        msg, soup_auth_get_scheme_name (auth),
465                        soup_auth_get_realm (auth),
466                        &username, &password);
467         if (username || password)
468                 soup_auth_authenticate (auth, username, password);
469         if (username)
470                 g_free (username);
471         if (password) {
472                 memset (password, 0, strlen (password));
473                 g_free (password);
474         }
475
476         return soup_auth_is_authenticated (auth);
477 }
478
479 static gboolean
480 update_auth_internal (SoupSession *session, SoupMessage *msg,
481                       const GSList *headers, gboolean proxy,
482                       gboolean got_unauthorized)
483 {
484         SoupSessionHost *host;
485         SoupAuth *new_auth, *prior_auth, *old_auth;
486         gpointer old_path, old_realm;
487         const SoupUri *msg_uri;
488         const char *path;
489         char *realm;
490         GSList *pspace, *p;
491         gboolean prior_auth_failed = FALSE;
492
493         host = get_host_for_message (session, msg);
494         g_return_val_if_fail (host != NULL, FALSE);
495
496         /* Try to construct a new auth from the headers; if we can't,
497          * there's no way we'll be able to authenticate.
498          */
499         msg_uri = soup_message_get_uri (msg);
500         new_auth = soup_auth_new_from_header_list (headers);
501         if (!new_auth)
502                 return FALSE;
503
504         /* See if this auth is the same auth we used last time */
505         prior_auth = lookup_auth (session, msg, proxy);
506         if (prior_auth &&
507             G_OBJECT_TYPE (prior_auth) == G_OBJECT_TYPE (new_auth) &&
508             !strcmp (soup_auth_get_realm (prior_auth),
509                      soup_auth_get_realm (new_auth))) {
510                 if (!got_unauthorized) {
511                         /* The user is just trying to preauthenticate
512                          * using information we already have, so
513                          * there's nothing more that needs to be done.
514                          */
515                         g_object_unref (new_auth);
516                         return TRUE;
517                 }
518
519                 /* The server didn't like the username/password we
520                  * provided before. Invalidate it and note this fact.
521                  */
522                 invalidate_auth (host, prior_auth);
523                 prior_auth_failed = TRUE;
524         }
525
526         if (!host->auth_realms) {
527                 host->auth_realms = g_hash_table_new (g_str_hash, g_str_equal);
528                 host->auths = g_hash_table_new (g_str_hash, g_str_equal);
529         }
530
531         /* Record where this auth realm is used */
532         realm = g_strdup_printf ("%s:%s",
533                                  soup_auth_get_scheme_name (new_auth),
534                                  soup_auth_get_realm (new_auth));
535         pspace = soup_auth_get_protection_space (new_auth, msg_uri);
536         for (p = pspace; p; p = p->next) {
537                 path = p->data;
538                 if (g_hash_table_lookup_extended (host->auth_realms, path,
539                                                   &old_path, &old_realm)) {
540                         g_hash_table_remove (host->auth_realms, old_path);
541                         g_free (old_path);
542                         g_free (old_realm);
543                 }
544
545                 g_hash_table_insert (host->auth_realms,
546                                      g_strdup (path), g_strdup (realm));
547         }
548         soup_auth_free_protection_space (new_auth, pspace);
549
550         /* Now, make sure the auth is recorded. (If there's a
551          * pre-existing auth, we keep that rather than the new one,
552          * since the old one might already be authenticated.)
553          */
554         old_auth = g_hash_table_lookup (host->auths, realm);
555         if (old_auth) {
556                 g_free (realm);
557                 g_object_unref (new_auth);
558                 new_auth = old_auth;
559         } else 
560                 g_hash_table_insert (host->auths, realm, new_auth);
561
562         /* If we need to authenticate, try to do it. */
563         if (!soup_auth_is_authenticated (new_auth)) {
564                 return authenticate_auth (session, new_auth,
565                                           msg, prior_auth_failed);
566         }
567
568         /* Otherwise we're good. */
569         return TRUE;
570 }
571
572 static void
573 connection_authenticate (SoupConnection *conn, SoupMessage *msg,
574                          const char *auth_type, const char *auth_realm,
575                          char **username, char **password, gpointer session)
576 {
577         g_signal_emit (session, signals[AUTHENTICATE], 0,
578                        msg, auth_type, auth_realm, username, password);
579 }
580
581 static void
582 connection_reauthenticate (SoupConnection *conn, SoupMessage *msg,
583                            const char *auth_type, const char *auth_realm,
584                            char **username, char **password,
585                            gpointer user_data)
586 {
587         g_signal_emit (conn, signals[REAUTHENTICATE], 0,
588                        msg, auth_type, auth_realm, username, password);
589 }
590
591
592 static void
593 authorize_handler (SoupMessage *msg, gpointer user_data)
594 {
595         SoupSession *session = user_data;
596         const GSList *headers;
597         gboolean proxy;
598
599         if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
600                 headers = soup_message_get_header_list (msg->response_headers,
601                                                         "Proxy-Authenticate");
602                 proxy = TRUE;
603         } else {
604                 headers = soup_message_get_header_list (msg->response_headers,
605                                                         "WWW-Authenticate");
606                 proxy = FALSE;
607         }
608         if (!headers)
609                 return;
610
611         if (update_auth_internal (session, msg, headers, proxy, TRUE))
612                 soup_session_requeue_message (session, msg);
613 }
614
615 static void
616 redirect_handler (SoupMessage *msg, gpointer user_data)
617 {
618         SoupSession *session = user_data;
619         const char *new_loc;
620         SoupUri *new_uri;
621
622         new_loc = soup_message_get_header (msg->response_headers, "Location");
623         if (!new_loc)
624                 return;
625         new_uri = soup_uri_new (new_loc);
626         if (!new_uri)
627                 goto INVALID_REDIRECT;
628
629         soup_message_set_uri (msg, new_uri);
630         soup_uri_free (new_uri);
631
632         soup_session_requeue_message (session, msg);
633         return;
634
635  INVALID_REDIRECT:
636         soup_message_set_status_full (msg,
637                                       SOUP_STATUS_MALFORMED,
638                                       "Invalid Redirect URL");
639 }
640
641 static void
642 request_restarted (SoupMessage *req, gpointer session)
643 {
644         run_queue (session, FALSE);
645 }
646
647 static void
648 request_finished (SoupMessage *req, gpointer user_data)
649 {
650         req->status = SOUP_MESSAGE_STATUS_FINISHED;
651 }
652
653 static void
654 final_finished (SoupMessage *req, gpointer user_data)
655 {
656         SoupSession *session = user_data;
657
658         if (!SOUP_MESSAGE_IS_STARTING (req)) {
659                 soup_message_queue_remove_message (session->priv->queue, req);
660
661                 g_signal_handlers_disconnect_by_func (req, request_finished, session);
662                 g_signal_handlers_disconnect_by_func (req, final_finished, session);
663                 g_object_unref (req);
664         }
665
666         run_queue (session, FALSE);
667 }
668
669 static void
670 add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
671 {
672         const char *header = proxy ? "Proxy-Authorization" : "Authorization";
673         SoupAuth *auth;
674         char *token;
675
676         soup_message_remove_header (msg->request_headers, header);
677
678         auth = lookup_auth (session, msg, proxy);
679         if (!auth)
680                 return;
681         if (!soup_auth_is_authenticated (auth) &&
682             !authenticate_auth (session, auth, msg, FALSE))
683                 return;
684
685         token = soup_auth_get_authorization (auth, msg);
686         if (token) {
687                 soup_message_add_header (msg->request_headers, header, token);
688                 g_free (token);
689         }
690 }
691
692 static void
693 send_request (SoupSession *session, SoupMessage *req, SoupConnection *conn)
694 {
695         req->status = SOUP_MESSAGE_STATUS_RUNNING;
696
697         add_auth (session, req, FALSE);
698         if (session->priv->proxy_uri)
699                 add_auth (session, req, TRUE);
700         soup_connection_send_request (conn, req);
701 }
702
703 static void
704 find_oldest_connection (gpointer key, gpointer host, gpointer data)
705 {
706         SoupConnection *conn = key, **oldest = data;
707
708         /* Don't prune a connection that hasn't even been used yet. */
709         if (soup_connection_last_used (conn) == 0)
710                 return;
711
712         if (!*oldest || (soup_connection_last_used (conn) <
713                          soup_connection_last_used (*oldest)))
714                 *oldest = conn;
715 }
716
717 static gboolean
718 try_prune_connection (SoupSession *session)
719 {
720         SoupConnection *oldest = NULL;
721
722         g_hash_table_foreach (session->priv->conns, find_oldest_connection,
723                               &oldest);
724         if (oldest) {
725                 soup_connection_disconnect (oldest);
726                 g_object_unref (oldest);
727                 return TRUE;
728         } else
729                 return FALSE;
730 }
731
732 static void connection_closed (SoupConnection *conn, SoupSession *session);
733
734 static void
735 cleanup_connection (SoupSession *session, SoupConnection *conn)
736 {
737         SoupSessionHost *host =
738                 g_hash_table_lookup (session->priv->conns, conn);
739
740         g_return_if_fail (host != NULL);
741
742         g_hash_table_remove (session->priv->conns, conn);
743         g_signal_handlers_disconnect_by_func (conn, connection_closed, session);
744         session->priv->num_conns--;
745
746         host->connections = g_slist_remove (host->connections, conn);
747         host->num_conns--;
748 }
749
750 static void
751 connection_closed (SoupConnection *conn, SoupSession *session)
752 {
753         cleanup_connection (session, conn);
754
755         /* Run the queue in case anyone was waiting for a connection
756          * to be closed.
757          */
758         run_queue (session, FALSE);
759 }
760
761 static void
762 got_connection (SoupConnection *conn, guint status, gpointer user_data)
763 {
764         SoupSession *session = user_data;
765         SoupSessionHost *host = g_hash_table_lookup (session->priv->conns, conn);
766
767         g_return_if_fail (host != NULL);
768
769         if (status == SOUP_STATUS_OK) {
770                 host->connections = g_slist_prepend (host->connections, conn);
771                 run_queue (session, FALSE);
772                 return;
773         }
774
775         /* We failed */
776         cleanup_connection (session, conn);
777         g_object_unref (conn);
778
779         if (host->connections) {
780                 /* Something went wrong this time, but we have at
781                  * least one open connection to this host. So just
782                  * leave the message in the queue so it can use that
783                  * connection once it's free.
784                  */
785                 return;
786         }
787
788         /* Flush any queued messages for this host */
789         host->error = status;
790         run_queue (session, FALSE);
791
792         if (status != SOUP_STATUS_CANT_RESOLVE &&
793             status != SOUP_STATUS_CANT_RESOLVE_PROXY) {
794                 /* If the error was "can't resolve", then it's not likely
795                  * to improve. But if it was something else, it may have
796                  * been transient, so we clear the error so the user can
797                  * try again later.
798                  */
799                 host->error = 0;
800         }
801 }
802
803 static gboolean
804 run_queue (SoupSession *session, gboolean try_pruning)
805 {
806         SoupMessageQueueIter iter;
807         SoupMessage *msg;
808         SoupConnection *conn;
809         SoupSessionHost *host;
810         gboolean skipped_any = FALSE, started_any = FALSE;
811         GSList *conns;
812
813         /* FIXME: prefer CONNECTING messages */
814
815  try_again:
816         for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
817
818                 if (!SOUP_MESSAGE_IS_STARTING (msg))
819                         continue;
820
821                 host = get_host_for_message (session, msg);
822
823                 /* If the hostname is known to be bad, fail right away */
824                 if (host->error) {
825                         soup_message_set_status (msg, host->error);
826                         msg->status = SOUP_MESSAGE_STATUS_FINISHED;
827                         soup_message_finished (msg);
828                 }
829
830                 /* If there is an idle connection, use it */
831                 for (conns = host->connections; conns; conns = conns->next) {
832                         if (!soup_connection_is_in_use (conns->data))
833                                 break;
834                 }
835                 if (conns) {
836                         send_request (session, msg, conns->data);
837                         started_any = TRUE;
838                         continue;
839                 }
840
841                 if (msg->status == SOUP_MESSAGE_STATUS_CONNECTING) {
842                         /* We already started a connection for this
843                          * message, so don't start another one.
844                          */
845                         continue;
846                 }
847
848                 /* If we have the max number of per-host connections
849                  * or total connections open, we'll have to wait.
850                  */
851                 if (host->num_conns >= session->priv->max_conns_per_host)
852                         continue;
853                 else if (session->priv->num_conns >= session->priv->max_conns) {
854                         /* In this case, closing an idle connection
855                          * somewhere else would let us open one here.
856                          */
857                         skipped_any = TRUE;
858                         continue;
859                 }
860
861                 /* Otherwise, open a new connection */
862                 conn = g_object_new (
863                         (session->priv->use_ntlm ?
864                          SOUP_TYPE_CONNECTION_NTLM : SOUP_TYPE_CONNECTION),
865                         SOUP_CONNECTION_ORIGIN_URI, host->root_uri,
866                         SOUP_CONNECTION_PROXY_URI, session->priv->proxy_uri,
867                         SOUP_CONNECTION_SSL_CREDENTIALS, session->priv->ssl_creds,
868                         NULL);
869                 g_signal_connect (conn, "authenticate",
870                                   G_CALLBACK (connection_authenticate),
871                                   session);
872                 g_signal_connect (conn, "reauthenticate",
873                                   G_CALLBACK (connection_reauthenticate),
874                                   session);
875
876                 soup_connection_connect_async (conn, got_connection, session);
877                 g_signal_connect (conn, "disconnected",
878                                   G_CALLBACK (connection_closed), session);
879                 g_hash_table_insert (session->priv->conns, conn, host);
880                 session->priv->num_conns++;
881
882                 /* Increment the host's connection count, but don't add
883                  * this connection to the list yet, since it's not ready.
884                  */
885                 host->num_conns++;
886
887                 /* Mark the request as connecting, so we don't try to
888                  * open another new connection for it next time around.
889                  */
890                 msg->status = SOUP_MESSAGE_STATUS_CONNECTING;
891
892                 started_any = TRUE;
893         }
894
895         if (try_pruning && skipped_any && !started_any) {
896                 /* We didn't manage to start any message, but there is
897                  * at least one message in the queue that could be
898                  * sent if we pruned an idle connection from some
899                  * other server.
900                  */
901                 if (try_prune_connection (session)) {
902                         try_pruning = FALSE;
903                         goto try_again;
904                 }
905         }
906
907         return started_any;
908 }
909
910 static void
911 queue_message (SoupSession *session, SoupMessage *req, gboolean requeue)
912 {
913         req->status = SOUP_MESSAGE_STATUS_QUEUED;
914         if (!requeue) {
915                 soup_message_queue_append (session->priv->queue, req);
916                 run_queue (session, TRUE);
917         }
918 }
919
920 /**
921  * soup_session_queue_message:
922  * @session: a #SoupSession
923  * @req: the message to queue
924  * @callback: a #SoupMessageCallbackFn which will be called after the
925  * message completes or when an unrecoverable error occurs.
926  * @user_data: a pointer passed to @callback.
927  * 
928  * Queues the message @req for sending. All messages are processed
929  * while the glib main loop runs. If @req has been processed before,
930  * any resources related to the time it was last sent are freed.
931  *
932  * Upon message completion, the callback specified in @callback will
933  * be invoked. If after returning from this callback the message has
934  * not been requeued, @req will be unreffed.
935  */
936 void
937 soup_session_queue_message (SoupSession *session, SoupMessage *req,
938                             SoupMessageCallbackFn callback, gpointer user_data)
939 {
940         g_return_if_fail (SOUP_IS_SESSION (session));
941         g_return_if_fail (SOUP_IS_MESSAGE (req));
942
943         g_signal_connect (req, "restarted",
944                           G_CALLBACK (request_restarted), session);
945
946         g_signal_connect (req, "finished",
947                           G_CALLBACK (request_finished), session);
948         if (callback) {
949                 g_signal_connect (req, "finished",
950                                   G_CALLBACK (callback), user_data);
951         }
952         g_signal_connect_after (req, "finished",
953                                 G_CALLBACK (final_finished), session);
954
955         soup_message_add_status_code_handler  (req, SOUP_STATUS_UNAUTHORIZED,
956                                                SOUP_HANDLER_POST_BODY,
957                                                authorize_handler, session);
958         soup_message_add_status_code_handler  (req,
959                                                SOUP_STATUS_PROXY_UNAUTHORIZED,
960                                                SOUP_HANDLER_POST_BODY,
961                                                authorize_handler, session);
962
963         if (!(soup_message_get_flags (req) & SOUP_MESSAGE_NO_REDIRECT)) {
964                 soup_message_add_status_class_handler (
965                         req, SOUP_STATUS_CLASS_REDIRECT,
966                         SOUP_HANDLER_POST_BODY,
967                         redirect_handler, session);
968         }
969
970         queue_message (session, req, FALSE);
971 }
972
973 /**
974  * soup_session_requeue_message:
975  * @session: a #SoupSession
976  * @req: the message to requeue
977  *
978  * This causes @req to be placed back on the queue to be attempted
979  * again.
980  **/
981 void
982 soup_session_requeue_message (SoupSession *session, SoupMessage *req)
983 {
984         g_return_if_fail (SOUP_IS_SESSION (session));
985         g_return_if_fail (SOUP_IS_MESSAGE (req));
986
987         queue_message (session, req, TRUE);
988 }
989
990
991 /**
992  * soup_session_send_message:
993  * @session: a #SoupSession
994  * @req: the message to send
995  * 
996  * Synchronously send @req. This call will not return until the
997  * transfer is finished successfully or there is an unrecoverable
998  * error.
999  *
1000  * @req is not freed upon return.
1001  *
1002  * Return value: the HTTP status code of the response
1003  */
1004 guint
1005 soup_session_send_message (SoupSession *session, SoupMessage *req)
1006 {
1007         g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
1008         g_return_val_if_fail (SOUP_IS_MESSAGE (req), SOUP_STATUS_MALFORMED);
1009
1010         /* Balance out the unref that final_finished will do */
1011         g_object_ref (req);
1012
1013         soup_session_queue_message (session, req, NULL, NULL);
1014
1015         while (req->status != SOUP_MESSAGE_STATUS_FINISHED &&
1016                !SOUP_STATUS_IS_TRANSPORT_ERROR (req->status_code))
1017                 g_main_iteration (TRUE);
1018
1019         return req->status_code;
1020 }
1021
1022 /**
1023  * soup_session_abort:
1024  * @session: the session
1025  *
1026  * Cancels all pending requests in @session.
1027  **/
1028 void
1029 soup_session_abort (SoupSession *session)
1030 {
1031         SoupMessageQueueIter iter;
1032         SoupMessage *msg;
1033
1034         for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
1035                 soup_message_queue_remove (session->priv->queue, &iter);
1036                 soup_message_cancel (msg);
1037         }
1038 }