+2003-09-05 Dan Winship <danw@ximian.com>
+
+ * libsoup/soup-session.c: Move a bunch of logic here from
+ soup-context. Now the session keeps track of hosts (instead of
+ having a global soup_hosts hash) and their connections.
+ (soup_session_new_with_proxy, soup_session_new_full): New session
+ constructors to specify a proxy or a proxy and connection limits
+ (send_request): Add Authorization and Proxy-Authorization headers
+ before sending off the request.
+ (soup_session_queue_message, et al): Improve the way this works.
+ There's no need to use timeouts to wait for connections to become
+ free; we *know* when they become free.
+
+ * libsoup/soup-private.h: Remove SoupHost and some other
+ no-longer-used stuff.
+
+ * libsoup/soup-misc.c (soup_set_proxy, soup_get_proxy,
+ soup_set_connection_limit, soup_set_connection_limit): Gone. These
+ are all per-session now.
+
+ * libsoup/soup-message.c: Remove all SoupContext references
+ (mostly replaced with SoupUri references)
+ (cleanup_message): priv->connect_tag and priv->connection are gone
+ now, so this was just soup_message_io_cancel(). So remove
+ cleanup_message and replace it with that everywhere.
+ (soup_message_disconnect): Gone.
+ (soup_message_set_uri): Replaces soup_message_set_context.
+ (soup_message_set_connection, soup_message_get_connection): Gone
+
+ * libsoup/soup-message-server-io.c (parse_request_headers):
+ s/soup_message_set_context/soup_message_set_uri/
+
+ * libsoup/soup-message-private.h (SoupMessagePrivate): Remove
+ connect_tag, context, and connection.
+
+ * libsoup/soup-message-client-io.c (encode_http_auth): Gone.
+
+ * libsoup/soup-context.c: Gone
+
+ * tests/auth-test.c (identify_auth): update for session/context
+ changes
+
2003-09-03 Dan Winship <danw@ximian.com>
* libsoup/soup-status.h: Renamed from soup-error.h, with types
soup.h \
soup-address.h \
soup-connection.h \
- soup-context.h \
soup-headers.h \
soup-message.h \
soup-method.h \
soup-auth-ntlm.h \
soup-auth-ntlm.c \
soup-connection.c \
- soup-context.c \
soup-dns.h \
soup-dns.c \
soup-gnutls.h \
+++ /dev/null
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
-/*
- * soup-context.c: Asyncronous Callback-based HTTP Request Queue.
- *
- * Copyright (C) 2000-2003, Ximian, Inc.
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-#include <glib.h>
-
-#include <fcntl.h>
-#include <sys/types.h>
-
-#include <sys/socket.h>
-#include <netinet/tcp.h>
-#include <netinet/in.h>
-
-#include "soup-auth.h"
-#include "soup-auth-ntlm.h"
-#include "soup-context.h"
-#include "soup-connection.h"
-#include "soup-message-private.h"
-#include "soup-private.h"
-#include "soup-misc.h"
-#include "soup-socket.h"
-#include "soup-ssl.h"
-
-GHashTable *soup_hosts; /* KEY: hostname, VALUE: SoupHost */
-static int connection_count = 0;
-
-struct SoupContextPrivate {
- SoupUri *uri;
- SoupHost *server;
-};
-
-#define PARENT_TYPE G_TYPE_OBJECT
-static GObjectClass *parent_class;
-
-static void
-init (GObject *object)
-{
- SoupContext *ctx = SOUP_CONTEXT (object);
-
- ctx->priv = g_new0 (SoupContextPrivate, 1);
-}
-
-static void
-free_path (gpointer path, gpointer realm, gpointer unused)
-{
- g_free (path);
- g_free (realm);
-}
-
-static void
-free_auth (gpointer key, gpointer auth, gpointer free_key)
-{
- if (free_key)
- g_free (key);
- g_object_unref (auth);
-}
-
-static void
-finalize (GObject *object)
-{
- SoupContext *ctx = SOUP_CONTEXT (object);
- SoupHost *serv = ctx->priv->server;
-
- if (serv && ctx->priv->uri) {
- g_hash_table_remove (serv->contexts, ctx->priv->uri);
- if (g_hash_table_size (serv->contexts) == 0) {
- /* Remove this host from the active hosts hash */
- g_hash_table_remove (soup_hosts, serv->host);
-
- /* Free all cached SoupAuths */
- if (serv->auth_realms) {
- g_hash_table_foreach (serv->auth_realms,
- free_path, NULL);
- g_hash_table_destroy (serv->auth_realms);
- }
- if (serv->auths) {
- g_hash_table_foreach (serv->auths,
- free_auth,
- GINT_TO_POINTER (TRUE));
- g_hash_table_destroy (serv->auths);
- }
- if (serv->ntlm_auths) {
- g_hash_table_foreach (serv->ntlm_auths,
- free_auth,
- GINT_TO_POINTER (FALSE));
- g_hash_table_destroy (serv->ntlm_auths);
- }
-
- g_hash_table_destroy (serv->contexts);
- g_free (serv->host);
- g_free (serv);
- }
- }
-
- if (ctx->priv->uri)
- soup_uri_free (ctx->priv->uri);
-
- g_free (ctx->priv);
-
- G_OBJECT_CLASS (parent_class)->finalize (object);
-}
-
-static void
-class_init (GObjectClass *object_class)
-{
- parent_class = g_type_class_ref (PARENT_TYPE);
-
- /* virtual method override */
- object_class->finalize = finalize;
-}
-
-SOUP_MAKE_TYPE (soup_context, SoupContext, class_init, init, PARENT_TYPE)
-
-
-/**
- * soup_context_get:
- * @uri: the stringified URI.
- *
- * Returns a pointer to the #SoupContext representing @uri. If a
- * context already exists for the URI, it is returned with an added
- * reference. Otherwise, a new context is created with a reference
- * count of one.
- *
- * Return value: a #SoupContext representing @uri.
- */
-SoupContext *
-soup_context_get (const char *uri)
-{
- SoupUri *suri;
- SoupContext *ctx;
-
- g_return_val_if_fail (uri != NULL, NULL);
-
- suri = soup_uri_new (uri);
- if (!suri)
- return NULL;
-
- ctx = soup_context_from_uri (suri);
- soup_uri_free (suri);
-
- return ctx;
-}
-
-/**
- * soup_context_uri_hash:
- * @key: a #SoupUri
- *
- * Return value: Hash value of the user, passwd, path, and query
- * fields in @key.
- **/
-static guint
-soup_context_uri_hash (gconstpointer key)
-{
- const SoupUri *uri = key;
- guint ret;
-
- ret = uri->protocol;
- if (uri->path)
- ret += g_str_hash (uri->path);
- if (uri->query)
- ret += g_str_hash (uri->query);
- if (uri->user)
- ret += g_str_hash (uri->user);
- if (uri->passwd)
- ret += g_str_hash (uri->passwd);
-
- return ret;
-}
-
-static inline gboolean
-parts_equal (const char *one, const char *two)
-{
- if (!one && !two)
- return TRUE;
- if (!one || !two)
- return FALSE;
- return !strcmp (one, two);
-}
-
-/**
- * soup_context_uri_equal:
- * @v1: a #SoupUri
- * @v2: a #SoupUri
- *
- * Return value: %TRUE if @v1 and @v2 match in user, passwd, path, and
- * query. Otherwise, %FALSE.
- **/
-static gboolean
-soup_context_uri_equal (gconstpointer v1, gconstpointer v2)
-{
- const SoupUri *one = v1;
- const SoupUri *two = v2;
-
- if (one->protocol != two->protocol)
- return FALSE;
- if (!parts_equal (one->path, two->path))
- return FALSE;
- if (!parts_equal (one->user, two->user))
- return FALSE;
- if (!parts_equal (one->passwd, two->passwd))
- return FALSE;
- if (!parts_equal (one->query, two->query))
- return FALSE;
-
- return TRUE;
-}
-
-/**
- * soup_context_from_uri:
- * @suri: a #SoupUri.
- *
- * Returns a pointer to the #SoupContext representing @suri. If a
- * context already exists for the URI, it is returned with an added
- * reference. Otherwise, a new context is created with a reference
- * count of one.
- *
- * Return value: a #SoupContext representing @uri.
- */
-SoupContext *
-soup_context_from_uri (const SoupUri *suri)
-{
- SoupHost *serv = NULL;
- SoupContext *ctx = NULL;
-
- g_return_val_if_fail (suri != NULL, NULL);
- g_return_val_if_fail (suri->protocol != 0, NULL);
-
- if (!soup_hosts) {
- soup_hosts = g_hash_table_new (soup_str_case_hash,
- soup_str_case_equal);
- } else
- serv = g_hash_table_lookup (soup_hosts, suri->host);
-
- if (!serv) {
- serv = g_new0 (SoupHost, 1);
- serv->host = g_strdup (suri->host);
- g_hash_table_insert (soup_hosts, serv->host, serv);
- }
-
- if (!serv->contexts) {
- serv->contexts = g_hash_table_new (soup_context_uri_hash,
- soup_context_uri_equal);
- } else
- ctx = g_hash_table_lookup (serv->contexts, suri);
-
- if (!ctx) {
- ctx = g_object_new (SOUP_TYPE_CONTEXT, NULL);
- ctx->priv->server = serv;
- ctx->priv->uri = soup_uri_copy (suri);
-
- g_hash_table_insert (serv->contexts, ctx->priv->uri, ctx);
- }
-
- return g_object_ref (ctx);
-}
-
-static void
-connection_disconnected (SoupConnection *conn, gpointer user_data)
-{
- SoupHost *server = user_data;
- SoupAuth *auth;
-
- if (server->ntlm_auths) {
- auth = g_hash_table_lookup (server->ntlm_auths, conn);
- if (auth) {
- g_hash_table_remove (server->ntlm_auths, conn);
- g_object_unref (auth);
- }
- }
-
- server->connections = g_slist_remove (server->connections, conn);
- connection_count--;
-}
-
-struct SoupConnectData {
- SoupContext *ctx;
- SoupConnectCallbackFn cb;
- gpointer user_data;
-
- guint timeout_tag;
- SoupConnection *conn;
-};
-
-static void
-prune_connection_foreach (gpointer key, gpointer value, gpointer data)
-{
- SoupHost *serv = value;
- SoupConnection **last = data;
- GSList *conns;
-
- for (conns = serv->connections; conns; conns = conns->next) {
- SoupConnection *conn = conns->data;
-
- if (soup_connection_is_in_use (conn))
- continue;
-
- if (*last == NULL ||
- soup_connection_last_used (*last) >
- soup_connection_last_used (conn))
- *last = conn;
- }
-}
-
-static gboolean
-prune_least_used_connection (void)
-{
- SoupConnection *last = NULL;
-
- g_hash_table_foreach (soup_hosts,
- (GHFunc) prune_connection_foreach,
- &last);
- if (last) {
- soup_connection_disconnect (last);
- g_object_unref (last);
- return TRUE;
- }
-
- return FALSE;
-}
-
-static gboolean retry_connect_timeout_cb (struct SoupConnectData *data);
-
-static void
-soup_context_connect_cb (SoupConnection *conn,
- guint status,
- gpointer user_data)
-{
- struct SoupConnectData *data = user_data;
- SoupContext *ctx = data->ctx;
-
- switch (status) {
- case SOUP_STATUS_OK:
- g_signal_connect (conn, "disconnected",
- G_CALLBACK (connection_disconnected),
- ctx->priv->server);
-
- /* FIXME */
- g_object_set_data (G_OBJECT (conn), "SoupContext-port",
- GUINT_TO_POINTER (ctx->priv->uri->port));
-
- ctx->priv->server->connections =
- g_slist_prepend (ctx->priv->server->connections, conn);
-
- break;
-
- case SOUP_STATUS_CANT_RESOLVE:
- connection_count--;
- g_object_unref (conn);
- break;
-
- default:
- connection_count--;
- g_object_unref (conn);
-
- /*
- * Check if another connection exists to this server
- * before reporting error.
- */
- if (ctx->priv->server->connections) {
- data->timeout_tag =
- g_timeout_add (
- 150,
- (GSourceFunc) retry_connect_timeout_cb,
- data);
- return;
- }
-
- break;
- }
-
- (*data->cb) (ctx, status, conn, data->user_data);
- g_object_unref (ctx);
- g_free (data);
-}
-
-static gboolean
-try_existing_connections (SoupContext *ctx,
- SoupConnectCallbackFn cb,
- gpointer user_data)
-{
- GSList *conns = ctx->priv->server->connections;
-
- while (conns) {
- SoupConnection *conn = conns->data;
- guint port = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (conn), "SoupContext-port"));
-
- if (!soup_connection_is_in_use (conn) &&
- port == ctx->priv->uri->port) {
- /* Issue success callback */
- (*cb) (ctx, SOUP_STATUS_OK, conn, user_data);
- return TRUE;
- }
-
- conns = conns->next;
- }
-
- return FALSE;
-}
-
-static gboolean
-try_create_connection (struct SoupConnectData *data)
-{
- int conn_limit = soup_get_connection_limit ();
- SoupContext *proxy = soup_get_proxy ();
- SoupUri *uri = data->ctx->priv->uri;
-
- /*
- * Check if we are allowed to create a new connection, otherwise wait
- * for next timeout.
- */
- if (conn_limit &&
- connection_count >= conn_limit &&
- !prune_least_used_connection ()) {
- data->conn = NULL;
- return FALSE;
- }
-
- connection_count++;
-
- data->timeout_tag = 0;
-
- if (proxy) {
- if (uri->protocol == SOUP_PROTOCOL_HTTPS) {
- data->conn = soup_connection_new_tunnel (
- proxy->priv->uri, uri,
- soup_context_connect_cb, data);
- } else {
- data->conn = soup_connection_new_proxy (
- proxy->priv->uri,
- soup_context_connect_cb, data);
- }
- } else
- data->conn = soup_connection_new (uri, soup_context_connect_cb, data);
-
- return TRUE;
-}
-
-static gboolean
-retry_connect_timeout_cb (struct SoupConnectData *data)
-{
- if (try_existing_connections (data->ctx,
- data->cb,
- data->user_data)) {
- g_object_unref (data->ctx);
- g_free (data);
- return FALSE;
- }
-
- return try_create_connection (data) == FALSE;
-}
-
-/**
- * soup_context_get_connection:
- * @ctx: a #SoupContext.
- * @cb: a #SoupConnectCallbackFn to be called when a valid connection is
- * available.
- * @user_data: the user_data passed to @cb.
- *
- * Initiates the process of establishing a network connection to the
- * server referenced in @ctx. If an existing connection is available
- * and not in use, @cb is called immediately, and a #SoupConnectId of
- * 0 is returned. Otherwise, a new connection is established. If the
- * current connection count exceeds that set in
- * soup_set_connection_limit(), the new connection is not created
- * until an existing connection is closed.
- *
- * Once a network connection is successfully established, or an
- * existing connection becomes available for use, @cb is called,
- * passing the #SoupConnection representing it.
- *
- * Return value: a #SoupConnectId which can be used to cancel a
- * connection attempt using soup_context_cancel_connect().
- */
-SoupConnectId
-soup_context_get_connection (SoupContext *ctx,
- SoupConnectCallbackFn cb,
- gpointer user_data)
-{
- struct SoupConnectData *data;
-
- g_return_val_if_fail (SOUP_IS_CONTEXT (ctx), NULL);
-
- /* Look for an existing unused connection */
- if (try_existing_connections (ctx, cb, user_data))
- return NULL;
-
- data = g_new0 (struct SoupConnectData, 1);
- data->cb = cb;
- data->user_data = user_data;
-
- data->ctx = g_object_ref (ctx);
-
- if (!try_create_connection (data)) {
- data->timeout_tag =
- g_timeout_add (150,
- (GSourceFunc) retry_connect_timeout_cb,
- data);
- }
- return data;
-}
-
-/**
- * soup_context_cancel_connect:
- * @tag: a #SoupConnectId representing a connection in progress.
- *
- * Cancels the connection attempt represented by @tag. The
- * #SoupConnectCallbackFn passed in soup_context_get_connection() is
- * not called.
- */
-void
-soup_context_cancel_connect (SoupConnectId tag)
-{
- struct SoupConnectData *data = tag;
-
- g_return_if_fail (data != NULL);
-
- if (data->timeout_tag)
- g_source_remove (data->timeout_tag);
- else if (data->conn) {
- connection_count--;
- g_object_unref (data->conn);
- }
-
- g_free (data);
-}
-
-/**
- * soup_context_get_uri:
- * @ctx: a #SoupContext.
- *
- * Returns a pointer to the #SoupUri represented by @ctx.
- *
- * Return value: the #SoupUri for @ctx.
- */
-const SoupUri *
-soup_context_get_uri (SoupContext *ctx)
-{
- g_return_val_if_fail (SOUP_IS_CONTEXT (ctx), NULL);
- return ctx->priv->uri;
-}
-
-static void
-get_idle_conns_for_host (gpointer key, gpointer value, gpointer data)
-{
- SoupHost *host = value;
- SoupConnection *conn;
- GSList *c, **idle_conns = data;
-
- for (c = host->connections; c; c = c->next) {
- conn = c->data;
- if (!soup_connection_is_in_use (conn))
- *idle_conns = g_slist_prepend (*idle_conns, conn);
- }
-}
-
-/**
- * soup_connection_purge_idle:
- *
- * Closes all idle open connections.
- **/
-void
-soup_connection_purge_idle (void)
-{
- GSList *idle_conns, *i;
-
- if (!soup_hosts)
- return;
-
- idle_conns = NULL;
- g_hash_table_foreach (soup_hosts, get_idle_conns_for_host, &idle_conns);
-
- for (i = idle_conns; i; i = i->next) {
- soup_connection_disconnect (i->data);
- g_object_unref (i->data);
- }
- g_slist_free (idle_conns);
-}
-
-
-/* Authentication */
-
-SoupAuth *
-soup_context_lookup_auth (SoupContext *ctx, SoupMessage *msg)
-{
- char *path, *dir;
- const char *realm;
-
- g_return_val_if_fail (SOUP_IS_CONTEXT (ctx), NULL);
-
- if (ctx->priv->server->ntlm_auths && msg) {
- SoupConnection *conn = soup_message_get_connection (msg);
-
- if (conn) {
- GHashTable *ntlm_auths = ctx->priv->server->ntlm_auths;
- SoupAuth *auth;
-
- auth = g_hash_table_lookup (ntlm_auths, conn);
- if (!auth) {
- auth = soup_auth_ntlm_new ();
- g_hash_table_insert (ntlm_auths, conn, auth);
- }
- return auth;
- }
- }
-
- if (!ctx->priv->server->auth_realms)
- return NULL;
-
- path = g_strdup (ctx->priv->uri->path);
- dir = path;
- do {
- realm = g_hash_table_lookup (ctx->priv->server->auth_realms, path);
- if (realm)
- break;
-
- dir = strrchr (path, '/');
- if (dir)
- *dir = '\0';
- } while (dir);
-
- g_free (path);
- if (realm)
- return g_hash_table_lookup (ctx->priv->server->auths, realm);
- else
- return NULL;
-}
-
-static gboolean
-update_auth_internal (SoupContext *ctx, SoupConnection *conn,
- const GSList *headers, gboolean prior_auth_failed)
-{
- SoupHost *server = ctx->priv->server;
- SoupAuth *new_auth, *prior_auth, *old_auth;
- gpointer old_path, old_realm;
- const char *path;
- char *realm;
- GSList *pspace, *p;
-
- if (server->ntlm_auths && conn) {
- prior_auth = g_hash_table_lookup (server->ntlm_auths, conn);
- if (prior_auth) {
- if (soup_auth_is_authenticated (prior_auth)) {
- /* This is a "permission denied", not
- * a "password incorrect". There's
- * nothing more we can do.
- */
- return FALSE;
- }
-
- /* Free the intermediate auth */
- g_hash_table_remove (server->ntlm_auths, conn);
- g_object_unref (prior_auth);
- }
- }
-
- /* Try to construct a new auth from the headers; if we can't,
- * there's no way we'll be able to authenticate.
- */
- new_auth = soup_auth_new_from_header_list (headers, ctx->priv->uri->authmech);
- if (!new_auth)
- return FALSE;
-
- /* See if this auth is the same auth we used last time */
- prior_auth = soup_context_lookup_auth (ctx, NULL);
- if (prior_auth &&
- G_OBJECT_TYPE (prior_auth) == G_OBJECT_TYPE (new_auth) &&
- !strcmp (soup_auth_get_realm (prior_auth),
- soup_auth_get_realm (new_auth))) {
- g_object_unref (new_auth);
- if (prior_auth_failed) {
- /* The server didn't like the username/password
- * we provided before.
- */
- soup_context_invalidate_auth (ctx, prior_auth);
- return FALSE;
- } else {
- /* The user is trying to preauthenticate using
- * information we already have, so there's nothing
- * that needs to be done.
- */
- return TRUE;
- }
- }
-
- if (SOUP_IS_AUTH_NTLM (new_auth)) {
- if (!server->ntlm_auths)
- server->ntlm_auths = g_hash_table_new (NULL, NULL);
- if (conn) {
- g_hash_table_insert (server->ntlm_auths, conn, new_auth);
- return soup_context_authenticate_auth (ctx, new_auth);
- } else {
- g_object_unref (new_auth);
- return FALSE;
- }
- }
-
- if (!server->auth_realms) {
- server->auth_realms = g_hash_table_new (g_str_hash, g_str_equal);
- server->auths = g_hash_table_new (g_str_hash, g_str_equal);
- }
-
- /* Record where this auth realm is used */
- realm = g_strdup_printf ("%s:%s",
- soup_auth_get_scheme_name (new_auth),
- soup_auth_get_realm (new_auth));
- pspace = soup_auth_get_protection_space (new_auth, ctx->priv->uri);
- for (p = pspace; p; p = p->next) {
- path = p->data;
- if (g_hash_table_lookup_extended (server->auth_realms, path,
- &old_path, &old_realm)) {
- g_hash_table_remove (server->auth_realms, old_path);
- g_free (old_path);
- g_free (old_realm);
- }
-
- g_hash_table_insert (server->auth_realms,
- g_strdup (path), g_strdup (realm));
- }
- soup_auth_free_protection_space (new_auth, pspace);
-
- /* Now, make sure the auth is recorded. (If there's a
- * pre-existing auth, we keep that rather than the new one,
- * since the old one might already be authenticated.)
- */
- old_auth = g_hash_table_lookup (server->auths, realm);
- if (old_auth) {
- g_free (realm);
- g_object_unref (new_auth);
- new_auth = old_auth;
- } else
- g_hash_table_insert (server->auths, realm, new_auth);
-
- /* Try to authenticate if needed. */
- if (!soup_auth_is_authenticated (new_auth))
- return soup_context_authenticate_auth (ctx, new_auth);
-
- return TRUE;
-}
-
-gboolean
-soup_context_update_auth (SoupContext *ctx, SoupMessage *msg)
-{
- const GSList *headers;
-
- g_return_val_if_fail (SOUP_IS_CONTEXT (ctx), FALSE);
- g_return_val_if_fail (msg != NULL, FALSE);
-
- if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
- headers = soup_message_get_header_list (msg->response_headers,
- "Proxy-Authenticate");
- } else {
- headers = soup_message_get_header_list (msg->response_headers,
- "WWW-Authenticate");
- }
-
- return update_auth_internal (ctx, soup_message_get_connection (msg),
- headers, TRUE);
-}
-
-void
-soup_context_preauthenticate (SoupContext *ctx, const char *header)
-{
- GSList *headers;
-
- g_return_if_fail (SOUP_IS_CONTEXT (ctx));
- g_return_if_fail (header != NULL);
-
- headers = g_slist_append (NULL, (char *)header);
- update_auth_internal (ctx, NULL, headers, FALSE);
- g_slist_free (headers);
-}
-
-gboolean
-soup_context_authenticate_auth (SoupContext *ctx, SoupAuth *auth)
-{
- const SoupUri *uri = ctx->priv->uri;
-
- if (!uri->user && soup_auth_fn) {
- (*soup_auth_fn) (soup_auth_get_scheme_name (auth),
- (SoupUri *) uri,
- soup_auth_get_realm (auth),
- soup_auth_fn_user_data);
- }
-
- if (!uri->user)
- return FALSE;
-
- soup_auth_authenticate (auth, uri->user, uri->passwd);
- return TRUE;
-}
-
-void
-soup_context_invalidate_auth (SoupContext *ctx, SoupAuth *auth)
-{
- char *realm;
- gpointer key, value;
-
- g_return_if_fail (SOUP_IS_CONTEXT (ctx));
- g_return_if_fail (auth != NULL);
-
- /* Try to just clean up the auth without removing it. */
- if (soup_auth_invalidate (auth))
- return;
-
- /* Nope, need to remove it completely */
- realm = g_strdup_printf ("%s:%s",
- soup_auth_get_scheme_name (auth),
- soup_auth_get_realm (auth));
-
- if (g_hash_table_lookup_extended (ctx->priv->server->auths, realm,
- &key, &value) &&
- auth == (SoupAuth *)value) {
- g_hash_table_remove (ctx->priv->server->auths, realm);
- g_free (key);
- g_object_unref (auth);
- }
- g_free (realm);
-}
+++ /dev/null
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
-/*
- * Copyright (C) 2000-2003, Ximian, Inc.
- */
-
-#ifndef SOUP_CONTEXT_H
-#define SOUP_CONTEXT_H 1
-
-#include <libsoup/soup-types.h>
-
-#define SOUP_TYPE_CONTEXT (soup_context_get_type ())
-#define SOUP_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_CONTEXT, SoupContext))
-#define SOUP_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CONTEXT, SoupContextClass))
-#define SOUP_IS_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_CONTEXT))
-#define SOUP_IS_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_CONTEXT))
-#define SOUP_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CONTEXT, SoupContextClass))
-
-typedef struct SoupContextPrivate SoupContextPrivate;
-
-struct SoupContext {
- GObject parent;
-
- SoupContextPrivate *priv;
-};
-
-typedef struct {
- GObjectClass parent_class;
-
-} SoupContextClass;
-
-GType soup_context_get_type (void);
-
-
-typedef void (*SoupConnectCallbackFn) (SoupContext *ctx,
- guint status,
- SoupConnection *conn,
- gpointer user_data);
-
-typedef gpointer SoupConnectId;
-
-SoupContext *soup_context_get (const char *uri);
-
-SoupContext *soup_context_from_uri (const SoupUri *suri);
-
-SoupConnectId soup_context_get_connection (SoupContext *ctx,
- SoupConnectCallbackFn cb,
- gpointer user_data);
-
-void soup_context_cancel_connect (SoupConnectId tag);
-
-const SoupUri *soup_context_get_uri (SoupContext *ctx);
-
-
-void soup_context_preauthenticate (SoupContext *ctx,
- const char *header);
-
-
-void soup_connection_purge_idle (void);
-
-
-#endif /*SOUP_CONTEXT_H*/
#include "soup-message-private.h"
#include "soup-auth.h"
-#include "soup-context.h"
#include "soup-headers.h"
#include "soup-misc.h"
#include "soup-private.h"
return SOUP_STATUS_OK;
}
-static void
-encode_http_auth (SoupMessage *msg, GString *header, gboolean proxy_auth)
-{
- SoupAuth *auth;
- SoupContext *ctx;
- char *token;
-
- ctx = proxy_auth ? soup_get_proxy () : msg->priv->context;
-
- auth = soup_context_lookup_auth (ctx, msg);
- if (!auth)
- return;
- if (!soup_auth_is_authenticated (auth) &&
- !soup_context_authenticate_auth (ctx, auth))
- return;
-
- token = soup_auth_get_authorization (auth, msg);
- if (token) {
- g_string_sprintfa (header, "%s: %s\r\n",
- proxy_auth ?
- "Proxy-Authorization" :
- "Authorization",
- token);
- g_free (token);
- }
-}
-
static void
add_header (gpointer name, gpointer value, gpointer data)
{
*encoding = SOUP_TRANSFER_CONTENT_LENGTH;
}
- encode_http_auth (req, header, FALSE);
- if (proxy)
- encode_http_auth (req, header, TRUE);
-
soup_message_foreach_header (req->request_headers, add_header, header);
g_string_append (header, "\r\n");
}
#define SOUP_MESSAGE_PRIVATE_H 1
#include <libsoup/soup-message.h>
-#include <libsoup/soup-context.h>
typedef enum {
SOUP_MESSAGE_STATUS_IDLE,
struct SoupMessagePrivate {
SoupMessageStatus status;
- SoupConnectId connect_tag;
gpointer io_data;
guint msg_flags;
SoupHttpVersion http_version;
- SoupContext *context;
- SoupConnection *connection;
+ SoupUri *uri;
};
void soup_message_run_handlers (SoupMessage *msg,
gboolean soup_message_is_keepalive (SoupMessage *msg);
-void soup_message_set_context (SoupMessage *msg,
- SoupContext *new_ctx);
-void soup_message_set_connection (SoupMessage *msg,
- SoupConnection *conn);
-SoupConnection *soup_message_get_connection (SoupMessage *msg);
+void soup_message_set_uri (SoupMessage *msg,
+ const SoupUri *uri);
typedef void (*SoupMessageGetHeadersFn) (SoupMessage *msg,
#include "soup-message-private.h"
#include "soup-address.h"
#include "soup-auth.h"
-#include "soup-context.h"
#include "soup-headers.h"
#include "soup-misc.h"
#include "soup-private.h"
SoupTransferEncoding *encoding, guint *content_len,
gpointer sock)
{
- SoupContext *ctx;
+ SoupUri *uri;
char *req_path = NULL, *url;
const char *length, *enc, *req_host;
SoupServer *server;
return SOUP_STATUS_BAD_REQUEST;
}
- ctx = soup_context_get (url);
+ uri = soup_uri_new (url);
g_free (url);
g_free (req_path);
- if (!ctx)
+ if (!uri)
return SOUP_STATUS_BAD_REQUEST;
- soup_message_set_context (msg, ctx);
- g_object_unref (ctx);
+ soup_message_set_uri (msg, uri);
+ soup_uri_free (uri);
return SOUP_STATUS_OK;
}
#include <string.h>
#include "soup-auth.h"
-#include "soup-connection.h"
#include "soup-marshal.h"
#include "soup-message.h"
#include "soup-message-private.h"
#include "soup-misc.h"
-#include "soup-context.h"
#include "soup-private.h"
#define PARENT_TYPE G_TYPE_OBJECT
static void got_chunk (SoupMessage *req);
static void got_body (SoupMessage *req);
static void finished (SoupMessage *req);
-static void cleanup_message (SoupMessage *req);
static void free_chunks (SoupMessage *msg);
static void
{
SoupMessage *msg = SOUP_MESSAGE (object);
- cleanup_message (msg);
+ soup_message_io_cancel (msg);
- if (msg->priv->context)
- g_object_unref (msg->priv->context);
+ if (msg->priv->uri)
+ soup_uri_free (msg->priv->uri);
if (msg->request.owner == SOUP_BUFFER_SYSTEM_OWNED)
g_free (msg->request.body);
/**
* soup_message_new:
* @method: the HTTP method for the created request
- * @uri: the destination endpoint (as a string)
+ * @uri_string: the destination endpoint (as a string)
*
* Creates a new empty #SoupMessage, which will connect to @uri
*
* be parsed).
*/
SoupMessage *
-soup_message_new (const char *method, const char *uri)
+soup_message_new (const char *method, const char *uri_string)
{
SoupMessage *msg;
- SoupContext *ctx;
+ SoupUri *uri;
- ctx = soup_context_get (uri);
- if (!ctx)
+ uri = soup_uri_new (uri_string);
+ if (!uri)
return NULL;
msg = g_object_new (SOUP_TYPE_MESSAGE, NULL);
msg->method = method ? method : SOUP_METHOD_GET;
- msg->priv->context = ctx;
+ msg->priv->uri = uri;
return msg;
}
*
* Creates a new empty #SoupMessage, which will connect to @uri
*
- * Return value: the new #SoupMessage (or %NULL if @uri is invalid)
+ * Return value: the new #SoupMessage
*/
SoupMessage *
soup_message_new_from_uri (const char *method, const SoupUri *uri)
{
SoupMessage *msg;
- SoupContext *ctx;
-
- ctx = soup_context_from_uri (uri);
- if (!ctx)
- return NULL;
msg = g_object_new (SOUP_TYPE_MESSAGE, NULL);
msg->method = method ? method : SOUP_METHOD_GET;
- msg->priv->context = ctx;
+ msg->priv->uri = soup_uri_copy (uri);
return msg;
}
static void
finished (SoupMessage *req)
{
- cleanup_message (req);
+ soup_message_io_cancel (req);
}
void
}
-static void
-cleanup_message (SoupMessage *req)
-{
- if (req->priv->io_data)
- soup_message_io_cancel (req);
-
- if (req->priv->connect_tag) {
- soup_context_cancel_connect (req->priv->connect_tag);
- req->priv->connect_tag = NULL;
- }
-
- soup_message_set_connection (req, NULL);
-}
-
-/**
- * soup_message_disconnect:
- * @msg: a #SoupMessage
- *
- * Utility function to close and unref the connection associated with
- * @msg if there was an error.
- **/
-void
-soup_message_disconnect (SoupMessage *msg)
-{
- if (msg->priv->connection) {
- soup_connection_disconnect (msg->priv->connection);
- soup_message_set_connection (msg, NULL);
- }
-}
-
/**
* soup_message_cancel:
* @msg: a #SoupMessage currently being processed.
*
* Cancel a running message, and issue completion callback with an
- * error code of %SOUP_STATUS_CANCELLED. If not requeued by the
+ * status code of %SOUP_STATUS_CANCELLED. If not requeued by the
* completion callback, the @msg will be destroyed.
*/
void
soup_message_cancel (SoupMessage *msg)
{
soup_message_set_status (msg, SOUP_STATUS_CANCELLED);
- soup_message_disconnect (msg);
soup_message_finished (msg);
}
void
soup_message_prepare (SoupMessage *req)
{
- if (req->priv->status != SOUP_MESSAGE_STATUS_IDLE) {
- cleanup_message (req);
+ soup_message_io_cancel (req);
+
+ if (req->priv->status != SOUP_MESSAGE_STATUS_IDLE)
req->priv->status = SOUP_MESSAGE_STATUS_IDLE;
- }
if (req->response.owner == SOUP_BUFFER_SYSTEM_OWNED)
g_free (req->response.body);
}
void
-soup_message_set_context (SoupMessage *msg, SoupContext *new_ctx)
+soup_message_set_uri (SoupMessage *msg, const SoupUri *new_uri)
{
g_return_if_fail (SOUP_IS_MESSAGE (msg));
- if (msg->priv->context && new_ctx) {
- const SoupUri *old, *new;
-
- old = soup_context_get_uri (msg->priv->context);
- new = soup_context_get_uri (new_ctx);
- if (strcmp (old->host, new->host) != 0)
- cleanup_message (msg);
- } else if (!new_ctx)
- cleanup_message (msg);
+ if (msg->priv->uri && new_uri) {
+ if (strcmp (msg->priv->uri->host, new_uri->host) != 0)
+ soup_message_io_cancel (msg);
+ } else if (!new_uri)
+ soup_message_io_cancel (msg);
- if (new_ctx)
- g_object_ref (new_ctx);
- if (msg->priv->context)
- g_object_unref (msg->priv->context);
-
- msg->priv->context = new_ctx;
+ if (msg->priv->uri)
+ soup_uri_free (msg->priv->uri);
+ msg->priv->uri = soup_uri_copy (new_uri);
}
const SoupUri *
{
g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
- return soup_context_get_uri (msg->priv->context);
-}
-
-void
-soup_message_set_connection (SoupMessage *msg, SoupConnection *conn)
-{
- if (conn)
- g_object_ref (conn);
- if (msg->priv->connection)
- g_object_unref (msg->priv->connection);
-
- msg->priv->connection = conn;
-}
-
-SoupConnection *
-soup_message_get_connection (SoupMessage *msg)
-{
- g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
-
- return msg->priv->connection;
+ return msg->priv->uri;
}
void
void soup_message_cancel (SoupMessage *req);
-void soup_message_disconnect (SoupMessage *req);
-
void soup_message_prepare (SoupMessage *req);
void soup_message_add_header (GHashTable *hash,
gboolean soup_initialized = FALSE;
-static guint max_connections = 10;
-
-static SoupContext *proxy_context = NULL;
-
static SoupSecurityPolicy ssl_security_level = SOUP_SECURITY_DOMESTIC;
/**
- * soup_set_proxy:
- * @context: a %SoupContext to use as the proxy context for all outgoing
- * connections.
- *
- * Use @context as the %SoupContext to connect to instead of the actual
- * destination specified in a SoupMessage. Messages will be routed through the
- * proxy host on their way to the actual specified destination. The URL for this
- * context should be of the form:
- * [http|https]://<USERNAME>:<PASSWORD>@<PROXYHOST>
- */
-void
-soup_set_proxy (SoupContext *context)
-{
- if (context)
- g_object_ref (context);
- if (proxy_context)
- g_object_unref (proxy_context);
-
- proxy_context = context;
-}
-
-/**
- * soup_get_proxy:
- *
- * Get the current proxy %SoupContext.
- *
- * Return value: the current proxy context.
- */
-SoupContext *
-soup_get_proxy (void)
-{
- return proxy_context;
-}
-
-/**
- * soup_set_connection_limit:
- * @max_conn: the number of connections.
- *
- * Set the maximum concurrent connection limit for outgoing requests.
- */
-void
-soup_set_connection_limit (guint max_conn)
-{
- max_connections = max_conn;
-}
-
-/**
- * soup_get_connection_limit:
- *
- * Return value: The maximum concurrent connection limit for outgoing requests.
- */
-guint
-soup_get_connection_limit (void)
-{
- return max_connections;
-}
-
-/**
* soup_set_security_policy:
* @policy: the %SoupSecurityPolicy to use.
*
static GSList *deny_tokens = NULL;
static void
-soup_config_connection_limit (gchar *key, gchar *value)
-{
- soup_set_connection_limit (MAX (atoi (value), 0));
-}
-
-static void
-soup_config_proxy_uri (gchar *key, gchar *value)
-{
- SoupContext *con = soup_context_get (value);
- if (con) soup_set_proxy (con);
-}
-
-static void
soup_config_security_policy (gchar *key, gchar *value)
{
switch (toupper (value [0])) {
gchar *key;
SoupConfigFunc func;
} soup_config_funcs [] = {
- { "connection-limit", soup_config_connection_limit },
- { "proxy-uri", soup_config_proxy_uri },
- { "proxy-url", soup_config_proxy_uri },
{ "security-policy", soup_config_security_policy },
{ "ssl-ca-file", soup_config_ssl_ca_file },
{ "ssl-ca-directory", soup_config_ssl_ca_directory },
{
/* Reset values */
if (soup_initialized) {
- soup_set_proxy (NULL);
- soup_set_connection_limit (0);
soup_set_security_policy (SOUP_SECURITY_DOMESTIC);
}
#define SOUP_MISC_H 1
#include <glib.h>
-#include <libsoup/soup-context.h>
#include <libsoup/soup-message.h>
#include <libsoup/soup-uri.h>
void soup_load_config (gchar *config_file);
-void soup_set_proxy (SoupContext *context);
-
-SoupContext *soup_get_proxy (void);
-
-void soup_set_connection_limit (guint max_conn);
-
-guint soup_get_connection_limit (void);
-
typedef enum {
SOUP_SECURITY_DOMESTIC = 1,
SOUP_SECURITY_EXPORT = 2,
#define RESPONSE_BLOCK_SIZE 8192
extern gboolean soup_initialized;
-extern GSList *soup_active_requests; /* CONTAINS: SoupMessage */
-extern GHashTable *soup_hosts; /* KEY: uri->host, VALUE: SoupHost */
extern SoupAuthorizeFn soup_auth_fn;
extern gpointer soup_auth_fn_user_data;
-typedef struct {
- gchar *host;
- GSList *connections; /* CONTAINS: SoupConnection */
- GHashTable *contexts; /* KEY: uri->path, VALUE: SoupContext */
-
- GHashTable *auth_realms; /* KEY: uri->path, VALUE: scheme:realm */
- GHashTable *auths; /* KEY: scheme:realm, VALUE: SoupAuth */
-
- GHashTable *ntlm_auths; /* KEY: SoupConnection, VALUE: SoupAuth */
-} SoupHost;
-
#ifdef HAVE_IPV6
#define soup_sockaddr_max sockaddr_in6
#else
#include "soup-message-queue.h"
#include "soup-private.h"
+typedef struct {
+ SoupUri *root_uri;
+ guint error;
+
+ GSList *connections; /* CONTAINS: SoupConnection */
+ guint num_conns;
+
+ GHashTable *auth_realms; /* path -> scheme:realm */
+ GHashTable *auths; /* scheme:realm -> SoupAuth */
+
+ GHashTable *ntlm_auths; /* SoupConnection -> SoupAuth */
+} SoupSessionHost;
+
struct SoupSessionPrivate {
+ SoupUri *proxy_uri;
+ guint max_conns, max_conns_per_host;
+
SoupMessageQueue *queue;
guint queue_idle_tag;
+ GHashTable *hosts; /* SoupUri -> SoupSessionHost */
+ GHashTable *conns; /* SoupConnection -> SoupSessionHost */
+ guint num_conns;
+
+ SoupSessionHost *proxy_host;
};
+static guint host_uri_hash (gconstpointer key);
+static gboolean host_uri_equal (gconstpointer v1, gconstpointer v2);
+
+static gboolean run_queue (SoupSession *session, gboolean try_pruning);
+
+#define SOUP_SESSION_MAX_CONNS_DEFAULT 10
+#define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 4
+
#define PARENT_TYPE G_TYPE_OBJECT
static GObjectClass *parent_class;
session->priv = g_new0 (SoupSessionPrivate, 1);
session->priv->queue = soup_message_queue_new ();
+ session->priv->hosts = g_hash_table_new (host_uri_hash,
+ host_uri_equal);
+ session->priv->conns = g_hash_table_new (NULL, NULL);
+
+ session->priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT;
+ session->priv->max_conns_per_host = SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT;
}
static void
SOUP_MAKE_TYPE (soup_session, SoupSession, class_init, init, PARENT_TYPE)
-
SoupSession *
soup_session_new (void)
{
return g_object_new (SOUP_TYPE_SESSION, NULL);
}
+SoupSession *
+soup_session_new_with_proxy (const SoupUri *proxy_uri)
+{
+ SoupSession *session;
+
+ session = soup_session_new ();
+ if (proxy_uri)
+ session->priv->proxy_uri = soup_uri_copy (proxy_uri);
+
+ return session;
+}
+
+SoupSession *
+soup_session_new_full (const SoupUri *proxy_uri,
+ guint max_conns, guint max_per_host)
+{
+ SoupSession *session;
+
+ session = soup_session_new_with_proxy (proxy_uri);
+ session->priv->max_conns = max_conns;
+ session->priv->max_conns_per_host = max_conns_per_host;
+
+ return session;
+}
+
+
+/* Hosts */
+static guint
+host_uri_hash (gconstpointer key)
+{
+ const SoupUri *uri = key;
+
+ return (uri->protocol << 16) + uri->port + g_str_hash (uri->host);
+}
+
+static gboolean
+host_uri_equal (gconstpointer v1, gconstpointer v2)
+{
+ const SoupUri *one = v1;
+ const SoupUri *two = v2;
+
+ if (one->protocol != two->protocol)
+ return FALSE;
+ if (one->port != two->port)
+ return FALSE;
+
+ return strcmp (one->host, two->host) == 0;
+}
+
+static SoupSessionHost *
+get_host_for_message (SoupSession *session, SoupMessage *msg)
+{
+ SoupSessionHost *host;
+ const SoupUri *source = soup_message_get_uri (msg);
+
+ host = g_hash_table_lookup (session->priv->hosts, source);
+ if (host)
+ return host;
+
+ host = g_new0 (SoupSessionHost, 1);
+ host->root_uri = g_new0 (SoupUri, 1);
+ host->root_uri->protocol = source->protocol;
+ host->root_uri->host = g_strdup (source->host);
+ host->root_uri->port = source->port;
+
+ g_hash_table_insert (session->priv->hosts, host->root_uri, host);
+ return host;
+}
+
+
+/* Authentication */
+
+static SoupAuth *
+lookup_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
+{
+ SoupSessionHost *host;
+ char *path, *dir;
+ const char *realm, *const_path;
+
+ if (proxy) {
+ host = session->priv->proxy_host;
+ const_path = "/";
+ } else {
+ host = get_host_for_message (session, msg);
+ const_path = soup_message_get_uri (msg)->path;
+ }
+ g_return_val_if_fail (host != NULL, NULL);
+
+ if (!host->auth_realms)
+ return NULL;
+
+ path = g_strdup (const_path);
+ dir = path;
+ do {
+ realm = g_hash_table_lookup (host->auth_realms, path);
+ if (realm)
+ break;
-/* Default handlers */
+ dir = strrchr (path, '/');
+ if (dir)
+ *dir = '\0';
+ } while (dir);
+
+ g_free (path);
+ if (realm)
+ return g_hash_table_lookup (host->auths, realm);
+ else
+ return NULL;
+}
+
+static void
+invalidate_auth (SoupSessionHost *host, SoupAuth *auth)
+{
+ char *realm;
+ gpointer key, value;
+
+ /* Try to just clean up the auth without removing it. */
+ if (soup_auth_invalidate (auth))
+ return;
+
+ /* Nope, need to remove it completely */
+ realm = g_strdup_printf ("%s:%s",
+ soup_auth_get_scheme_name (auth),
+ soup_auth_get_realm (auth));
+
+ if (g_hash_table_lookup_extended (host->auths, realm, &key, &value) &&
+ auth == (SoupAuth *)value) {
+ g_hash_table_remove (host->auths, realm);
+ g_free (key);
+ g_object_unref (auth);
+ }
+ g_free (realm);
+}
+
+static gboolean
+authenticate_auth (SoupAuth *auth, SoupMessage *msg)
+{
+ const SoupUri *uri = soup_message_get_uri (msg);
+
+ if (!uri->user && soup_auth_fn) {
+ (*soup_auth_fn) (soup_auth_get_scheme_name (auth),
+ (SoupUri *) uri,
+ soup_auth_get_realm (auth),
+ soup_auth_fn_user_data);
+ }
+
+ if (!uri->user)
+ return FALSE;
+
+ soup_auth_authenticate (auth, uri->user, uri->passwd);
+ return TRUE;
+}
+
+static gboolean
+update_auth_internal (SoupSession *session, SoupMessage *msg,
+ const GSList *headers, gboolean proxy,
+ gboolean prior_auth_failed)
+{
+ SoupSessionHost *host;
+ SoupAuth *new_auth, *prior_auth, *old_auth;
+ gpointer old_path, old_realm;
+ const SoupUri *msg_uri;
+ const char *path;
+ char *realm;
+ GSList *pspace, *p;
+
+ host = get_host_for_message (session, msg);
+ g_return_val_if_fail (host != NULL, FALSE);
+
+ /* Try to construct a new auth from the headers; if we can't,
+ * there's no way we'll be able to authenticate.
+ */
+ msg_uri = soup_message_get_uri (msg);
+ new_auth = soup_auth_new_from_header_list (headers, msg_uri->authmech);
+ if (!new_auth)
+ return FALSE;
+
+ /* See if this auth is the same auth we used last time */
+ prior_auth = lookup_auth (session, msg, proxy);
+ if (prior_auth &&
+ G_OBJECT_TYPE (prior_auth) == G_OBJECT_TYPE (new_auth) &&
+ !strcmp (soup_auth_get_realm (prior_auth),
+ soup_auth_get_realm (new_auth))) {
+ g_object_unref (new_auth);
+ if (prior_auth_failed) {
+ /* The server didn't like the username/password
+ * we provided before.
+ */
+ invalidate_auth (host, prior_auth);
+ return FALSE;
+ } else {
+ /* The user is trying to preauthenticate using
+ * information we already have, so there's nothing
+ * that needs to be done.
+ */
+ return TRUE;
+ }
+ }
+
+ if (!host->auth_realms) {
+ host->auth_realms = g_hash_table_new (g_str_hash, g_str_equal);
+ host->auths = g_hash_table_new (g_str_hash, g_str_equal);
+ }
+
+ /* Record where this auth realm is used */
+ realm = g_strdup_printf ("%s:%s",
+ soup_auth_get_scheme_name (new_auth),
+ soup_auth_get_realm (new_auth));
+ pspace = soup_auth_get_protection_space (new_auth, msg_uri);
+ for (p = pspace; p; p = p->next) {
+ path = p->data;
+ if (g_hash_table_lookup_extended (host->auth_realms, path,
+ &old_path, &old_realm)) {
+ g_hash_table_remove (host->auth_realms, old_path);
+ g_free (old_path);
+ g_free (old_realm);
+ }
+
+ g_hash_table_insert (host->auth_realms,
+ g_strdup (path), g_strdup (realm));
+ }
+ soup_auth_free_protection_space (new_auth, pspace);
+
+ /* Now, make sure the auth is recorded. (If there's a
+ * pre-existing auth, we keep that rather than the new one,
+ * since the old one might already be authenticated.)
+ */
+ old_auth = g_hash_table_lookup (host->auths, realm);
+ if (old_auth) {
+ g_free (realm);
+ g_object_unref (new_auth);
+ new_auth = old_auth;
+ } else
+ g_hash_table_insert (host->auths, realm, new_auth);
+
+ /* Try to authenticate if needed. */
+ if (!soup_auth_is_authenticated (new_auth))
+ return authenticate_auth (new_auth, msg);
+
+ return TRUE;
+}
static void
authorize_handler (SoupMessage *msg, gpointer user_data)
{
SoupSession *session = user_data;
- SoupContext *ctx;
-
- if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED)
- ctx = soup_get_proxy ();
- else
- ctx = msg->priv->context;
+ const GSList *headers;
+ gboolean proxy;
+
+ if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
+ headers = soup_message_get_header_list (msg->response_headers,
+ "Proxy-Authenticate");
+ proxy = TRUE;
+ } else {
+ headers = soup_message_get_header_list (msg->response_headers,
+ "WWW-Authenticate");
+ proxy = FALSE;
+ }
- if (soup_context_update_auth (ctx, msg))
+ if (update_auth_internal (session, msg, headers, proxy, TRUE))
soup_session_requeue_message (session, msg);
}
const char *new_loc;
const SoupUri *old_uri;
SoupUri *new_uri;
- SoupContext *new_ctx;
new_loc = soup_message_get_header (msg->response_headers, "Location");
if (!new_loc)
old_uri->passwd,
old_uri->authmech);
- new_ctx = soup_context_from_uri (new_uri);
+ soup_message_set_uri (msg, new_uri);
soup_uri_free (new_uri);
- if (!new_ctx)
- goto INVALID_REDIRECT;
-
- soup_message_set_context (msg, new_ctx);
- g_object_unref (new_ctx);
soup_session_requeue_message (session, msg);
return;
}
static void
-start_request (SoupConnection *conn, SoupMessage *req)
+add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
+{
+ const char *header = proxy ? "Proxy-Authorization" : "Authorization";
+ SoupAuth *auth;
+ char *token;
+
+ soup_message_remove_header (msg->request_headers, header);
+
+ auth = lookup_auth (session, msg, proxy);
+ if (!auth)
+ return;
+ if (!soup_auth_is_authenticated (auth) &&
+ !authenticate_auth (auth, msg))
+ return;
+
+ token = soup_auth_get_authorization (auth, msg);
+ if (token) {
+ soup_message_add_header (msg->request_headers, header, token);
+ g_free (token);
+ }
+}
+
+static void
+send_request (SoupSession *session, SoupMessage *req, SoupConnection *conn)
{
req->priv->status = SOUP_MESSAGE_STATUS_RUNNING;
+
+ add_auth (session, req, FALSE);
+ if (session->priv->proxy_uri)
+ add_auth (session, req, TRUE);
soup_connection_send_request (conn, req);
}
static void
-got_connection (SoupContext *ctx, guint status,
- SoupConnection *conn, gpointer user_data)
+find_oldest_connection (gpointer key, gpointer host, gpointer data)
{
- SoupMessage *req = user_data;
+ SoupConnection *conn = key, **oldest = data;
- req->priv->connect_tag = NULL;
- soup_message_set_connection (req, conn);
+ if (!oldest || (soup_connection_last_used (conn) <
+ soup_connection_last_used (*oldest)))
+ *oldest = conn;
+}
- if (status != SOUP_STATUS_OK) {
- soup_message_set_status (req, status);
- soup_message_finished (req);
+static gboolean
+try_prune_connection (SoupSession *session)
+{
+ SoupConnection *oldest = NULL;
+
+ g_hash_table_foreach (session->priv->conns, find_oldest_connection,
+ &oldest);
+ if (oldest) {
+ soup_connection_disconnect (oldest);
+ g_object_unref (oldest);
+ return TRUE;
+ } else
+ return FALSE;
+}
+
+static void connection_closed (SoupConnection *conn, SoupSession *session);
+
+static void
+cleanup_connection (SoupSession *session, SoupConnection *conn)
+{
+ SoupSessionHost *host =
+ g_hash_table_lookup (session->priv->conns, conn);
+
+ g_return_if_fail (host != NULL);
+
+ g_hash_table_remove (session->priv->conns, conn);
+ g_signal_handlers_disconnect_by_func (conn, connection_closed, session);
+ session->priv->num_conns--;
+
+ host->connections = g_slist_remove (host->connections, conn);
+ host->num_conns--;
+}
+
+static void
+connection_closed (SoupConnection *conn, SoupSession *session)
+{
+ cleanup_connection (session, conn);
+
+ /* Run the queue in case anyone was waiting for a connection
+ * to be closed.
+ */
+ run_queue (session, FALSE);
+}
+
+static void
+got_connection (SoupConnection *conn, guint status, gpointer user_data)
+{
+ SoupSession *session = user_data;
+ SoupSessionHost *host = g_hash_table_lookup (session->priv->conns, conn);
+
+ g_return_if_fail (host != NULL);
+
+ if (status == SOUP_STATUS_OK) {
+ host->connections = g_slist_prepend (host->connections, conn);
+ run_queue (session, FALSE);
+ return;
+ }
+
+ /* We failed */
+ cleanup_connection (session, conn);
+ g_object_unref (conn);
+
+ if (host->connections) {
+ /* Something went wrong this time, but we have at
+ * least one open connection to this host. So just
+ * leave the message in the queue so it can use that
+ * connection once it's free.
+ */
return;
}
- start_request (conn, req);
+ /* Flush any queued messages for this host */
+ host->error = status;
+ run_queue (session, FALSE);
+
+ if (status != SOUP_STATUS_CANT_RESOLVE &&
+ status != SOUP_STATUS_CANT_RESOLVE_PROXY) {
+ /* If the error was "can't resolve", then it's not likely
+ * to improve. But if it was something else, it may have
+ * been transient, so we clear the error so the user can
+ * try again later.
+ */
+ host->error = 0;
+ }
}
static gboolean
-idle_run_queue (gpointer user_data)
+run_queue (SoupSession *session, gboolean try_pruning)
{
- SoupSession *session = user_data;
SoupMessageQueueIter iter;
- SoupMessage *req;
+ SoupMessage *msg;
SoupConnection *conn;
+ SoupSessionHost *host;
+ gboolean skipped_any = FALSE, started_any = FALSE;
+ GSList *conns;
- session->priv->queue_idle_tag = 0;
+ /* FIXME: prefer CONNECTING messages */
+
+ try_again:
+ for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
+
+ if (!SOUP_MESSAGE_IS_STARTING (msg))
+ continue;
+
+ host = get_host_for_message (session, msg);
+
+ /* If the hostname is known to be bad, fail right away */
+ if (host->error) {
+ soup_message_set_status (msg, host->error);
+ soup_message_finished (msg);
+ }
+
+ /* If there is an idle connection, use it */
+ for (conns = host->connections; conns; conns = conns->next) {
+ if (!soup_connection_is_in_use (conns->data))
+ break;
+ }
+ if (conns) {
+ send_request (session, msg, conns->data);
+ started_any = TRUE;
+ continue;
+ }
- for (req = soup_message_queue_first (session->priv->queue, &iter); req;
- req = soup_message_queue_next (session->priv->queue, &iter)) {
+ if (msg->priv->status == SOUP_MESSAGE_STATUS_CONNECTING) {
+ /* We already started a connection for this
+ * message, so don't start another one.
+ */
+ continue;
+ }
- if (req->priv->status != SOUP_MESSAGE_STATUS_QUEUED)
+ /* If we have the max number of per-host connections
+ * or total connections open, we'll have to wait.
+ */
+ if (host->num_conns >= session->priv->max_conns_per_host)
+ continue;
+ else if (session->priv->num_conns >= session->priv->max_conns) {
+ /* In this case, closing an idle connection
+ * somewhere else would let us open one here.
+ */
+ skipped_any = TRUE;
continue;
+ }
- conn = soup_message_get_connection (req);
- if (conn && soup_connection_is_connected (conn)) {
- start_request (conn, req);
+ /* Otherwise, open a new connection */
+ if (session->priv->proxy_uri &&
+ host->root_uri->protocol == SOUP_PROTOCOL_HTTPS) {
+ conn = soup_connection_new_tunnel (
+ session->priv->proxy_uri, host->root_uri,
+ got_connection, session);
+ } else if (session->priv->proxy_uri) {
+ conn = soup_connection_new_proxy (
+ session->priv->proxy_uri,
+ got_connection, session);
} else {
- gpointer connect_tag;
+ conn = soup_connection_new (host->root_uri,
+ got_connection, session);
+ }
+
+ g_signal_connect (conn, "disconnected",
+ G_CALLBACK (connection_closed), session);
+ g_hash_table_insert (session->priv->conns, conn, host);
+ session->priv->num_conns++;
- req->priv->status = SOUP_MESSAGE_STATUS_CONNECTING;
- connect_tag =
- soup_context_get_connection (
- req->priv->context,
- got_connection, req);
+ /* Increment the host's connection count, but don't add
+ * this connection to the list yet, since it's not ready.
+ */
+ host->num_conns++;
- if (connect_tag)
- req->priv->connect_tag = connect_tag;
+ /* Mark the request as connecting, so we don't try to
+ * open another new connection for it next time around.
+ */
+ msg->priv->status = SOUP_MESSAGE_STATUS_CONNECTING;
+
+ started_any = TRUE;
+ }
+
+ if (try_pruning && skipped_any && !started_any) {
+ /* We didn't manage to start any message, but there is
+ * at least one message in the queue that could be
+ * sent if we pruned an idle connection from some
+ * other server.
+ */
+ if (try_prune_connection (session)) {
+ try_pruning = FALSE;
+ goto try_again;
}
}
+ return started_any;
+}
+
+static gboolean
+idle_run_queue (gpointer user_data)
+{
+ SoupSession *session = user_data;
+
+ session->priv->queue_idle_tag = 0;
+ run_queue (session, TRUE);
return FALSE;
}
GType soup_session_get_type (void);
-SoupSession *soup_session_new (void);
+SoupSession *soup_session_new_default (void);
+SoupSession *soup_session_new_with_proxy (const SoupUri *proxy_uri);
+SoupSession *soup_session_new_full (const SoupUri *proxy_uri,
+ guint max_conns,
+ guint max_per_host);
void soup_session_queue_message (SoupSession *session,
SoupMessage *req,
extern "C" {
#endif
-#include <libsoup/soup-context.h>
#include <libsoup/soup-message.h>
#include <libsoup/soup-misc.h>
#include <libsoup/soup-session.h>
identify_auth (SoupMessage *msg)
{
SoupAuth *auth;
- char *header;
+ const char *header;
int num;
- auth = soup_context_lookup_auth (msg->priv->context, msg);
- if (!auth || !soup_auth_is_authenticated (auth))
+ header = soup_message_get_header (msg->request_headers,
+ "Authorization");
+ if (!header)
return 0;
- header = soup_auth_get_authorization (auth, msg);
if (!g_ascii_strncasecmp (header, "Basic ", 6)) {
char *token;
int len;
num = 0;
}
- g_free (header);
return num;
}