[evolution-patches] soup auth patch



This fixes soup's auth handling, almost completely. I did it because I
wanted to fix 43332, 46724, 46725 and one other bug not in bugzilla, but
this patch actually fixes a lot more than that, because I already had a
lot of the logic (and the regression test) on the soup-refactoring
branch, and if I was going to fix a lot of things, I figured I might as
well fix everything.

Um... ok, so yes, it's a big patch. But it has a regression test! (And
I'm running connector with it now.)

-- Dan
Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/libsoup/ChangeLog,v
retrieving revision 1.317
diff -u -w -r1.317 ChangeLog
--- ChangeLog	29 Jul 2003 15:04:00 -0000	1.317
+++ ChangeLog	7 Aug 2003 18:24:22 -0000
@@ -1,3 +1,68 @@
+2003-08-07  Dan Winship  <danw ximian com>
+
+	* libsoup/soup-auth.c (soup_auth_lookup, soup_auth_set_context,
+	soup_auth_invalidate): These are all really SoupContext functions,
+	so move them to soup-context.c (and rename them appropriately).
+	(soup_auth_get_protection_space): New method to get the
+	"protection space" of an auth (paths where it is valid).
+	(soup_auth_invalidate): New method to try to un-authenticate an
+	auth (so we can keep the domain info cached even if the auth info
+	is wrong).
+	(basic_pspace_func): Basic protection space is all directories
+	below the current one.
+	(basic_invalidate_func): Clear the encoded username/password
+	(digest_pspace_func): Digest protection space is either the whole
+	server, or "what the domain parameter says" (though we don't deal
+	with cross-host domains).
+	(digest_invalidate_func): Return FALSE; bad digest auth info isn't
+	cacheable.
+	(digest_parse_func, digest_free): Set/free domain parameter
+	(ntlm_pspace): NTLM protection space is always the whole server.
+	(ntlm_invalidate): Clear the auth state.
+	(soup_auth_new_ntlm): Make this non-static
+	(SoupAuth): Replace the quad-state "status" field with an
+	"authenticated" boolean.
+	
+	* libsoup/soup-private.h (SoupHost): Replace the "valid_auths"
+	hash with separate "auth_realms" (path->realm) and "auths"
+	(realm->auth) hashes. Also add a "use_ntlm" flag.
+
+	* libsoup/soup-context.c (soup_context_unref): Update SoupHost
+	freeing code.
+	(connection_free): Don't the connection's auth, just free it.
+	(soup_context_lookup_auth): Formerly soup_auth_lookup, but now
+	does two-stage lookup (path->realm then realm->auth) and also
+	deals with NTLM hacks.
+	(soup_context_update_auth): Mostly formerly soup_auth_set_context,
+	but also large parts of authorize_handler. Updates the auth hashes
+	based on information from a 401 or 407 response. Does a better job
+	than authorize_handler did of not throwing away good information.
+	(soup_context_preauthenticate): New; fakes up auth info so that
+	requests will end up using authentication without the server
+	needing to return an error first.
+	(soup_context_authenticate_auth): Moved out of authorize_handler
+	so it can be used at request-sending time too, if we know that we
+	need it. (That way we can avoid requeuing the request if it isn't
+	going to be able to be authenticated.)
+	(soup_context_invalidate_auth): Sort of like the old
+	soup_auth_invalidate, but only destroys the auth data, while still
+	remembering the path->realm mapping.
+
+	* libsoup/soup-message.c (authorize_handler): Mostly moved into
+	soup_context_update_auth.
+	(maybe_validate_auth): Remove this; it was only useful because of
+	bugs elsewhere in the auth handling.
+	
+	* libsoup/soup-queue.c (soup_encode_http_auth): Update for
+	soup_context_lookup_auth. If the returned auth isn't
+	authenticated, call soup_context_authenticate_auth() on it.
+
+	* tests/auth-test.c: New (from soup-refactoring branch). Tests
+	that the Basic/Digest auth code does the right thing. (TODO: find
+	a good way to add NTLM tests too.)
+
+	* tests/Makefile.am (check_PROGRAMS): add auth-test
+
 2003-07-29  Dan Winship  <danw ximian com>
 
 	* configure.in: 1.99.25 ("Potato and Leek Soup")
Index: libsoup/soup-auth.c
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-auth.c,v
retrieving revision 1.21
diff -u -w -r1.21 soup-auth.c
--- libsoup/soup-auth.c	29 Jul 2003 15:04:01 -0000	1.21
+++ libsoup/soup-auth.c	7 Aug 2003 18:24:22 -0000
@@ -62,6 +62,21 @@
 	soup_header_param_destroy_hash (tokens);
 }
 
+static GSList *
+basic_pspace_func (SoupAuth *auth, const SoupUri *source_uri)
+{
+	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
 basic_init_func (SoupAuth *auth, const SoupUri *uri)
 {
@@ -71,6 +86,20 @@
 	user_pass = g_strdup_printf ("%s:%s", uri->user, uri->passwd);
 	basic->token = soup_base64_encode (user_pass, strlen (user_pass));
 	g_free (user_pass);
+
+	auth->authenticated = TRUE;
+}
+
+static gboolean
+basic_invalidate_func (SoupAuth *auth)
+{
+	SoupAuthBasic *basic = (SoupAuthBasic *) auth;
+
+	g_free (basic->token);
+	basic->token = NULL;
+	auth->authenticated = FALSE;
+
+	return TRUE;
 }
 
 static void
@@ -91,9 +120,12 @@
 	basic = g_new0 (SoupAuthBasic, 1);
 	auth = (SoupAuth *) basic;
 	auth->type = SOUP_AUTH_TYPE_BASIC;
+	auth->authenticated = FALSE;
 
 	auth->parse_func = basic_parse_func;
 	auth->init_func = basic_init_func;
+	auth->invalidate_func = basic_invalidate_func;
+	auth->pspace_func = basic_pspace_func;
 	auth->auth_func = basic_auth_func;
 	auth->free_func = basic_free;
 
@@ -126,6 +158,7 @@
 	char *nonce;
 	QOPType qop_options;
 	AlgorithmType algorithm;
+	char *domain;
 
 	/* These are generated by the client */
 	char *cnonce;
@@ -331,6 +364,7 @@
 	auth->realm = soup_header_param_copy_token (tokens, "realm");
 
 	digest->nonce = soup_header_param_copy_token (tokens, "nonce");
+	digest->domain = soup_header_param_copy_token (tokens, "domain");
 
 	tmp = soup_header_param_copy_token (tokens, "qop");
 	ptr = tmp;
@@ -356,6 +390,50 @@
 	soup_header_param_destroy_hash (tokens);
 }
 
+static GSList *
+digest_pspace_func (SoupAuth *auth, const SoupUri *source_uri)
+{
+	SoupAuthDigest *digest = (SoupAuthDigest *) auth;
+	GSList *space = NULL;
+	SoupUri *uri;
+	char *domain, *d, *lasts, *dir, *slash;
+
+	if (!digest->domain) {
+		/* If no domain directive, the protection space is the
+		 * whole server.
+		 */
+		return g_slist_prepend (NULL, g_strdup (""));
+	}
+
+	domain = g_strdup (digest->domain);
+	for (d = strtok_r (domain, " ", &lasts); d; d = strtok_r (NULL, " ", &lasts)) {
+		if (*d == '/')
+			dir = g_strdup (d);
+		else {
+			uri = soup_uri_new (d);
+			if (uri && uri->protocol == source_uri->protocol &&
+			    uri->port == source_uri->port &&
+			    !strcmp (uri->host, source_uri->host))
+				dir = g_strdup (uri->path);
+			else
+				dir = NULL;
+			if (uri)
+				soup_uri_free (uri);
+		}
+
+		if (dir) {
+			slash = strrchr (dir, '/');
+			if (slash && !slash[1])
+				*slash = '\0';
+
+			space = g_slist_prepend (space, dir);
+		}
+	}
+	g_free (domain);
+
+	return space;
+}
+
 static void
 digest_init_func (SoupAuth *auth, const SoupUri *uri)
 {
@@ -392,6 +470,17 @@
 	/* hexify A1 */
 	md5_final (&ctx, d);
 	digest_hex (d, digest->hex_a1);
+
+	auth->authenticated = TRUE;
+}
+
+static gboolean
+digest_invalidate_func (SoupAuth *auth)
+{
+	/* If we failed, we need to get a new nonce from the server
+	 * next time, so this can't be reused.
+	 */
+	return FALSE;
 }
 
 static void
@@ -400,6 +489,7 @@
 	SoupAuthDigest *digest = (SoupAuthDigest *) auth;
 
 	g_free (digest->user);
+	g_free (digest->domain);
 
 	g_free (digest->nonce);
 	g_free (digest->cnonce);
@@ -417,9 +507,12 @@
 
 	auth = (SoupAuth *) digest;
 	auth->type = SOUP_AUTH_TYPE_DIGEST;
+	auth->authenticated = FALSE;
 
 	auth->parse_func = digest_parse_func;
 	auth->init_func = digest_init_func;
+	auth->invalidate_func = digest_invalidate_func;
+	auth->pspace_func = digest_pspace_func;
 	auth->auth_func = digest_auth_func;
 	auth->free_func = digest_free;
 
@@ -452,15 +545,14 @@
 ntlm_auth (SoupAuth *sa, SoupMessage *msg)
 {
 	SoupAuthNTLM *auth = (SoupAuthNTLM *) sa;
-	gchar *ret;
+	char *ret;
 
-	if (sa->status == SOUP_AUTH_STATUS_PENDING)
+	if (!sa->authenticated)
 		return soup_ntlm_request ();
 
 	/* Otherwise, return the response; but only once */
 	ret = auth->response;
 	auth->response = NULL;
-
 	return ret;
 }
 
@@ -495,13 +587,20 @@
 	g_strstrip (auth->header);
 }
 
+static GSList *
+ntlm_pspace (SoupAuth *auth, const SoupUri *source_uri)
+{
+	/* The protection space is the whole server. */
+	return g_slist_prepend (NULL, g_strdup (""));
+}
+
 static void
 ntlm_init (SoupAuth *sa, const SoupUri *uri)
 {
 	SoupAuthNTLM *auth = (SoupAuthNTLM *) sa;
 	gchar *host, *domain, *nonce;
 
-	if (strlen (auth->header) < sizeof ("NTLM"))
+	if (!auth->header || strlen (auth->header) < sizeof ("NTLM"))
 		return;
 
 	if (auth->response)
@@ -525,14 +624,24 @@
 	g_free (host);
 	g_free (domain);
 
-	/* Set this now so that if the server returns 401,
-	 * soup will fail instead of looping (since that
-	 * probably means the password was incorrect).
-	 */
-	sa->status = SOUP_AUTH_STATUS_SUCCESSFUL;
+	g_free (auth->header);
+	auth->header = NULL;
+
+	sa->authenticated = TRUE;
+}
+
+static gboolean
+ntlm_invalidate (SoupAuth *sa)
+{
+	SoupAuthNTLM *auth = (SoupAuthNTLM *) sa;
 
+	g_free (auth->response);
+	auth->response = NULL;
 	g_free (auth->header);
 	auth->header = NULL;
+
+	sa->authenticated = FALSE;
+	return TRUE;
 }
 
 static void
@@ -545,16 +654,20 @@
 	g_free (auth);
 }
 
-static SoupAuth *
-ntlm_new (void)
+SoupAuth *
+soup_auth_new_ntlm (void)
 {
 	SoupAuthNTLM *auth;
 
 	auth = g_new0 (SoupAuthNTLM, 1);
 	auth->auth.type = SOUP_AUTH_TYPE_NTLM;
+	auth->auth.authenticated = FALSE;
+	auth->auth.realm = g_strdup ("");
 
 	auth->auth.parse_func = ntlm_parse;
 	auth->auth.init_func = ntlm_init;
+	auth->auth.invalidate_func = ntlm_invalidate;
+	auth->auth.pspace_func = ntlm_pspace;
 	auth->auth.auth_func = ntlm_auth;
 	auth->auth.free_func = ntlm_free;
 
@@ -566,92 +679,6 @@
  * Generic Authentication Interface
  */
 
-SoupAuth *
-soup_auth_lookup (SoupContext  *ctx)
-{
-	GHashTable *auth_hash = ctx->server->valid_auths;
-	SoupAuth *ret = NULL;
-	gchar *mypath, *dir;
-
-	if (!auth_hash) return NULL;
-
-	mypath = g_strdup (ctx->uri->path);
-	dir = mypath;
-
-        do {
-                ret = g_hash_table_lookup (auth_hash, mypath);
-                if (ret) break;
-
-                dir = strrchr (mypath, '/');
-                if (dir) *dir = '\0';
-        } while (dir);
-
-	g_free (mypath);
-	return ret;
-}
-
-void
-soup_auth_invalidate (SoupAuth *auth, SoupContext *ctx)
-{
-	SoupHost *server;
-	const SoupUri *uri;
-	SoupAuth *old_auth;
-	char *old_path;
-
-	g_return_if_fail (ctx != NULL);
-	g_return_if_fail (auth != NULL);
-
-	server = ctx->server;
-
-	if (!server->valid_auths)
-		return;
-
-	uri = soup_context_get_uri (ctx);
-	if (g_hash_table_lookup_extended (server->valid_auths,
-					  uri->path,
-					  (gpointer *) &old_path,
-					  (gpointer *) &old_auth)) {
-		g_hash_table_remove (server->valid_auths, old_path);
-		g_free (old_path);
-		soup_auth_free (old_auth);
-	}
-}
-
-void
-soup_auth_set_context (SoupAuth *auth, SoupContext *ctx)
-{
-	SoupHost *server;
-	SoupAuth *old_auth = NULL;
-	gchar *old_path;
-	const SoupUri *uri;
-
-	g_return_if_fail (ctx != NULL);
-	g_return_if_fail (auth != NULL);
-
-	server = ctx->server;
-	uri = soup_context_get_uri (ctx);
-
-	if (!server->valid_auths) {
-		server->valid_auths = g_hash_table_new (g_str_hash, 
-							g_str_equal);
-	}
-	else if (g_hash_table_lookup_extended (server->valid_auths, 
-					       uri->path,
-					       (gpointer *) &old_path,
-					       (gpointer *) &old_auth)) {
-		if (auth == old_auth)
-			return;
-
-		g_hash_table_remove (server->valid_auths, old_path);
-		g_free (old_path);
-		soup_auth_free (old_auth);
-	}
-
-	g_hash_table_insert (server->valid_auths,
-			     g_strdup (uri->path),
-			     auth);
-}
-
 typedef SoupAuth *(*SoupAuthNewFn) (void);
 
 typedef struct {
@@ -662,7 +689,7 @@
 
 static AuthScheme known_auth_schemes [] = {
 	{ "Basic",  soup_auth_new_basic,  0 },
-	{ "NTLM",   ntlm_new,             2 },
+	{ "NTLM",   soup_auth_new_ntlm,   2 },
 	{ "Digest", soup_auth_new_digest, 3 },
 	{ NULL }
 };
@@ -727,6 +754,14 @@
 	auth->init_func (auth, uri);
 }
 
+gboolean
+soup_auth_invalidate (SoupAuth *auth)
+{
+	g_return_val_if_fail (auth != NULL, FALSE);
+
+	return auth->invalidate_func (auth);
+}
+
 gchar *
 soup_auth_authorize (SoupAuth *auth, SoupMessage *msg)
 {
@@ -743,4 +778,23 @@
 
 	g_free (auth->realm);
 	auth->free_func (auth);
+}
+
+GSList *
+soup_auth_get_protection_space (SoupAuth *auth, const SoupUri *source_uri)
+{
+	g_return_val_if_fail (auth != NULL, NULL);
+	g_return_val_if_fail (source_uri != NULL, NULL);
+
+	return auth->pspace_func (auth, source_uri);
+}
+
+void
+soup_auth_free_protection_space (SoupAuth *auth, GSList *space)
+{
+	GSList *s;
+
+	for (s = space; s; s = s->next)
+		g_free (s->data);
+	g_slist_free (space);
 }
Index: libsoup/soup-auth.h
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-auth.h,v
retrieving revision 1.9
diff -u -w -r1.9 soup-auth.h
--- libsoup/soup-auth.h	15 Nov 2002 16:26:42 -0000	1.9
+++ libsoup/soup-auth.h	7 Aug 2003 18:24:22 -0000
@@ -18,19 +18,10 @@
 typedef   enum _SoupAuthStatus SoupAuthStatus;
 typedef struct _SoupAuth       SoupAuth;
 
-enum _SoupAuthStatus {
-	SOUP_AUTH_STATUS_INVALID = 0,
-	SOUP_AUTH_STATUS_PENDING,
-	SOUP_AUTH_STATUS_FAILED,
-	SOUP_AUTH_STATUS_SUCCESSFUL
-};
-
 struct _SoupAuth {
 	SoupAuthType  type;
-	gchar        *realm;
-
-	SoupAuthStatus status;
-	SoupMessage *controlling_msg;
+	char           *realm;
+	gboolean        authenticated;
 
 	void     (*parse_func)   (SoupAuth      *auth,
 				  const gchar   *header);
@@ -38,29 +29,36 @@
 	void     (*init_func)    (SoupAuth      *auth, 
 				  const SoupUri *uri);
 
+	gboolean (*invalidate_func) (SoupAuth      *auth);
+
 	char    *(*auth_func)    (SoupAuth      *auth, 
 				  SoupMessage   *message);
 
+	GSList  *(*pspace_func)     (SoupAuth      *auth,
+				     const SoupUri *source_uri);
+
 	void     (*free_func)    (SoupAuth      *auth);
 };
 
-SoupAuth *soup_auth_lookup                 (SoupContext   *ctx);
-
-void      soup_auth_set_context            (SoupAuth      *auth,
-					    SoupContext   *ctx);
-
-void      soup_auth_invalidate             (SoupAuth      *auth,
-					    SoupContext   *ctx);
-
 SoupAuth *soup_auth_new_from_header_list   (const SoupUri *uri,
 					    const GSList  *header);
 
+SoupAuth   *soup_auth_new_ntlm              (void);
+
 void      soup_auth_initialize             (SoupAuth      *auth,
 					    const SoupUri *uri);
 
+gboolean    soup_auth_invalidate            (SoupAuth      *auth);
+
 void      soup_auth_free                   (SoupAuth      *auth);
 
 gchar    *soup_auth_authorize              (SoupAuth      *auth, 
 					    SoupMessage   *msg);
+
+GSList     *soup_auth_get_protection_space  (SoupAuth      *auth,
+					     const SoupUri *source_uri);
+void        soup_auth_free_protection_space (SoupAuth      *auth,
+					     GSList        *space);
+
 
 #endif /* SOUP_AUTH_H */
Index: libsoup/soup-context.c
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-context.c,v
retrieving revision 1.50
diff -u -w -r1.50 soup-context.c
--- libsoup/soup-context.c	29 Jul 2003 15:04:01 -0000	1.50
+++ libsoup/soup-context.c	7 Aug 2003 18:24:22 -0000
@@ -193,13 +193,18 @@
 	ctx->refcnt++;
 }
 
-static gboolean
-remove_auth (gchar *path, SoupAuth *auth)
+static void
+free_path (gpointer path, gpointer realm, gpointer unused)
 {
 	g_free (path);
-	soup_auth_free (auth);
+	g_free (realm);
+}
 
-	return TRUE;
+static void
+free_auth (gpointer realm, gpointer auth, gpointer unused)
+{
+	g_free (realm);
+	soup_auth_free (auth);
 }
 
 /**
@@ -231,12 +236,15 @@
 			/* 
 			 * Free all cached SoupAuths
 			 */
-			if (serv->valid_auths) {
-				g_hash_table_foreach_remove (
-					serv->valid_auths,
-					(GHRFunc) remove_auth,
-					NULL);
-				g_hash_table_destroy (serv->valid_auths);
+			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, NULL);
+				g_hash_table_destroy (serv->auths);
 			}
 
 			g_hash_table_destroy (serv->contexts);
@@ -257,10 +265,8 @@
 	conn->server->connections =
 		g_slist_remove (conn->server->connections, conn);
 
-	if (conn->auth) {
-		soup_auth_invalidate (conn->auth, conn->context);
+	if (conn->auth)
 		soup_auth_free (conn->auth);
-	}
 
 	g_io_channel_unref (conn->channel);
 	soup_context_unref (conn->context);
@@ -769,4 +775,224 @@
 	for (i = idle_conns; i; i = i->next)
 		connection_free (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 (ctx != NULL, NULL);
+
+	if (ctx->server->use_ntlm && msg && msg->connection) {
+		if (!msg->connection->auth)
+			msg->connection->auth = soup_auth_new_ntlm ();
+		return msg->connection->auth;
+	}
+
+	if (!ctx->server->auth_realms)
+		return NULL;
+
+	path = g_strdup (ctx->uri->path);
+	dir = path;
+        do {
+                realm = g_hash_table_lookup (ctx->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->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->server;
+	SoupAuth *new_auth, *prior_auth, *old_auth;
+	gpointer old_path, old_realm;
+	const char *path;
+	char *realm;
+	GSList *pspace, *p;
+
+	if (server->use_ntlm && conn && conn->auth) {
+		if (conn->auth->authenticated) {
+			/* This is a "permission denied", not a
+			 * "password incorrect". There's nothing more
+			 * we can do.
+			 */
+			return FALSE;
+		}
+
+		/* Free the intermediate auth */
+		soup_auth_free (conn->auth);
+		conn->auth = NULL;
+	}
+
+	/* 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 (ctx->uri, headers);
+	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 && prior_auth->type == new_auth->type &&
+	    !strcmp (prior_auth->realm, new_auth->realm)) {
+		soup_auth_free (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 (new_auth->type == SOUP_AUTH_TYPE_NTLM) {
+		server->use_ntlm = TRUE;
+		if (conn) {
+			conn->auth = new_auth;
+			return soup_context_authenticate_auth (ctx, new_auth);
+		} else {
+			soup_auth_free (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 ("%d:%s", new_auth->type, new_auth->realm);
+	pspace = soup_auth_get_protection_space (new_auth, ctx->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);
+		soup_auth_free (new_auth);
+		new_auth = old_auth;
+	} else 
+		g_hash_table_insert (server->auths, realm, new_auth);
+
+	/* Try to authenticate if needed. */
+	if (!new_auth->authenticated)
+		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 (ctx != NULL, FALSE);
+	g_return_val_if_fail (msg != NULL, FALSE);
+
+	if (msg->errorcode == SOUP_ERROR_PROXY_UNAUTHORIZED) {
+		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, msg->connection, headers, TRUE);
+}
+
+void
+soup_context_preauthenticate (SoupContext *ctx, const char *header)
+{
+	GSList *headers;
+
+	g_return_if_fail (ctx != NULL);
+	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->uri;
+
+	if (!uri->user && soup_auth_fn) {
+		(*soup_auth_fn) (auth->type,
+				 (SoupUri *) uri,
+				 auth->realm, 
+				 soup_auth_fn_user_data);
+	}
+
+	if (!uri->user)
+		return FALSE;
+
+	soup_auth_initialize (auth, uri);
+	return TRUE;
+}
+
+void
+soup_context_invalidate_auth (SoupContext *ctx, SoupAuth *auth)
+{
+	char *realm;
+	gpointer key, value;
+
+	g_return_if_fail (ctx != NULL);
+	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 ("%d:%s", auth->type, auth->realm);
+
+	if (g_hash_table_lookup_extended (ctx->server->auths, realm,
+					  &key, &value) &&
+	    auth == (SoupAuth *)value) {
+		g_hash_table_remove (ctx->server->auths, realm);
+		g_free (key);
+		soup_auth_free (auth);
+	}
+	g_free (realm);
 }
Index: libsoup/soup-context.h
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-context.h,v
retrieving revision 1.17
diff -u -w -r1.17 soup-context.h
--- libsoup/soup-context.h	9 May 2003 15:25:31 -0000	1.17
+++ libsoup/soup-context.h	7 Aug 2003 18:24:22 -0000
@@ -65,4 +65,8 @@
 
 void           soup_connection_purge_idle     (void);
 
+
+void           soup_context_preauthenticate   (SoupContext          *ctx,
+					       const char           *header);
+					  
 #endif /*SOUP_CONTEXT_H*/
Index: libsoup/soup-message.c
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-message.c,v
retrieving revision 1.59
diff -u -w -r1.59 soup-message.c
--- libsoup/soup-message.c	29 Jul 2003 15:04:01 -0000	1.59
+++ libsoup/soup-message.c	7 Aug 2003 18:24:22 -0000
@@ -8,6 +8,8 @@
  * Copyright (C) 2000-2002, Ximian, Inc.
  */
 
+#include <string.h>
+
 #include "soup-auth.h"
 #include "soup-error.h"
 #include "soup-message.h"
@@ -623,157 +625,19 @@
 }
 
 static void
-maybe_validate_auth (SoupMessage *msg, gpointer user_data)
-{
-	gboolean proxy = GPOINTER_TO_INT (user_data);
-	int auth_failure;
-	SoupContext *ctx;
-	SoupAuth *auth;
-
-	if (proxy) {
-		ctx = soup_get_proxy ();
-		auth_failure = SOUP_ERROR_PROXY_UNAUTHORIZED; /* 407 */
-	}
-	else {
-		ctx = msg->context;
-		auth_failure = SOUP_ERROR_UNAUTHORIZED; /* 401 */
-	}
-
-	auth = soup_auth_lookup (ctx);
-	if (!auth)
-		return;
-
-	if (msg->errorcode == auth_failure) {
-		/* Pass through */
-	}
-	else if (msg->errorclass == SOUP_ERROR_CLASS_SERVER_ERROR) {
-		/* 
-		 * We have no way of knowing whether our auth is any good
-		 * anymore, so just invalidate it and start from the
-		 * beginning next time.
-		 */
-		soup_auth_invalidate (auth, ctx);
-	}
-	else {
-		auth->status = SOUP_AUTH_STATUS_SUCCESSFUL;
-		auth->controlling_msg = NULL;
-	}
-} /* maybe_validate_auth */
-
-static void 
 authorize_handler (SoupMessage *msg, gboolean proxy)
 {
-	const GSList *vals;
-	SoupAuth *auth;
 	SoupContext *ctx;
-	const SoupUri *uri;
-
-	if (msg->connection->auth &&
-	    msg->connection->auth->status == SOUP_AUTH_STATUS_SUCCESSFUL)
-		goto THROW_CANT_AUTHENTICATE;
 
 	ctx = proxy ? soup_get_proxy () : msg->context;
-	uri = soup_context_get_uri (ctx);
-
-	vals = soup_message_get_header_list (msg->response_headers, 
-					     proxy ? 
-					             "Proxy-Authenticate" : 
-					             "WWW-Authenticate");
-	if (!vals) goto THROW_CANT_AUTHENTICATE;
-
-	auth = soup_auth_lookup (ctx);
-	if (auth && auth->type == SOUP_AUTH_TYPE_NTLM)
-		auth = NULL;
-
-	if (auth) {
-		g_assert (auth->status != SOUP_AUTH_STATUS_INVALID);
-
-		if (auth->status == SOUP_AUTH_STATUS_PENDING) {
-			if (auth->controlling_msg == msg) {
-				auth->status = SOUP_AUTH_STATUS_FAILED;
-				goto THROW_CANT_AUTHENTICATE;
-			}
-			else {
-				soup_message_requeue (msg);
-				return;
-			}
-		}
-		else if (auth->status == SOUP_AUTH_STATUS_FAILED ||
-			 auth->status == SOUP_AUTH_STATUS_SUCCESSFUL) {
-			/*
-			 * We've failed previously, but we'll give it
-			 * another go, or we've been successful
-			 * previously, but it's not working anymore.
-			 *
-			 * Invalidate the auth, so it's removed from the
-			 * hash and try it again as if we never had it
-			 * in the first place.
-			 */
-			soup_auth_invalidate (auth, ctx);
-			soup_message_requeue (msg);
-			return;
-		}
-	}
-
-	if (!auth) {
-		auth = soup_auth_new_from_header_list (uri, vals);
-
-		if (!auth) {
-			soup_message_set_error_full (
-				msg, 
-				proxy ? 
-			                SOUP_ERROR_CANT_AUTHENTICATE_PROXY : 
-			                SOUP_ERROR_CANT_AUTHENTICATE,
-				proxy ? 
-			                "Unknown authentication scheme "
-				        "required by proxy" :
-			                "Unknown authentication scheme "
-				        "required");
-			return;
-		}
-
-		auth->status = SOUP_AUTH_STATUS_PENDING;
-		auth->controlling_msg = msg;
-		soup_message_add_handler (msg, SOUP_HANDLER_PRE_BODY,
-					  maybe_validate_auth,
-					  GINT_TO_POINTER (proxy));
-	}
-
-	/*
-	 * Call registered authenticate handler
-	 */
-	if (!uri->user && soup_auth_fn)
-		(*soup_auth_fn) (auth->type,
-				 (SoupUri *) uri,
-				 auth->realm, 
-				 soup_auth_fn_user_data);
-
-	if (!uri->user) {
-		soup_auth_free (auth);
-		goto THROW_CANT_AUTHENTICATE;
-	}
-
-	/*
-	 * Initialize with auth data (possibly returned from 
-	 * auth callback).
-	 */
-	soup_auth_initialize (auth, uri);
-
-	if (auth->type == SOUP_AUTH_TYPE_NTLM &&
-	    auth->status == SOUP_AUTH_STATUS_SUCCESSFUL)
-		msg->connection->auth = auth;
-	else
-		soup_auth_set_context (auth, ctx);
-
+	if (soup_context_update_auth (ctx, msg))
 	soup_message_requeue (msg);
-
-        return;
-
- THROW_CANT_AUTHENTICATE:
+	else {
 	soup_message_set_error (msg, 
 				proxy ? 
 			                SOUP_ERROR_CANT_AUTHENTICATE_PROXY : 
 			                SOUP_ERROR_CANT_AUTHENTICATE);
+	}
 }
 
 static void 
Index: libsoup/soup-private.h
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-private.h,v
retrieving revision 1.55
diff -u -w -r1.55 soup-private.h
--- libsoup/soup-private.h	11 Jun 2003 18:11:30 -0000	1.55
+++ libsoup/soup-private.h	7 Aug 2003 18:24:22 -0000
@@ -48,7 +48,9 @@
 	gchar      *host;
 	GSList     *connections;        /* CONTAINS: SoupConnection */
 	GHashTable *contexts;           /* KEY: uri->path, VALUE: SoupContext */
-	GHashTable *valid_auths;        /* KEY: uri->path, VALUE: SoupAuth */
+	gboolean    use_ntlm;
+	GHashTable *auth_realms;      /* KEY: uri->path, VALUE: scheme:realm */
+	GHashTable *auths;            /* KEY: scheme:realm, VALUE: SoupAuth */
 } SoupHost;
 
 struct _SoupSocket {
@@ -124,6 +126,20 @@
 	SoupServerMessage *server_msg;
 };
 
+/* from soup-context.c */
+
+SoupAuth   *soup_context_lookup_auth       (SoupContext    *ctx,
+					    SoupMessage    *msg);
+
+gboolean    soup_context_update_auth       (SoupContext    *ctx,
+					    SoupMessage    *msg);
+
+gboolean    soup_context_authenticate_auth (SoupContext    *ctx,
+					    SoupAuth       *auth);
+
+void        soup_context_invalidate_auth   (SoupContext    *ctx,
+					    SoupAuth       *auth);
+					  
 /* from soup-message.c */
 
 void     soup_message_issue_callback (SoupMessage      *req);
Index: libsoup/soup-queue.c
===================================================================
RCS file: /cvs/gnome/libsoup/libsoup/soup-queue.c,v
retrieving revision 1.86
diff -u -w -r1.86 soup-queue.c
--- libsoup/soup-queue.c	23 Jun 2003 15:52:33 -0000	1.86
+++ libsoup/soup-queue.c	7 Aug 2003 18:24:22 -0000
@@ -295,23 +295,22 @@
 
 	ctx = proxy_auth ? soup_get_proxy () : msg->context;
 
-	if (msg->connection->auth)
-		auth = msg->connection->auth;
-	else
-		auth = soup_auth_lookup (ctx);
+	auth = soup_context_lookup_auth (ctx, msg);
+	if (!auth)
+		return;
+	if (!auth->authenticated &&
+	    !soup_context_authenticate_auth (ctx, auth))
+		return;
 
-	if (auth) {
 		token = soup_auth_authorize (auth, msg);
 		if (token) {
-			g_string_sprintfa (header, 
-					   "%s: %s\r\n",
+		g_string_sprintfa (header, "%s: %s\r\n",
 					   proxy_auth ? 
 					   	"Proxy-Authorization" : 
 					   	"Authorization",
 					   token);
 			g_free (token);
 		}
- 	}
 }
 
 struct SoupUsedHeaders {
Index: tests/.cvsignore
===================================================================
RCS file: /cvs/gnome/libsoup/tests/.cvsignore,v
retrieving revision 1.2
diff -u -w -r1.2 .cvsignore
--- tests/.cvsignore	13 Nov 2002 17:06:58 -0000	1.2
+++ tests/.cvsignore	7 Aug 2003 18:24:22 -0000
@@ -2,3 +2,4 @@
 Makefile.in
 get
 timeserver
+auth-test
Index: tests/Makefile.am
===================================================================
RCS file: /cvs/gnome/libsoup/tests/Makefile.am,v
retrieving revision 1.5
diff -u -w -r1.5 Makefile.am
--- tests/Makefile.am	15 Nov 2002 16:18:04 -0000	1.5
+++ tests/Makefile.am	7 Aug 2003 18:24:22 -0000
@@ -2,12 +2,17 @@
 	-I$(top_srcdir)	\
 	$(GLIB_CFLAGS)
 
-noinst_PROGRAMS = get #timeserver
+noinst_PROGRAMS = get timeserver
 
 get_SOURCES = get.c
 
 get_LDADD = $(top_builddir)/libsoup/libsoup-2.0.la
 
-#timeserver_SOURCES = timeserver.c
+timeserver_SOURCES = timeserver.c
 
-#timeserver_LDADD = $(top_builddir)/libsoup/libsoup-2.0.la
+timeserver_LDADD = $(top_builddir)/libsoup/libsoup-2.0.la
+
+check_PROGRAMS = auth-test
+
+auth_test_SOURCES = auth-test.c
+auth_test_LDADD = $(top_builddir)/libsoup/libsoup-2.0.la
Index: tests/auth-test.c
===================================================================
RCS file: tests/auth-test.c
diff -N tests/auth-test.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/auth-test.c	7 Aug 2003 18:24:22 -0000
@@ -0,0 +1,273 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libsoup/soup.h"
+#include "libsoup/soup-auth.h"
+#include "libsoup/soup-private.h"
+
+int errors = 0;
+
+typedef struct {
+	const char *explanation;
+	const char *url;
+	const char *expected;
+	gboolean success;
+} SoupAuthTest;
+
+SoupAuthTest tests[] = {
+	{ "No auth available, should fail",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm1/index.txt";,
+	  "0", FALSE },
+
+	{ "Should fail with no auth, fail again with bad password, and give up",
+	  "http://user4:realm4 primates ximian com/~danw/soup-test/Basic/realm2/index.txt",
+	  "04", FALSE },
+
+	{ "Known realm, auth provided, so should succeed immediately",
+	  "http://user1:realm1 primates ximian com/~danw/soup-test/Basic/realm1/index.txt",
+	  "1", TRUE },
+
+	{ "Now should automatically reuse previous auth",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm1/index.txt";,
+	  "1", TRUE },
+
+	{ "Subdir should also automatically reuse auth",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm1/subdir/index.txt";,
+	  "1", TRUE },
+
+	{ "Subdir should retry last auth, but will fail this time",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm1/realm2/index.txt";,
+	  "1", FALSE },
+
+	{ "Now should use provided auth on first try",
+	  "http://user2:realm2 primates ximian com/~danw/soup-test/Basic/realm1/realm2/index.txt",
+	  "2", TRUE },
+
+	{ "Reusing last auth. Should succeed on first try",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm1/realm2/index.txt";,
+	  "2", TRUE },
+
+	{ "Reuse will fail, but 2nd try will succeed because it's a known realm",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm1/realm2/realm1/index.txt";,
+	  "21", TRUE },
+
+	{ "Should succeed on first try. (Known realm with cached password)",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm2/index.txt";,
+	  "2", TRUE },
+
+	{ "Fail once, then use password",
+	  "http://user3:realm3 primates ximian com/~danw/soup-test/Basic/realm3/index.txt",
+	  "03", TRUE },
+
+
+	{ "No auth available, should fail",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm1/index.txt";,
+	  "0", FALSE },
+
+	{ "Should fail with no auth, fail again with bad password, and give up",
+	  "http://user4:realm4 primates ximian com/~danw/soup-test/Digest/realm2/index.txt",
+	  "04", FALSE },
+
+	{ "Known realm, auth provided, so should succeed immediately",
+	  "http://user1:realm1 primates ximian com/~danw/soup-test/Digest/realm1/index.txt",
+	  "1", TRUE },
+
+	{ "Now should automatically reuse previous auth",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm1/index.txt";,
+	  "1", TRUE },
+
+	{ "Subdir should also automatically reuse auth",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm1/subdir/index.txt";,
+	  "1", TRUE },
+
+	{ "Subdir should retry last auth, but will fail this time",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm1/realm2/index.txt";,
+	  "1", FALSE },
+
+	{ "Now should use provided auth on first try",
+	  "http://user2:realm2 primates ximian com/~danw/soup-test/Digest/realm1/realm2/index.txt",
+	  "2", TRUE },
+
+	{ "Reusing last auth. Should succeed on first try",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm1/realm2/index.txt";,
+	  "2", TRUE },
+
+	{ "Should succeed on first try because of earlier domain directive",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm1/realm2/realm1/index.txt";,
+	  "1", TRUE },
+
+	{ "Should succeed on first try. (Known realm with cached password)",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm2/index.txt";,
+	  "2", TRUE },
+
+	{ "Fail once, then use password",
+	  "http://user3:realm3 primates ximian com/~danw/soup-test/Digest/realm3/index.txt",
+	  "03", TRUE },
+
+
+	{ "Make sure we haven't forgotten anything",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm1/index.txt";,
+	  "1", TRUE },
+
+	{ "Make sure we haven't forgotten anything",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm1/realm2/index.txt";,
+	  "2", TRUE },
+
+	{ "Make sure we haven't forgotten anything",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm1/realm2/realm1/index.txt";,
+	  "1", TRUE },
+
+	{ "Make sure we haven't forgotten anything",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm2/index.txt";,
+	  "2", TRUE },
+
+	{ "Make sure we haven't forgotten anything",
+	  "http://primates.ximian.com/~danw/soup-test/Basic/realm3/index.txt";,
+	  "3", TRUE },
+
+
+	{ "Make sure we haven't forgotten anything",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm1/index.txt";,
+	  "1", TRUE },
+
+	{ "Make sure we haven't forgotten anything",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm1/realm2/index.txt";,
+	  "2", TRUE },
+
+	{ "Make sure we haven't forgotten anything",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm1/realm2/realm1/index.txt";,
+	  "1", TRUE },
+
+	{ "Make sure we haven't forgotten anything",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm2/index.txt";,
+	  "2", TRUE },
+
+	{ "Make sure we haven't forgotten anything",
+	  "http://primates.ximian.com/~danw/soup-test/Digest/realm3/index.txt";,
+	  "3", TRUE }
+};
+int ntests = sizeof (tests) / sizeof (tests[0]);
+
+static const char *auths[] = {
+	"no password", "password 1",
+	"password 2", "password 3",
+	"intentionally wrong password",
+};
+
+static int
+identify_auth (SoupMessage *msg)
+{
+	SoupAuth *auth;
+	char *header;
+	int num;
+
+	auth = soup_context_lookup_auth (msg->context, msg);
+	if (!auth || !auth->authenticated)
+		return 0;
+
+	header = soup_auth_authorize (auth, msg);
+	if (!g_ascii_strncasecmp (header, "Basic ", 6)) {
+		char *token;
+		int len;
+
+		token = soup_base64_decode (header + 6, &len);
+		num = token[len - 1] - '0';
+		g_free (token);
+	} else {
+		const char *user;
+
+		user = strstr (header, "username=\"user");
+		if (user)
+			num = user[14] - '0';
+		else
+			num = 0;
+	}
+
+	g_free (header);
+	return num;
+}
+
+static void
+handler (SoupMessage *msg, gpointer data)
+{
+	char *expected = data;
+	int auth, exp;
+
+	auth = identify_auth (msg);
+
+	printf ("  %d %s (using %s)\n", msg->errorcode, msg->errorphrase,
+		auths[auth]);
+
+	if (*expected) {
+		exp = *expected - '0';
+		if (auth != exp) {
+			printf ("    expected %s!\n", auths[exp]);
+			errors++;
+		}
+		memmove (expected, expected + 1, strlen (expected));
+	} else {
+		printf ("    expected to be finished\n");
+		errors++;
+	}
+}
+
+int
+main (int argc, char **argv)
+{
+        SoupContext *ctx;
+        SoupMessage *msg;
+	char *expected;
+	int i;
+
+	for (i = 0; i < ntests; i++) {
+		printf ("Test %d: %s\n", i + 1, tests[i].explanation);
+
+		printf ("  GET %s\n", tests[i].url);
+		ctx = soup_context_get (tests[i].url);
+		if (!ctx) {
+			fprintf (stderr, "auth-test: Could not parse URI\n");
+			exit (1);
+		}
+
+		msg = soup_message_new (ctx, SOUP_METHOD_GET);
+
+		expected = g_strdup (tests[i].expected);
+		soup_message_add_error_code_handler (
+			msg, SOUP_ERROR_UNAUTHORIZED,
+			SOUP_HANDLER_PRE_BODY, handler, expected);
+		soup_message_add_error_code_handler (
+			msg, SOUP_ERROR_OK, SOUP_HANDLER_PRE_BODY,
+			handler, expected);
+		soup_message_send (msg);
+		if (msg->errorcode != SOUP_ERROR_CANT_AUTHENTICATE &&
+		    msg->errorcode != SOUP_ERROR_OK) {
+			printf ("  %d %s !\n", msg->errorcode,
+				msg->errorphrase);
+		}
+		if (*expected) {
+			printf ("  expected %d more round(s)\n",
+				strlen (expected));
+			errors++;
+		}
+		g_free (expected);
+
+		if (SOUP_ERROR_IS_SUCCESSFUL (msg->errorcode) !=
+		    tests[i].success) {
+			printf ("  expected %s\n",
+				tests[i].success ? "success" : "failure");
+			errors++;
+		}
+
+		printf ("\n");
+
+		soup_message_free (msg);
+		/* We don't free ctx because if we did, we'd end up
+		 * discarding the cached auth info at the end of each
+		 * test.
+		 */
+	}
+
+	printf ("\nauth-test: %d errors\n", errors);
+	return errors;
+}


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