[libsoup] soup-auth-manager: split out connection handling



commit 586aef885f64352bbd3c1b9e68ae2020f33d5833
Author: Dan Winship <danw gnome org>
Date:   Wed Dec 12 10:19:00 2012 +0100

    soup-auth-manager: split out connection handling
    
    Add a new SoupConnectionAuth class to help with connection tracking,
    and make SoupAuthNTLM a subclass of it. Allow a single SoupAuthNTLM to
    carry state information about multiple connections.
    
    Make SoupSession store the SoupConnection a SoupMessage is associated
    with on the message, and use that from SoupConnectionAuth rather than
    tracking sockets by hand like SoupAuthManager had previously done.
    Remove the connection tracking in SoupAuthManager, since it is no
    longer needed.

 docs/reference/Makefile.am              |    2 +-
 docs/reference/libsoup-2.4-sections.txt |    1 +
 libsoup/Makefile.am                     |    2 +
 libsoup/libsoup-2.4.sym                 |    1 +
 libsoup/soup-auth-manager.c             |  217 +++++++++++-------------------
 libsoup/soup-auth-ntlm.c                |  192 +++++++++++++++++-----------
 libsoup/soup-auth-ntlm.h                |    6 +-
 libsoup/soup-auth.c                     |   27 ++++
 libsoup/soup-auth.h                     |    8 +-
 libsoup/soup-connection-auth.c          |  173 ++++++++++++++++++++++++
 libsoup/soup-connection-auth.h          |   52 ++++++++
 libsoup/soup-message-private.h          |    5 +
 libsoup/soup-message.c                  |   13 ++
 libsoup/soup-session.c                  |    1 +
 14 files changed, 483 insertions(+), 217 deletions(-)
---
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index 74a6d92..17ae6ca 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -32,7 +32,7 @@ CFILE_GLOB=
 IGNORE_HFILES= soup.h soup-marshal.h soup-enum-types.h \
 	soup-message-private.h soup-session-private.h \
 	soup-auth-basic.h soup-auth-digest.h soup-auth-ntlm.h \
-	soup-connection.h soup-auth-manager.h soup-auth-manager-ntlm.h \
+	soup-connection.h soup-auth-manager.h soup-connection-auth.h \
 	soup-message-queue.h soup-path-map.h soup-gnome-features.h \
 	soup-proxy-resolver.h soup-proxy-resolver-gnome.h \
 	soup-proxy-resolver-static.h soup-directory-input-stream.h \
diff --git a/docs/reference/libsoup-2.4-sections.txt b/docs/reference/libsoup-2.4-sections.txt
index 963cbce..e1bbbff 100644
--- a/docs/reference/libsoup-2.4-sections.txt
+++ b/docs/reference/libsoup-2.4-sections.txt
@@ -536,6 +536,7 @@ soup_auth_get_info
 <SUBSECTION>
 soup_auth_authenticate
 soup_auth_is_authenticated
+soup_auth_is_ready
 <SUBSECTION>
 soup_auth_get_authorization
 soup_auth_get_protection_space
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index 7a9d6fc..5974744 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -119,6 +119,8 @@ libsoup_2_4_la_SOURCES =		\
 	soup-client-input-stream.c	\
 	soup-connection.h		\
 	soup-connection.c		\
+	soup-connection-auth.h		\
+	soup-connection-auth.c		\
 	soup-content-decoder.c		\
 	soup-content-processor.h	\
 	soup-content-processor.c	\
diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym
index 8ec30b1..521a5f7 100644
--- a/libsoup/libsoup-2.4.sym
+++ b/libsoup/libsoup-2.4.sym
@@ -53,6 +53,7 @@ soup_auth_get_type
 soup_auth_has_saved_password
 soup_auth_is_authenticated
 soup_auth_is_for_proxy
+soup_auth_is_ready
 soup_auth_new
 soup_auth_ntlm_get_type
 soup_auth_save_password
diff --git a/libsoup/soup-auth-manager.c b/libsoup/soup-auth-manager.c
index d284826..773291b 100644
--- a/libsoup/soup-auth-manager.c
+++ b/libsoup/soup-auth-manager.c
@@ -13,6 +13,7 @@
 
 #include "soup-auth-manager.h"
 #include "soup.h"
+#include "soup-connection-auth.h"
 #include "soup-marshal.h"
 #include "soup-message-private.h"
 #include "soup-message-queue.h"
@@ -36,13 +37,10 @@ G_DEFINE_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT,
 typedef struct {
 	SoupSession *session;
 	GPtrArray *auth_types;
-	gboolean has_ntlm;
+	gboolean auto_ntlm;
 
 	SoupAuth *proxy_auth;
 	GHashTable *auth_hosts;
-
-	GHashTable *connauths;
-	GHashTable *sockets_by_msg;
 } SoupAuthManagerPrivate;
 #define SOUP_AUTH_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerPrivate))
 
@@ -53,6 +51,8 @@ typedef struct {
 } SoupAuthHost;
 
 static void soup_auth_host_free (SoupAuthHost *host);
+static SoupAuth *record_auth_for_uri (SoupAuthManagerPrivate *priv,
+				      SoupURI *uri, SoupAuth *auth);
 
 static void
 soup_auth_manager_init (SoupAuthManager *manager)
@@ -64,10 +64,6 @@ soup_auth_manager_init (SoupAuthManager *manager)
 						  soup_uri_host_equal,
 						  NULL,
 						  (GDestroyNotify)soup_auth_host_free);
-
-	priv->connauths = g_hash_table_new_full (NULL, NULL,
-						 NULL, g_object_unref);
-	priv->sockets_by_msg = g_hash_table_new (NULL, NULL);
 }
 
 static void
@@ -81,9 +77,6 @@ soup_auth_manager_finalize (GObject *object)
 
 	g_clear_object (&priv->proxy_auth);
 
-	g_hash_table_destroy (priv->connauths);
-	g_hash_table_destroy (priv->sockets_by_msg);
-
 	G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object);
 }
 
@@ -133,7 +126,7 @@ soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type)
 	g_ptr_array_sort (priv->auth_types, auth_type_compare_func);
 
 	if (type == SOUP_TYPE_AUTH_NTLM)
-		priv->has_ntlm = TRUE;
+		priv->auto_ntlm = TRUE;
 
 	return TRUE;
 }
@@ -153,7 +146,7 @@ soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type)
 	for (i = 0; i < priv->auth_types->len; i++) {
 		if (priv->auth_types->pdata[i] == (gpointer)auth_class) {
 			if (type == SOUP_TYPE_AUTH_NTLM)
-				priv->has_ntlm = FALSE;
+				priv->auto_ntlm = FALSE;
 
 			g_ptr_array_remove_index (priv->auth_types, i);
 			return TRUE;
@@ -349,10 +342,9 @@ check_auth (SoupMessage *msg, SoupAuth *auth)
 }
 
 static SoupAuthHost *
-get_auth_host_for_message (SoupAuthManagerPrivate *priv, SoupMessage *msg)
+get_auth_host_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri)
 {
 	SoupAuthHost *host;
-	SoupURI *uri = soup_message_get_uri (msg);
 
 	host = g_hash_table_lookup (priv->auth_hosts, uri);
 	if (host)
@@ -375,14 +367,30 @@ soup_auth_host_free (SoupAuthHost *host)
 	g_slice_free (SoupAuthHost, host);
 }
 
+static gboolean
+make_auto_ntlm_auth (SoupAuthManagerPrivate *priv, SoupAuthHost *host)
+{
+	SoupAuth *auth;
+
+	if (!priv->auto_ntlm)
+		return FALSE;
+
+	auth = g_object_new (SOUP_TYPE_AUTH_NTLM,
+			     SOUP_AUTH_HOST, host->uri->host,
+			     NULL);
+	record_auth_for_uri (priv, host->uri, auth);
+	g_object_unref (auth);
+	return TRUE;
+}
+
 static SoupAuth *
 lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
 {
 	SoupAuthHost *host;
 	const char *path, *realm;
 
-	host = get_auth_host_for_message (priv, msg);
-	if (!host->auth_realms)
+	host = get_auth_host_for_uri (priv, soup_message_get_uri (msg));
+	if (!host->auth_realms && !make_auto_ntlm_auth (priv, host))
 		return NULL;
 
 	path = soup_message_get_uri (msg)->path;
@@ -395,7 +403,7 @@ lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
 		return NULL;
 }
 
-static gboolean
+static void
 authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
 		   SoupMessage *msg, gboolean prior_auth_failed,
 		   gboolean proxy, gboolean can_interact)
@@ -416,7 +424,7 @@ authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
 			uri = NULL;
 
 		if (!uri)
-			return FALSE;
+			return;
 	} else
 		uri = soup_message_get_uri (msg);
 
@@ -431,110 +439,19 @@ authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
 		g_signal_emit (manager, signals[AUTHENTICATE], 0,
 			       msg, auth, prior_auth_failed);
 	}
-
-	return soup_auth_is_authenticated (auth);
-}
-
-static void
-delete_connauth (SoupSocket *socket, gpointer user_data)
-{
-	SoupAuthManagerPrivate *priv = user_data;
-
-	g_hash_table_remove (priv->connauths, socket);
-	g_signal_handlers_disconnect_by_func (socket, delete_connauth, priv);
-}
-
-static SoupAuth *
-get_connauth (SoupAuthManagerPrivate *priv, SoupSocket *socket)
-{
-	SoupAuth *auth;
-
-	if (!priv->has_ntlm)
-		return NULL;
-
-	auth = g_hash_table_lookup (priv->connauths, socket);
-	if (!auth) {
-		auth = g_object_new (SOUP_TYPE_AUTH_NTLM, NULL);
-		g_hash_table_insert (priv->connauths, socket, auth);
-		g_signal_connect (socket, "disconnected",
-				  G_CALLBACK (delete_connauth), priv);
-	}
-
-	return auth;
-}
-
-static void
-unset_socket (SoupMessage *msg, gpointer user_data)
-{
-	SoupAuthManagerPrivate *priv = user_data;
-
-	g_hash_table_remove (priv->sockets_by_msg, msg);
-	g_signal_handlers_disconnect_by_func (msg, unset_socket, priv);
-}
-
-static void
-set_socket_for_msg (SoupAuthManagerPrivate *priv,
-		    SoupMessage *msg, SoupSocket *sock)
-{
-	if (!g_hash_table_lookup (priv->sockets_by_msg, msg)) {
-		g_signal_connect (msg, "finished",
-				  G_CALLBACK (unset_socket), priv);
-		g_signal_connect (msg, "restarted",
-				  G_CALLBACK (unset_socket), priv);
-	}
-	g_hash_table_insert (priv->sockets_by_msg, msg, sock);
 }
 
 static SoupAuth *
-get_connauth_for_msg (SoupAuthManagerPrivate *priv, SoupMessage *msg)
-{
-	SoupSocket *sock;
-
-	sock = g_hash_table_lookup (priv->sockets_by_msg, msg);
-	if (sock)
-		return g_hash_table_lookup (priv->connauths, sock);
-	else
-		return NULL;
-}
-
-static void
-auth_got_headers (SoupMessage *msg, gpointer manager)
+record_auth_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri,
+		     SoupAuth *auth)
 {
-	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
 	SoupAuthHost *host;
-	SoupAuth *auth, *prior_auth, *old_auth;
+	SoupAuth *old_auth;
 	const char *path;
 	char *auth_info, *old_auth_info;
 	GSList *pspace, *p;
-	gboolean prior_auth_failed = FALSE;
-
-	auth = get_connauth_for_msg (priv, msg);
-	if (auth) {
-		const char *header;
-
-		header = auth_header_for_message (msg);
-		if (header && soup_auth_update (auth, msg, header)) {
-			if (!soup_auth_is_authenticated (auth)) {
-				g_signal_emit (manager, signals[AUTHENTICATE], 0,
-					       msg, auth, FALSE);
-			}
-			return;
-		}
-	}
 
-	host = get_auth_host_for_message (priv, msg);
-
-	/* See if we used auth last time */
-	prior_auth = soup_message_get_auth (msg);
-	if (prior_auth && check_auth (msg, prior_auth)) {
-		auth = prior_auth;
-		if (!soup_auth_is_authenticated (auth))
-			prior_auth_failed = TRUE;
-	} else {
-		auth = create_auth (priv, msg);
-		if (!auth)
-			return;
-	}
+	host = get_auth_host_for_uri (priv, uri);
 	auth_info = soup_auth_get_info (auth);
 
 	if (!host->auth_realms) {
@@ -544,7 +461,7 @@ auth_got_headers (SoupMessage *msg, gpointer manager)
 	}
 
 	/* Record where this auth realm is used. */
-	pspace = soup_auth_get_protection_space (auth, soup_message_get_uri (msg));
+	pspace = soup_auth_get_protection_space (auth, uri);
 	for (p = pspace; p; p = p->next) {
 		path = p->data;
 		old_auth_info = soup_path_map_lookup (host->auth_realms, path);
@@ -566,16 +483,39 @@ auth_got_headers (SoupMessage *msg, gpointer manager)
 	old_auth = g_hash_table_lookup (host->auths, auth_info);
 	if (old_auth) {
 		g_free (auth_info);
-		if (auth != old_auth && auth != prior_auth) {
-			g_object_unref (auth);
-			auth = old_auth;
-		}
+		return old_auth;
+	} else {
+		g_hash_table_insert (host->auths, auth_info,
+				     g_object_ref (auth));
+		return auth;
+	}
+}
+
+static void
+auth_got_headers (SoupMessage *msg, gpointer manager)
+{
+	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
+	SoupAuth *auth, *prior_auth, *new_auth;
+	gboolean prior_auth_failed = FALSE;
+
+	/* See if we used auth last time */
+	prior_auth = soup_message_get_auth (msg);
+	if (prior_auth && check_auth (msg, prior_auth)) {
+		auth = g_object_ref (prior_auth);
+		if (!soup_auth_is_ready (auth, msg))
+			prior_auth_failed = TRUE;
 	} else {
-		g_hash_table_insert (host->auths, auth_info, auth);
+		auth = create_auth (priv, msg);
+		if (!auth)
+			return;
 	}
 
+	new_auth = record_auth_for_uri (priv, soup_message_get_uri (msg),
+					auth);
+	g_object_unref (auth);
+
 	/* If we need to authenticate, try to do it. */
-	authenticate_auth (manager, auth, msg,
+	authenticate_auth (manager, new_auth, msg,
 			   prior_auth_failed, FALSE, TRUE);
 }
 
@@ -585,16 +525,17 @@ auth_got_body (SoupMessage *msg, gpointer manager)
 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
 	SoupAuth *auth;
 
-	auth = get_connauth_for_msg (priv, msg);
-	if (auth && soup_auth_is_authenticated (auth)) {
-		SoupMessageFlags flags;
+	auth = lookup_auth (priv, msg);
+	if (auth && soup_auth_is_ready (auth, msg)) {
+		if (SOUP_IS_CONNECTION_AUTH (auth)) {
+			SoupMessageFlags flags;
+
+			flags = soup_message_get_flags (msg);
+			soup_message_set_flags (msg, flags & ~SOUP_MESSAGE_NEW_CONNECTION);
+		}
 
-		flags = soup_message_get_flags (msg);
-		soup_message_set_flags (msg, flags & ~SOUP_MESSAGE_NEW_CONNECTION);
-	} else
-		auth = lookup_auth (priv, msg);
-	if (auth && soup_auth_is_authenticated (auth))
 		soup_session_requeue_message (priv->session, msg);
+	}
 }
 
 static void
@@ -607,7 +548,7 @@ proxy_auth_got_headers (SoupMessage *msg, gpointer manager)
 	/* See if we used auth last time */
 	prior_auth = soup_message_get_proxy_auth (msg);
 	if (prior_auth && check_auth (msg, prior_auth)) {
-		if (!soup_auth_is_authenticated (prior_auth))
+		if (!soup_auth_is_ready (prior_auth, msg))
 			prior_auth_failed = TRUE;
 	}
 
@@ -628,7 +569,7 @@ proxy_auth_got_body (SoupMessage *msg, gpointer manager)
 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
 	SoupAuth *auth = priv->proxy_auth;
 
-	if (auth && soup_auth_is_authenticated (auth))
+	if (auth && soup_auth_is_ready (auth, msg))
 		soup_session_requeue_message (priv->session, msg);
 }
 
@@ -662,18 +603,20 @@ soup_auth_manager_request_started (SoupSessionFeature *feature,
 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
 	SoupAuth *auth;
 
-	set_socket_for_msg (priv, msg, socket);
 	auth = lookup_auth (priv, msg);
 	if (auth) {
-		if (!authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE))
+		authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE);
+		if (!soup_auth_is_ready (auth, msg))
 			auth = NULL;
-	} else
-		auth = get_connauth (priv, socket);
+	}
 	soup_message_set_auth (msg, auth);
 
 	auth = priv->proxy_auth;
-	if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE))
-		auth = NULL;
+	if (auth) {
+		authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE);
+		if (!soup_auth_is_ready (auth, msg))
+			auth = NULL;
+	}
 	soup_message_set_proxy_auth (msg, auth);
 }
 
diff --git a/libsoup/soup-auth-ntlm.c b/libsoup/soup-auth-ntlm.c
index 63f8c8b..367a3ca 100644
--- a/libsoup/soup-auth-ntlm.c
+++ b/libsoup/soup-auth-ntlm.c
@@ -44,10 +44,14 @@ typedef enum {
 
 typedef struct {
 	SoupNTLMState state;
-	char *username;
-	guchar nt_hash[21], lm_hash[21];
-	char *nonce, *domain;
+	char *nonce;
 	char *response_header;
+} SoupNTLMConnectionState;
+
+typedef struct {
+	char *username, *domain;
+	guchar nt_hash[21], lm_hash[21];
+	gboolean authenticated;
 
 #ifdef USE_NTLM_AUTH
 	/* Use Samba's 'winbind' daemon to support NTLM single-sign-on,
@@ -80,19 +84,15 @@ static void sso_ntlm_close (SoupAuthNTLMPrivate *priv);
  * Since: 2.34
  */
 
-G_DEFINE_TYPE (SoupAuthNTLM, soup_auth_ntlm, SOUP_TYPE_AUTH)
+G_DEFINE_TYPE (SoupAuthNTLM, soup_auth_ntlm, SOUP_TYPE_CONNECTION_AUTH)
 
 static void
 soup_auth_ntlm_init (SoupAuthNTLM *ntlm)
 {
-	SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (ntlm);
 #ifdef USE_NTLM_AUTH
+	SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (ntlm);
 	const char *username = NULL, *slash;
-#endif
-
-	priv->state = SOUP_NTLM_NEW;
 
-#ifdef USE_NTLM_AUTH
 	priv->sso_available = TRUE;
 	priv->fd_in = -1;
 	priv->fd_out = -1;
@@ -123,9 +123,6 @@ soup_auth_ntlm_finalize (GObject *object)
 	memset (priv->nt_hash, 0, sizeof (priv->nt_hash));
 	memset (priv->lm_hash, 0, sizeof (priv->lm_hash));
 
-	g_free (priv->nonce);
-	g_free (priv->response_header);
-
 #ifdef USE_NTLM_AUTH
 	sso_ntlm_close (priv);
 #endif
@@ -255,75 +252,89 @@ wrfinish:
 }
 #endif /* USE_NTLM_AUTH */
 
+static gpointer
+soup_auth_ntlm_create_connection_state (SoupConnectionAuth *auth)
+{
+	SoupNTLMConnectionState *conn;
+
+	conn = g_slice_new0 (SoupNTLMConnectionState);
+	conn->state = SOUP_NTLM_NEW;
+
+	return conn;
+}
+
+static void
+soup_auth_ntlm_free_connection_state (SoupConnectionAuth *auth,
+				      gpointer state)
+{
+	SoupNTLMConnectionState *conn = state;
+
+	g_free (conn->nonce);
+	g_free (conn->response_header);
+	g_slice_free (SoupNTLMConnectionState, conn);
+}
+
 static gboolean
-soup_auth_ntlm_update (SoupAuth *auth, SoupMessage *msg,
-		       GHashTable *auth_params)
+soup_auth_ntlm_update_connection (SoupConnectionAuth *auth, SoupMessage *msg,
+				  const char *auth_header, gpointer state)
 {
 	SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth);
-	GHashTableIter iter;
-	gpointer key, value;
-	char *header;
+	SoupNTLMConnectionState *conn = state;
 	gboolean success = TRUE;
 
-	if (priv->state > SOUP_NTLM_SENT_REQUEST) {
+	if (conn->state > SOUP_NTLM_SENT_REQUEST) {
 		/* We already authenticated, but then got another 401.
 		 * That means "permission denied", so don't try to
 		 * authenticate again.
 		 */
-		priv->state = SOUP_NTLM_FAILED;
+		conn->state = SOUP_NTLM_FAILED;
+
+		/* FIXME: we should only do this if the password never worked */
+		priv->authenticated = FALSE;
+
 		return FALSE;
 	}
 
-	/* The header will be something like "NTLM blahblahblah===",
-	 * which soup_auth_update() will parse as
-	 * "blahblahblah" = "==". Undo that.
-	 */
-	g_hash_table_iter_init (&iter, auth_params);
-	if (!g_hash_table_iter_next (&iter, &key, &value))
+	if (!g_str_has_prefix (auth_header, "NTLM "))
 		return FALSE;
-	if (value)
-		header = g_strdup_printf ("%s=%s", (char *)key, (char *)value);
-	else
-		header = g_strdup (key);
-
-	if (!soup_ntlm_parse_challenge (header, &priv->nonce, &priv->domain)) {
-		g_free (header);
-		priv->state = SOUP_NTLM_FAILED;
+
+	if (!soup_ntlm_parse_challenge (auth_header + 5, &conn->nonce, &priv->domain)) {
+		conn->state = SOUP_NTLM_FAILED;
 		return FALSE;
 	}
 
 #ifdef USE_NTLM_AUTH
-	if (priv->sso_available && priv->state == SOUP_NTLM_SENT_REQUEST) {
+	if (priv->sso_available && conn->state == SOUP_NTLM_SENT_REQUEST) {
 		char *input, *response;
 
 		/* Re-Initiate ntlm_auth process in case it was closed/killed abnormally */
 		if (!sso_ntlm_initiate (priv)) {
-			priv->state = SOUP_NTLM_SSO_FAILED;
+			conn->state = SOUP_NTLM_SSO_FAILED;
 			success = FALSE;
 			goto out;
 		}
 
-		input = g_strdup_printf ("TT %s\n", header);
-		response = sso_ntlm_response (priv, input, priv->state);
+		input = g_strdup_printf ("TT %s\n", auth_header + 5);
+		response = sso_ntlm_response (priv, input, conn->state);
 		sso_ntlm_close (priv);
 		g_free (input);
 
 		if (!response) {
-			priv->state = SOUP_NTLM_SSO_FAILED;
+			conn->state = SOUP_NTLM_SSO_FAILED;
 			success = FALSE;
 		} else if (!g_ascii_strcasecmp (response, "PW")) {
 			priv->sso_available = FALSE;
 			g_free (response);
-		} else
-			priv->response_header = response;
+		} else {
+			conn->response_header = response;
+			priv->authenticated = TRUE;
+		}
 	}
  out:
 #endif
 
-	g_free (header);
-
-	if (priv->state == SOUP_NTLM_SENT_REQUEST)
-		priv->state = SOUP_NTLM_RECEIVED_CHALLENGE;
+	if (conn->state == SOUP_NTLM_SENT_REQUEST)
+		conn->state = SOUP_NTLM_RECEIVED_CHALLENGE;
 
 	g_object_set (G_OBJECT (auth),
 		      SOUP_AUTH_REALM, priv->domain,
@@ -335,7 +346,16 @@ soup_auth_ntlm_update (SoupAuth *auth, SoupMessage *msg,
 static GSList *
 soup_auth_ntlm_get_protection_space (SoupAuth *auth, SoupURI *source_uri)
 {
-	g_return_val_if_reached (NULL);
+	char *space, *p;
+
+	space = g_strdup (source_uri->path);
+
+	/* Strip query and filename component */
+	p = strrchr (space, '/');
+	if (p && p != space && p[1])
+		*p = '\0';
+
+	return g_slist_prepend (NULL, space);
 }
 
 static void
@@ -343,59 +363,66 @@ soup_auth_ntlm_authenticate (SoupAuth *auth, const char *username,
 			     const char *password)
 {
 	SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth);
-	const char *slash, *domain;
+	const char *slash;
 
 	g_return_if_fail (username != NULL);
 	g_return_if_fail (password != NULL);
-	g_return_if_fail (priv->nonce != NULL);
+
+	if (priv->username)
+		g_free (priv->username);
+	if (priv->domain)
+		g_free (priv->domain);
 
 	slash = strpbrk (username, "\\/");
 	if (slash) {
-		if (priv->domain)
-			g_free (priv->domain);
-		domain = priv->domain = g_strndup (username, slash - username);
+		priv->domain = g_strndup (username, slash - username);
 		priv->username = g_strdup (slash + 1);
 	} else {
-		domain = "";
+		priv->domain = g_strdup ("");
 		priv->username = g_strdup (username);
 	}
 
 	soup_ntlm_nt_hash (password, priv->nt_hash);
 	soup_ntlm_lanmanager_hash (password, priv->lm_hash);
 
-	priv->response_header = soup_ntlm_response (priv->nonce,
-						    priv->username,
-						    priv->nt_hash,
-						    priv->lm_hash,
-						    NULL,
-						    domain);
-
-	g_free (priv->nonce);
-	priv->nonce = NULL;
+	priv->authenticated = TRUE;
 }
 
 static gboolean
 soup_auth_ntlm_is_authenticated (SoupAuth *auth)
 {
-	return SOUP_AUTH_NTLM_GET_PRIVATE (auth)->username != NULL &&
-		SOUP_AUTH_NTLM_GET_PRIVATE (auth)->response_header != NULL;
+	SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth);
+
+	return priv->authenticated;
+}
+
+static gboolean
+soup_auth_ntlm_is_connection_ready (SoupConnectionAuth *auth,
+				    SoupMessage        *msg,
+				    gpointer            state)
+{
+	SoupNTLMConnectionState *conn = state;
+
+	return conn->state != SOUP_NTLM_FAILED && conn->state != SOUP_NTLM_SSO_FAILED;
 }
 
 static char *
-soup_auth_ntlm_get_authorization (SoupAuth *auth, SoupMessage *msg)
+soup_auth_ntlm_get_connection_authorization (SoupConnectionAuth *auth,
+					     SoupMessage        *msg,
+					     gpointer            state)
 {
-	SoupAuthNTLMPrivate *priv =
-		SOUP_AUTH_NTLM_GET_PRIVATE (auth);
+	SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth);
+	SoupNTLMConnectionState *conn = state;
 	char *header = NULL;
 
-	switch (priv->state) {
+	switch (conn->state) {
 	case SOUP_NTLM_NEW:
 #ifdef USE_NTLM_AUTH
 		if (sso_ntlm_initiate (priv)) {
-			header = sso_ntlm_response (priv, "YR\n", priv->state);
+			header = sso_ntlm_response (priv, "YR\n", conn->state);
 			if (header) {
 				if (g_ascii_strcasecmp (header, "PW") != 0) {
-					priv->state = SOUP_NTLM_SENT_REQUEST;
+					conn->state = SOUP_NTLM_SENT_REQUEST;
 					break;
 				} else {
 					g_free (header);
@@ -411,12 +438,22 @@ soup_auth_ntlm_get_authorization (SoupAuth *auth, SoupMessage *msg)
 		 */
 #endif
 		header = soup_ntlm_request ();
-		priv->state = SOUP_NTLM_SENT_REQUEST;
+		conn->state = SOUP_NTLM_SENT_REQUEST;
 		break;
 	case SOUP_NTLM_RECEIVED_CHALLENGE:
-		header = priv->response_header;
-		priv->response_header = NULL;
-		priv->state = SOUP_NTLM_SENT_RESPONSE;
+		if (conn->response_header) {
+			header = conn->response_header;
+			conn->response_header = NULL;
+		} else {
+			header = soup_ntlm_response (conn->nonce,
+						     priv->username,
+						     priv->nt_hash,
+						     priv->lm_hash,
+						     NULL,
+						     priv->domain);
+		}
+		g_clear_pointer (&conn->nonce, g_free);
+		conn->state = SOUP_NTLM_SENT_RESPONSE;
 		break;
 #ifdef USE_NTLM_AUTH
 	case SOUP_NTLM_SSO_FAILED:
@@ -424,7 +461,7 @@ soup_auth_ntlm_get_authorization (SoupAuth *auth, SoupMessage *msg)
 		g_warning ("NTLM single-sign-on by using %s failed", NTLM_AUTH);
 		priv->sso_available = FALSE;
 		header = soup_ntlm_request ();
-		priv->state = SOUP_NTLM_SENT_REQUEST;
+		conn->state = SOUP_NTLM_SENT_REQUEST;
 		break;
 #endif
 	default:
@@ -438,6 +475,7 @@ static void
 soup_auth_ntlm_class_init (SoupAuthNTLMClass *auth_ntlm_class)
 {
 	SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_ntlm_class);
+	SoupConnectionAuthClass *connauth_class = SOUP_CONNECTION_AUTH_CLASS (auth_ntlm_class);
 	GObjectClass *object_class = G_OBJECT_CLASS (auth_ntlm_class);
 
 	g_type_class_add_private (auth_ntlm_class, sizeof (SoupAuthNTLMPrivate));
@@ -445,11 +483,15 @@ soup_auth_ntlm_class_init (SoupAuthNTLMClass *auth_ntlm_class)
 	auth_class->scheme_name = "NTLM";
 	auth_class->strength = 3;
 
-	auth_class->update = soup_auth_ntlm_update;
 	auth_class->get_protection_space = soup_auth_ntlm_get_protection_space;
 	auth_class->authenticate = soup_auth_ntlm_authenticate;
 	auth_class->is_authenticated = soup_auth_ntlm_is_authenticated;
-	auth_class->get_authorization = soup_auth_ntlm_get_authorization;
+
+	connauth_class->create_connection_state = soup_auth_ntlm_create_connection_state;
+	connauth_class->free_connection_state = soup_auth_ntlm_free_connection_state;
+	connauth_class->update_connection = soup_auth_ntlm_update_connection;
+	connauth_class->get_connection_authorization = soup_auth_ntlm_get_connection_authorization;
+	connauth_class->is_connection_ready = soup_auth_ntlm_is_connection_ready;
 
 	object_class->finalize = soup_auth_ntlm_finalize;
 
diff --git a/libsoup/soup-auth-ntlm.h b/libsoup/soup-auth-ntlm.h
index 7ed9bd3..43c4085 100644
--- a/libsoup/soup-auth-ntlm.h
+++ b/libsoup/soup-auth-ntlm.h
@@ -6,7 +6,7 @@
 #ifndef SOUP_AUTH_NTLM_H
 #define SOUP_AUTH_NTLM_H 1
 
-#include "soup-auth.h"
+#include "soup-connection-auth.h"
 
 #define SOUP_AUTH_NTLM(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLM))
 #define SOUP_AUTH_NTLM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLMClass))
@@ -15,12 +15,12 @@
 #define SOUP_AUTH_NTLM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLMClass))
 
 typedef struct {
-	SoupAuth parent;
+	SoupConnectionAuth parent;
 
 } SoupAuthNTLM;
 
 typedef struct {
-	SoupAuthClass parent_class;
+	SoupConnectionAuthClass parent_class;
 
 } SoupAuthNTLMClass;
 
diff --git a/libsoup/soup-auth.c b/libsoup/soup-auth.c
index 77fb45e..5ce92d5 100644
--- a/libsoup/soup-auth.c
+++ b/libsoup/soup-auth.c
@@ -459,6 +459,33 @@ soup_auth_get_authorization (SoupAuth *auth, SoupMessage *msg)
 }
 
 /**
+ * soup_auth_is_ready:
+ * @auth: a #SoupAuth
+ * @msg: a #SoupMessage
+ *
+ * Tests if @auth is ready to make a request for @msg with. For most
+ * auths, this is equivalent to soup_auth_is_authenticated(), but for
+ * some auth types (eg, NTLM), the auth may be sendable (eg, as an
+ * authentication request) even before it is authenticated.
+ *
+ * Return value: %TRUE if @auth is ready to make a request with.
+ *
+ * Since: 2.42
+ **/
+gboolean
+soup_auth_is_ready (SoupAuth    *auth,
+		    SoupMessage *msg)
+{
+	g_return_val_if_fail (SOUP_IS_AUTH (auth), TRUE);
+	g_return_val_if_fail (SOUP_IS_MESSAGE (msg), TRUE);
+
+	if (SOUP_AUTH_GET_CLASS (auth)->is_ready)
+		return SOUP_AUTH_GET_CLASS (auth)->is_ready (auth, msg);
+	else
+		return SOUP_AUTH_GET_CLASS (auth)->is_authenticated (auth);
+}
+
+/**
  * soup_auth_get_protection_space:
  * @auth: a #SoupAuth
  * @source_uri: the URI of the request that @auth was generated in
diff --git a/libsoup/soup-auth.h b/libsoup/soup-auth.h
index 8abda8e..824857e 100644
--- a/libsoup/soup-auth.h
+++ b/libsoup/soup-auth.h
@@ -44,8 +44,11 @@ typedef struct {
 
 	char *       (*get_authorization)    (SoupAuth      *auth,
 					      SoupMessage   *msg);
+
+	gboolean     (*is_ready)             (SoupAuth      *auth,
+					      SoupMessage   *msg);
+
 	/* Padding for future expansion */
-	void (*_libsoup_reserved1) (void);
 	void (*_libsoup_reserved2) (void);
 	void (*_libsoup_reserved3) (void);
 	void (*_libsoup_reserved4) (void);
@@ -76,6 +79,9 @@ void        soup_auth_authenticate          (SoupAuth      *auth,
 					     const char    *username,
 					     const char    *password);
 gboolean    soup_auth_is_authenticated      (SoupAuth      *auth);
+SOUP_AVAILABLE_IN_2_42
+gboolean    soup_auth_is_ready              (SoupAuth      *auth,
+					     SoupMessage   *msg);
 
 char       *soup_auth_get_authorization     (SoupAuth      *auth, 
 					     SoupMessage   *msg);
diff --git a/libsoup/soup-connection-auth.c b/libsoup/soup-connection-auth.c
new file mode 100644
index 0000000..fc32733
--- /dev/null
+++ b/libsoup/soup-connection-auth.c
@@ -0,0 +1,173 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-connection-auth.c: Abstract base class for hacky Microsoft
+ * connection-based auth mechanisms (NTLM and Negotiate)
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <string.h>
+
+#include "soup-connection-auth.h"
+#include "soup.h"
+#include "soup-connection.h"
+#include "soup-message-private.h"
+
+G_DEFINE_ABSTRACT_TYPE (SoupConnectionAuth, soup_connection_auth, SOUP_TYPE_AUTH)
+
+struct SoupConnectionAuthPrivate {
+	GHashTable *conns;
+};
+
+static void
+soup_connection_auth_init (SoupConnectionAuth *auth)
+{
+	auth->priv = G_TYPE_INSTANCE_GET_PRIVATE (auth, SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuthPrivate);
+
+	auth->priv->conns = g_hash_table_new (NULL, NULL);
+}
+
+static void connection_disconnected (SoupConnection *conn, gpointer user_data);
+
+static void
+soup_connection_auth_free_connection_state (SoupConnectionAuth *auth,
+					    SoupConnection     *conn,
+					    gpointer            state)
+{
+	g_signal_handlers_disconnect_by_func (conn, G_CALLBACK (connection_disconnected), auth);
+	SOUP_CONNECTION_AUTH_GET_CLASS (auth)->free_connection_state (auth, state);
+}
+
+static void
+connection_disconnected (SoupConnection *conn, gpointer user_data)
+{
+	SoupConnectionAuth *auth = user_data;
+	gpointer state;
+
+	state = g_hash_table_lookup (auth->priv->conns, conn);
+	g_hash_table_remove (auth->priv->conns, conn);
+	soup_connection_auth_free_connection_state (auth, conn, state);
+}
+
+static void
+soup_connection_auth_finalize (GObject *object)
+{
+	SoupConnectionAuth *auth = SOUP_CONNECTION_AUTH (object);
+	GHashTableIter iter;
+	gpointer conn, state;
+
+	g_hash_table_iter_init (&iter, auth->priv->conns);
+	while (g_hash_table_iter_next (&iter, &conn, &state)) {
+		soup_connection_auth_free_connection_state (auth, conn, state);
+		g_hash_table_iter_remove (&iter);
+	}
+	g_hash_table_destroy (auth->priv->conns);
+
+	G_OBJECT_CLASS (soup_connection_auth_parent_class)->finalize (object);
+}
+
+static gpointer
+get_connection_state_for_message (SoupConnectionAuth *auth, SoupMessage *msg)
+{
+	SoupConnection *conn;
+	gpointer state;
+
+	conn = soup_message_get_connection (msg);
+	state = g_hash_table_lookup (auth->priv->conns, conn);
+	if (state)
+		return state;
+
+	state = SOUP_CONNECTION_AUTH_GET_CLASS (auth)->create_connection_state (auth);
+	if (conn) {
+		g_signal_connect (conn, "disconnected",
+				  G_CALLBACK (connection_disconnected), auth);
+	}
+
+	g_hash_table_insert (auth->priv->conns, conn, state);
+	return state;
+}
+
+static gboolean
+soup_connection_auth_update (SoupAuth    *auth,
+			     SoupMessage *msg,
+			     GHashTable  *auth_params)
+{
+	SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth);
+	gpointer conn = get_connection_state_for_message (cauth, msg);
+	GHashTableIter iter;
+	GString *auth_header;
+	gpointer key, value;
+	gboolean result;
+
+	/* Recreate @auth_header out of @auth_params. If the
+	 * base64 data ended with 1 or more "="s, then it
+	 * will have been parsed as key=value. Otherwise
+	 * it will all have been parsed as key, and value
+	 * will be %NULL.
+	 */
+	auth_header = g_string_new (soup_auth_get_scheme_name (auth));
+	g_hash_table_iter_init (&iter, auth_params);
+	if (g_hash_table_iter_next (&iter, &key, &value)) {
+		if (value) {
+			g_string_append_printf (auth_header, " %s=%s",
+						(char *)key,
+						(char *)value);
+		} else {
+			g_string_append_printf (auth_header, " %s",
+						(char *)key);
+		}
+
+		if (g_hash_table_iter_next (&iter, &key, &value)) {
+			g_string_free (auth_header, TRUE);
+			return FALSE;
+		}
+	}
+
+	result = SOUP_CONNECTION_AUTH_GET_CLASS (auth)->
+		update_connection (cauth, msg, auth_header->str, conn);
+
+	g_string_free (auth_header, TRUE);
+	return result;
+}
+
+static char *
+soup_connection_auth_get_authorization (SoupAuth    *auth,
+					SoupMessage *msg)
+{
+	SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth);
+	gpointer conn = get_connection_state_for_message (cauth, msg);
+
+	return SOUP_CONNECTION_AUTH_GET_CLASS (auth)->
+		get_connection_authorization (cauth, msg, conn);
+}
+
+static gboolean
+soup_connection_auth_is_ready (SoupAuth    *auth,
+			       SoupMessage *msg)
+{
+	SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth);
+	gpointer conn = get_connection_state_for_message (cauth, msg);
+
+	return SOUP_CONNECTION_AUTH_GET_CLASS (auth)->
+		is_connection_ready (SOUP_CONNECTION_AUTH (auth), msg, conn);
+}
+
+static void
+soup_connection_auth_class_init (SoupConnectionAuthClass *connauth_class)
+{
+	SoupAuthClass *auth_class = SOUP_AUTH_CLASS (connauth_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (connauth_class);
+
+	g_type_class_add_private (connauth_class, sizeof (SoupConnectionAuthPrivate));
+
+	auth_class->update = soup_connection_auth_update;
+	auth_class->get_authorization = soup_connection_auth_get_authorization;
+	auth_class->is_ready = soup_connection_auth_is_ready;
+
+	object_class->finalize = soup_connection_auth_finalize;
+}
diff --git a/libsoup/soup-connection-auth.h b/libsoup/soup-connection-auth.h
new file mode 100644
index 0000000..251ca35
--- /dev/null
+++ b/libsoup/soup-connection-auth.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ */
+
+#ifndef SOUP_CONNECTION_AUTH_H
+#define SOUP_CONNECTION_AUTH_H 1
+
+#include <libsoup/soup-auth.h>
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_CONNECTION_AUTH            (soup_connection_auth_get_type ())
+#define SOUP_CONNECTION_AUTH(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuth))
+#define SOUP_CONNECTION_AUTH_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuthClass))
+#define SOUP_IS_CONNECTION_AUTH(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_CONNECTION_AUTH))
+#define SOUP_IS_CONNECTION_AUTH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_CONNECTION_AUTH))
+#define SOUP_CONNECTION_AUTH_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuthClass))
+
+typedef struct SoupConnectionAuthPrivate SoupConnectionAuthPrivate;
+
+typedef struct {
+	SoupAuth parent;
+
+	SoupConnectionAuthPrivate *priv;
+} SoupConnectionAuth;
+
+typedef struct {
+	SoupAuthClass parent_class;
+
+	gpointer  (*create_connection_state)      (SoupConnectionAuth *auth);
+	void      (*free_connection_state)        (SoupConnectionAuth *auth,
+						   gpointer            conn);
+
+	gboolean  (*update_connection)            (SoupConnectionAuth *auth,
+						   SoupMessage        *msg,
+						   const char         *auth_header,
+						   gpointer            conn);
+	char     *(*get_connection_authorization) (SoupConnectionAuth *auth,
+						   SoupMessage        *msg,
+						   gpointer            conn);
+	gboolean  (*is_connection_ready)          (SoupConnectionAuth *auth,
+						   SoupMessage        *msg,
+						   gpointer            conn);
+} SoupConnectionAuthClass;
+
+SOUP_AVAILABLE_IN_2_42
+GType soup_connection_auth_get_type (void);
+
+G_END_DECLS
+
+#endif /* SOUP_CONNECTION_AUTH_H */
diff --git a/libsoup/soup-message-private.h b/libsoup/soup-message-private.h
index 74b6e0d..de7cb7d 100644
--- a/libsoup/soup-message-private.h
+++ b/libsoup/soup-message-private.h
@@ -31,6 +31,7 @@ typedef struct {
 	SoupAddress       *addr;
 
 	SoupAuth          *auth, *proxy_auth;
+	SoupConnection    *connection;
 
 	GSList            *disabled_features;
 
@@ -138,4 +139,8 @@ GInputStream *soup_message_setup_body_istream (GInputStream *body_stream,
 void soup_message_set_soup_request (SoupMessage *msg,
 				    SoupRequest *req);
 
+SoupConnection *soup_message_get_connection (SoupMessage    *msg);
+void            soup_message_set_connection (SoupMessage    *msg,
+					     SoupConnection *conn);
+
 #endif /* SOUP_MESSAGE_PRIVATE_H */
diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c
index 7fe6db8..67b6e6f 100644
--- a/libsoup/soup-message.c
+++ b/libsoup/soup-message.c
@@ -1260,6 +1260,19 @@ soup_message_get_proxy_auth (SoupMessage *msg)
 	return SOUP_MESSAGE_GET_PRIVATE (msg)->proxy_auth;
 }
 
+SoupConnection *
+soup_message_get_connection (SoupMessage *msg)
+{
+	return SOUP_MESSAGE_GET_PRIVATE (msg)->connection;
+}
+
+void
+soup_message_set_connection (SoupMessage    *msg,
+			     SoupConnection *conn)
+{
+	SOUP_MESSAGE_GET_PRIVATE (msg)->connection = conn;
+}
+
 /**
  * soup_message_cleanup_response:
  * @msg: a #SoupMessage
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index 63e90b3..20e9232 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -1162,6 +1162,7 @@ soup_session_set_item_connection (SoupSession          *session,
 	}
 
 	item->conn = conn;
+	soup_message_set_connection (item->msg, conn);
 
 	if (item->conn) {
 		g_object_ref (item->conn);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]