libsoup r1045 - in trunk: . docs/reference libsoup tests



Author: danw
Date: Wed Jan 16 21:49:54 2008
New Revision: 1045
URL: http://svn.gnome.org/viewvc/libsoup?rev=1045&view=rev

Log:
	* libsoup/soup-auth-manager.c (authorize_handler, etc): Allow the
	session authenticate signal to be handled asynchronously, by
	pausing the message and then authenticating the auth later.
	(auth_type_compare_func): make this work. oops.
	(extract_challenge): plug leak

	* libsoup/soup-auth-manager-ntlm.c: Make this work async too.

	* libsoup/soup-headers.c (soup_header_parse_list):
	(soup_header_parse_param_list): plug leaks

	* tests/auth-test.c (do_async_auth_test): test async auth

	* docs/reference/client-howto.xml (Handling Authentication):
	mention async auth


Modified:
   trunk/ChangeLog
   trunk/docs/reference/client-howto.xml
   trunk/libsoup/soup-auth-manager-ntlm.c
   trunk/libsoup/soup-auth-manager.c
   trunk/libsoup/soup-headers.c
   trunk/libsoup/soup-session.c
   trunk/tests/auth-test.c

Modified: trunk/docs/reference/client-howto.xml
==============================================================================
--- trunk/docs/reference/client-howto.xml	(original)
+++ trunk/docs/reference/client-howto.xml	Wed Jan 16 21:49:54 2008
@@ -345,6 +345,19 @@
 message to fail (with status 401 or 407).
 </para>
 
+<para>
+If you need to handle authentication asynchronously (eg, to pop up a
+password dialog without recursively entering the main loop), you can
+do that as well. Just call <link
+linkend="soup-session-pause-message"><function>soup_session_pause_message</function></link>
+on the message before returning from the signal handler, and
+<function>g_object_ref</function> the <type>SoupAuth</type>. Then,
+later on, after calling <function>soup_auth_authenticate</function>
+(or deciding not to), call <link
+linkend="soup-session-unpause-message"><function>soup_session_unpause_message</function></link>
+to resume the paused message.
+</para>
+
 </refsect2>
 
 <refsect2>

Modified: trunk/libsoup/soup-auth-manager-ntlm.c
==============================================================================
--- trunk/libsoup/soup-auth-manager-ntlm.c	(original)
+++ trunk/libsoup/soup-auth-manager-ntlm.c	Wed Jan 16 21:49:54 2008
@@ -33,6 +33,9 @@
 	SoupSocket *socket;
 	SoupNTLMState state;
 	char *response_header;
+
+	char *nonce, *domain;
+	SoupAuth *auth;
 } SoupNTLMConnection;
 
 struct SoupAuthManagerNTLM {
@@ -72,6 +75,10 @@
 free_ntlm_connection (SoupNTLMConnection *conn)
 {
 	g_free (conn->response_header);
+	g_free (conn->nonce);
+	g_free (conn->domain);
+	if (conn->auth)
+		g_object_unref (conn->auth);
 	g_slice_free (SoupNTLMConnection, conn);
 }
 
@@ -161,11 +168,7 @@
 {
 	SoupAuthManagerNTLM *ntlm = user_data;
 	SoupNTLMConnection *conn;
-	SoupAuth *auth;
 	const char *val;
-	char *nonce;
-	const char *username = NULL, *password = NULL;
-	char *slash, *domain;
 
 	conn = get_connection_for_msg (ntlm, msg);
 	if (!conn)
@@ -189,38 +192,15 @@
 		goto done;
 	}
 
-	if (!soup_ntlm_parse_challenge (val, &nonce, &domain)) {
+	if (!soup_ntlm_parse_challenge (val, &conn->nonce, &conn->domain)) {
 		conn->state = SOUP_NTLM_FAILED;
 		goto done;
 	}
 
-	auth = soup_auth_ntlm_new (domain, soup_message_get_uri (msg)->host);
-	soup_session_emit_authenticate (ntlm->session, msg, auth, FALSE);
-	username = soup_auth_ntlm_get_username (auth);
-	password = soup_auth_ntlm_get_password (auth);
-	if (!username || !password) {
-		g_free (nonce);
-		g_free (domain);
-		g_object_unref (auth);
-		goto done;
-	}
-
-	slash = strpbrk (username, "\\/");
-	if (slash) {
-		g_free (domain);
-		domain = g_strdup (username);
-		slash = domain + (slash - username);
-		*slash = '\0';
-		username = slash + 1;
-	}
-
-	conn->response_header =
-		soup_ntlm_response (nonce, username, password, NULL, domain);
 	conn->state = SOUP_NTLM_RECEIVED_CHALLENGE;
-
-	g_free (domain);
-	g_free (nonce);
-	g_object_unref (auth);
+	conn->auth = soup_auth_ntlm_new (conn->domain,
+					 soup_message_get_uri (msg)->host);
+	soup_session_emit_authenticate (ntlm->session, msg, conn->auth, FALSE);
 
  done:
 	/* Remove the WWW-Authenticate headers so the session won't try
@@ -234,14 +214,41 @@
 {
 	SoupAuthManagerNTLM *ntlm = user_data;
 	SoupNTLMConnection *conn;
+	const char *username = NULL, *password = NULL;
+	char *slash, *domain;
 
 	conn = get_connection_for_msg (ntlm, msg);
-	if (!conn)
+	if (!conn || !conn->auth)
 		return;
 
-	if (conn->state == SOUP_NTLM_RECEIVED_CHALLENGE &&
-	    conn->response_header)
-		soup_session_requeue_message (ntlm->session, msg);
+	username = soup_auth_ntlm_get_username (conn->auth);
+	password = soup_auth_ntlm_get_password (conn->auth);
+	if (!username || !password)
+		goto done;
+
+	slash = strpbrk (username, "\\/");
+	if (slash) {
+		domain = g_strdup (username);
+		slash = domain + (slash - username);
+		*slash = '\0';
+		username = slash + 1;
+	} else
+		domain = conn->domain;
+
+	conn->response_header = soup_ntlm_response (conn->nonce,
+						    username, password,
+						    NULL, domain);
+	soup_session_requeue_message (ntlm->session, msg);
+
+done:
+	if (domain != conn->domain)
+		g_free (domain);
+	g_free (conn->domain);
+	conn->domain = NULL;
+	g_free (conn->nonce);
+	conn->nonce = NULL;
+	g_object_unref (conn->auth);
+	conn->auth = NULL;
 }
 
 static void

Modified: trunk/libsoup/soup-auth-manager.c
==============================================================================
--- trunk/libsoup/soup-auth-manager.c	(original)
+++ trunk/libsoup/soup-auth-manager.c	Wed Jan 16 21:49:54 2008
@@ -99,10 +99,10 @@
 static int
 auth_type_compare_func (gconstpointer a, gconstpointer b)
 {
-	SoupAuthClass *auth1 = (SoupAuthClass *)a;
-	SoupAuthClass *auth2 = (SoupAuthClass *)b;
+	SoupAuthClass **auth1 = (SoupAuthClass **)a;
+	SoupAuthClass **auth2 = (SoupAuthClass **)b;
 
-	return auth2->strength - auth1->strength;
+	return (*auth2)->strength - (*auth1)->strength;
 }
 
 void
@@ -180,8 +180,10 @@
 		    g_ascii_isspace (item[schemelen]))
 			break;
 	}
-	if (!i)
+	if (!i) {
+		soup_header_free_list (items);
 		return NULL;
+	}
 
 	/* The challenge extends from this item until the end, or until
 	 * the next item that has a space before an equals sign.
@@ -315,9 +317,10 @@
 	return soup_auth_is_authenticated (auth);
 }
 
-static gboolean
-update_auth (SoupAuthManager *manager, SoupMessage *msg)
+static void
+update_auth (SoupMessage *msg, gpointer user_data)
 {
+	SoupAuthManager *manager = user_data;
 	SoupAuthHost *host;
 	SoupAuth *auth, *prior_auth, *old_auth;
 	const char *path;
@@ -336,7 +339,7 @@
 	} else {
 		auth = create_auth (manager, msg);
 		if (!auth)
-			return FALSE;
+			return;
 	}
 	auth_info = soup_auth_get_info (auth);
 
@@ -378,13 +381,24 @@
 	}
 
 	/* If we need to authenticate, try to do it. */
-	return authenticate_auth (manager, auth, msg,
-				  prior_auth_failed, FALSE);
+	authenticate_auth (manager, auth, msg,
+			   prior_auth_failed, FALSE);
 }
 
-static gboolean
-update_proxy_auth (SoupAuthManager *manager, SoupMessage *msg)
+static void
+requeue_if_authenticated (SoupMessage *msg, gpointer user_data)
+{
+	SoupAuthManager *manager = user_data;
+	SoupAuth *auth = lookup_auth (manager, msg);
+
+	if (auth && soup_auth_is_authenticated (auth))
+		soup_session_requeue_message (manager->session, msg);
+}
+
+static void
+update_proxy_auth (SoupMessage *msg, gpointer user_data)
 {
+	SoupAuthManager *manager = user_data;
 	SoupAuth *prior_auth;
 	gboolean prior_auth_failed = FALSE;
 
@@ -398,29 +412,21 @@
 	if (!manager->proxy_auth) {
 		manager->proxy_auth = create_auth (manager, msg);
 		if (!manager->proxy_auth)
-			return FALSE;
+			return;
 	}
 
 	/* If we need to authenticate, try to do it. */
-	return authenticate_auth (manager, manager->proxy_auth, msg,
-				  prior_auth_failed, TRUE);
+	authenticate_auth (manager, manager->proxy_auth, msg,
+			   prior_auth_failed, TRUE);
 }
 
 static void
-authorize_handler (SoupMessage *msg, gpointer user_data)
+requeue_if_proxy_authenticated (SoupMessage *msg, gpointer user_data)
 {
 	SoupAuthManager *manager = user_data;
+	SoupAuth *auth = manager->proxy_auth;
 
-	if (update_auth (manager, msg))
-		soup_session_requeue_message (manager->session, msg);
-}
-
-static void
-proxy_authorize_handler (SoupMessage *msg, gpointer user_data)
-{
-	SoupAuthManager *manager = user_data;
-
-	if (update_proxy_auth (manager, msg))
+	if (auth && soup_auth_is_authenticated (auth))
 		soup_session_requeue_message (manager->session, msg);
 }
 
@@ -436,16 +442,20 @@
 		auth = NULL;
 	soup_message_set_auth (msg, auth);
 	soup_message_add_status_code_handler (
+		msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
+		G_CALLBACK (update_auth), manager);
+	soup_message_add_status_code_handler (
 		msg, "got_body", SOUP_STATUS_UNAUTHORIZED,
-		G_CALLBACK (authorize_handler), manager);
+		G_CALLBACK (requeue_if_authenticated), manager);
 
 	auth = manager->proxy_auth;
 	if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE))
 		auth = NULL;
 	soup_message_set_proxy_auth (msg, auth);
 	soup_message_add_status_code_handler (
+		msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED,
+		G_CALLBACK (update_proxy_auth), manager);
+	soup_message_add_status_code_handler (
 		msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED,
-		G_CALLBACK (proxy_authorize_handler), manager);
+		G_CALLBACK (requeue_if_proxy_authenticated), manager);
 }
-
-

Modified: trunk/libsoup/soup-headers.c
==============================================================================
--- trunk/libsoup/soup-headers.c	(original)
+++ trunk/libsoup/soup-headers.c	Wed Jan 16 21:49:54 2008
@@ -511,7 +511,7 @@
 
 	for (l = list; l; l = l->next)
 		g_free (l->data);
-	g_slist_free (l);
+	g_slist_free (list);
 }
 
 /**
@@ -609,6 +609,7 @@
 		g_hash_table_insert (params, item, value);
 	}
 
+	g_slist_free (list);
 	return params;
 }
 

Modified: trunk/libsoup/soup-session.c
==============================================================================
--- trunk/libsoup/soup-session.c	(original)
+++ trunk/libsoup/soup-session.c	Wed Jan 16 21:49:54 2008
@@ -257,6 +257,15 @@
 	 * emitted again, with @retrying set to %TRUE, which will
 	 * continue until you return without calling
 	 * soup_auth_authenticate() on @auth.
+	 *
+	 * Note that this may be emitted before @msg's body has been
+	 * fully read.
+	 *
+	 * If you call soup_session_pause_message() on @msg before
+	 * returning, then you can authenticate @auth asynchronously
+	 * (as long as you g_object_ref() it to make sure it doesn't
+	 * get destroyed), and then unpause @msg when you are ready
+	 * for it to continue.
 	 **/
 	signals[AUTHENTICATE] =
 		g_signal_new ("authenticate",

Modified: trunk/tests/auth-test.c
==============================================================================
--- trunk/tests/auth-test.c	(original)
+++ trunk/tests/auth-test.c	Wed Jan 16 21:49:54 2008
@@ -352,6 +352,148 @@
 	g_object_unref (msg);
 }
 
+/* Async auth test. We queue three requests to /Basic/realm1, ensuring
+ * that they are sent in order. The first and third ones will be
+ * paused from the authentication callback. The second will be allowed
+ * to fail. Shortly after the third one requests auth, we'll provide
+ * the auth and unpause the two remaining messages, allowing them to
+ * succeed.
+ */
+
+static void
+async_authenticate (SoupSession *session, SoupMessage *msg,
+		    SoupAuth *auth, gboolean retrying, gpointer data)
+{
+	SoupAuth **saved_auth = data;
+	int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
+
+	debug_printf (2, "  async_authenticate msg%d\n", id);
+
+	/* The session will try to authenticate msg3 *before* sending
+	 * it, because it already knows it's going to need the auth.
+	 * Ignore that.
+	 */
+	if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
+		debug_printf (2, "    (ignoring)\n");
+		return;
+	}
+
+	soup_session_pause_message (session, msg);
+	if (saved_auth)
+		*saved_auth = g_object_ref (auth);
+	g_main_loop_quit (loop);
+}
+
+static void
+async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+	int *finished = user_data;
+	int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
+
+	debug_printf (2, "  async_finished msg%d\n", id);
+
+	(*finished)++;
+	if (*finished == 2)
+		g_main_loop_quit (loop);
+}
+
+static void
+do_async_auth_test (const char *base_uri)
+{
+	SoupSession *session;
+	SoupMessage *msg1, *msg2, *msg3, msg2_bak;
+	guint auth_id;
+	char *uri;
+	SoupAuth *auth = NULL;
+	int finished = 0;
+
+	debug_printf (1, "\nTesting async auth:\n");
+
+	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
+
+	uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
+
+	msg1 = soup_message_new ("GET", uri);
+	g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
+	auth_id = g_signal_connect (session, "authenticate",
+				    G_CALLBACK (async_authenticate), &auth);
+	g_object_ref (msg1);
+	soup_session_queue_message (session, msg1, async_finished, &finished);
+	g_main_loop_run (loop);
+	g_signal_handler_disconnect (session, auth_id);
+
+	/* async_authenticate will pause msg1 and quit loop */
+
+	msg2 = soup_message_new ("GET", uri);
+	g_object_set_data (G_OBJECT (msg2), "id", GINT_TO_POINTER (2));
+	soup_session_send_message (session, msg2);
+
+	if (msg2->status_code == SOUP_STATUS_UNAUTHORIZED)
+		debug_printf (1, "  msg2 failed as expected\n");
+	else {
+		debug_printf (1, "  msg2 got wrong status! (%u)\n",
+			      msg2->status_code);
+		errors++;
+	}
+
+	/* msg2 should be done at this point; assuming everything is
+	 * working correctly, the session won't look at it again; we
+	 * ensure that if it does, it will crash the test program.
+	 */
+	memcpy (&msg2_bak, msg2, sizeof (SoupMessage));
+	memset (msg2, 0, sizeof (SoupMessage));
+
+	msg3 = soup_message_new ("GET", uri);
+	g_object_set_data (G_OBJECT (msg3), "id", GINT_TO_POINTER (3));
+	auth_id = g_signal_connect (session, "authenticate",
+				    G_CALLBACK (async_authenticate), NULL);
+	g_object_ref (msg3);
+	soup_session_queue_message (session, msg3, async_finished, &finished);
+	g_main_loop_run (loop);
+	g_signal_handler_disconnect (session, auth_id);
+
+	/* async_authenticate will pause msg3 and quit loop */
+
+	/* Now do the auth, and restart */
+	if (auth) {
+		soup_auth_authenticate (auth, "user1", "realm1");
+		soup_session_unpause_message (session, msg1);
+		soup_session_unpause_message (session, msg3);
+
+		g_main_loop_run (loop);
+
+		/* async_finished will quit the loop */
+	} else {
+		debug_printf (1, "  msg1 didn't get authenticate signal!\n");
+		errors++;
+	}
+
+	if (msg1->status_code == SOUP_STATUS_OK)
+		debug_printf (1, "  msg1 succeeded\n");
+	else {
+		debug_printf (1, "  msg1 FAILED! (%u %s)\n",
+			      msg1->status_code, msg1->reason_phrase);
+		errors++;
+	}
+	if (msg3->status_code == SOUP_STATUS_OK)
+		debug_printf (1, "  msg3 succeeded\n");
+	else {
+		debug_printf (1, "  msg3 FAILED! (%u %s)\n",
+			      msg3->status_code, msg3->reason_phrase);
+		errors++;
+	}
+
+	soup_session_abort (session);
+	g_object_unref (session);
+
+	g_object_unref (msg1);
+	g_object_unref (msg3);
+	memcpy (msg2, &msg2_bak, sizeof (SoupMessage));
+	g_object_unref (msg2);
+	g_object_unref (auth);
+	g_free (uri);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -417,6 +559,7 @@
 	g_object_unref (session);
 
 	/* And now for some regression tests */
+	loop = g_main_loop_new (NULL, TRUE);
 
 	debug_printf (1, "Testing pipelined auth (bug 271540):\n");
 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
@@ -437,8 +580,9 @@
 	}
 	g_free (uri);
 
-	loop = g_main_loop_new (NULL, TRUE);
 	g_main_loop_run (loop);
+	soup_session_abort (session);
+	g_object_unref (session);
 
 	debug_printf (1, "\nTesting digest nonce expiration:\n");
 
@@ -516,6 +660,8 @@
 	 *
 	 */
 
+	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
+
 	uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
 	do_digest_nonce_test (session, "First", uri, TRUE, TRUE);
 	g_free (uri);
@@ -528,11 +674,14 @@
 	do_digest_nonce_test (session, "Fourth", uri, FALSE, FALSE);
 	g_free (uri);
 
-	g_main_loop_unref (loop);
-
 	soup_session_abort (session);
 	g_object_unref (session);
 
+	/* Async auth */
+	do_async_auth_test (base_uri);
+
+	g_main_loop_unref (loop);
+
 	test_cleanup ();
 	return errors != 0;
 }



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