get/set auth/proxy_auth info for a message.
authorDan Winship <danw@src.gnome.org>
Sat, 17 Mar 2007 18:51:28 +0000 (18:51 +0000)
committerDan Winship <danw@src.gnome.org>
Sat, 17 Mar 2007 18:51:28 +0000 (18:51 +0000)
* libsoup/soup-message.c (soup_message_set_auth)
(soup_message_get_auth, soup_message_set_proxy_auth)
(soup_message_get_proxy_auth): get/set auth/proxy_auth info for a
message.

* libsoup/soup-session.c (add_auth): Use soup_message_set_auth and
soup_message_set_proxy_auth.
(update_auth_internal): Call soup_message_get_auth or
soup_message_get_proxy_auth to determine the message's prior auth,
rather than calling lookup_auth() again, since it isn't guaranteed
to return the same thing now as it did when the message was
originally sent. Fixes erroneous 401s when queuing multiple
messages at once to an as-yet-unauthenticated-to server. #271540

* libsoup/soup-session-async.c (queue_message): don't run the
queue right away, do it at idle time. Otherwise in some cases
(especially errors), the message callbacks could be invoked before
queue_message returns.

* tests/auth-test.c: add a regression test for #271540.

svn path=/trunk/; revision=922

ChangeLog
libsoup/soup-message-private.h
libsoup/soup-message.c
libsoup/soup-session-async.c
libsoup/soup-session.c
tests/auth-test.c

index ea03272..6bee2c6 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,28 @@
 2007-03-17  Dan Winship  <danw@novell.com>
 
+       * libsoup/soup-message.c (soup_message_set_auth)
+       (soup_message_get_auth, soup_message_set_proxy_auth)
+       (soup_message_get_proxy_auth): get/set auth/proxy_auth info for a
+       message.
+
+       * libsoup/soup-session.c (add_auth): Use soup_message_set_auth and
+       soup_message_set_proxy_auth.
+       (update_auth_internal): Call soup_message_get_auth or
+       soup_message_get_proxy_auth to determine the message's prior auth,
+       rather than calling lookup_auth() again, since it isn't guaranteed
+       to return the same thing now as it did when the message was
+       originally sent. Fixes erroneous 401s when queuing multiple
+       messages at once to an as-yet-unauthenticated-to server. #271540
+
+       * libsoup/soup-session-async.c (queue_message): don't run the
+       queue right away, do it at idle time. Otherwise in some cases
+       (especially errors), the message callbacks could be invoked before
+       queue_message returns.
+
+       * tests/auth-test.c: add a regression test for #271540.
+
+2007-03-17  Dan Winship  <danw@novell.com>
+
        * configure.in: require glib 2.12. check for timegm().
 
        * libsoup/soup-date.c (soup_mktime_utc): Use timegm if available.
index 04ab11b..3f8e366 100644 (file)
@@ -6,7 +6,8 @@
 #ifndef SOUP_MESSAGE_PRIVATE_H
 #define SOUP_MESSAGE_PRIVATE_H 1
 
-#include <libsoup/soup-message.h>
+#include "soup-message.h"
+#include "soup-auth.h"
 
 typedef struct {
        gpointer           io_data;
@@ -20,6 +21,8 @@ typedef struct {
        SoupHttpVersion    http_version;
 
        SoupUri           *uri;
+
+       SoupAuth          *auth, *proxy_auth;
 } SoupMessagePrivate;
 #define SOUP_MESSAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_MESSAGE, SoupMessagePrivate))
 
@@ -57,4 +60,12 @@ void soup_message_io_server  (SoupMessage               *msg,
                              SoupMessageParseHeadersFn  parse_headers_cb,
                              gpointer                   user_data);
 
+/* Auth handling */
+void           soup_message_set_auth       (SoupMessage *msg,
+                                           SoupAuth    *auth);
+SoupAuth      *soup_message_get_auth       (SoupMessage *msg);
+void           soup_message_set_proxy_auth (SoupMessage *msg,
+                                           SoupAuth    *auth);
+SoupAuth      *soup_message_get_proxy_auth (SoupMessage *msg);
+
 #endif /* SOUP_MESSAGE_PRIVATE_H */
index 160ada5..1bb55a6 100644 (file)
@@ -70,6 +70,11 @@ finalize (GObject *object)
        if (priv->uri)
                soup_uri_free (priv->uri);
 
+       if (priv->auth)
+               g_object_unref (priv->auth);
+       if (priv->proxy_auth)
+               g_object_unref (priv->proxy_auth);
+
        if (msg->request.owner == SOUP_BUFFER_SYSTEM_OWNED)
                g_free (msg->request.body);
        if (msg->response.owner == SOUP_BUFFER_SYSTEM_OWNED)
@@ -725,6 +730,110 @@ soup_message_foreach_header (GHashTable *hash, GHFunc func, gpointer user_data)
 }
 
 /**
+ * soup_message_set_auth:
+ * @msg: a #SoupMessage
+ * @auth: a #SoupAuth, or %NULL
+ *
+ * Sets @msg to authenticate to its destination using @auth, which
+ * must have already been fully authenticated. If @auth is %NULL, @msg
+ * will not authenticate to its destination.
+ **/
+void
+soup_message_set_auth (SoupMessage *msg, SoupAuth *auth)
+{
+       SoupMessagePrivate *priv;
+       char *token;
+
+       g_return_if_fail (SOUP_IS_MESSAGE (msg));
+       g_return_if_fail (auth == NULL || SOUP_IS_AUTH (auth));
+       g_return_if_fail (auth == NULL || soup_auth_is_authenticated (auth));
+
+       priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+
+       if (priv->auth)
+               g_object_unref (priv->auth);
+       soup_message_remove_header (msg->request_headers, "Authorization");
+       priv->auth = auth;
+       if (!priv->auth)
+               return;
+
+       g_object_ref (priv->auth);
+       token = soup_auth_get_authorization (auth, msg);
+       soup_message_add_header (msg->request_headers, "Authorization", token);
+       g_free (token);
+}
+
+/**
+ * soup_message_get_auth:
+ * @msg: a #SoupMessage
+ *
+ * Gets the #SoupAuth used by @msg for authentication.
+ *
+ * Return value: the #SoupAuth used by @msg for authentication, or
+ * %NULL if @msg is unauthenticated.
+ **/
+SoupAuth *
+soup_message_get_auth (SoupMessage *msg)
+{
+       g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
+
+       return SOUP_MESSAGE_GET_PRIVATE (msg)->auth;
+}
+
+/**
+ * soup_message_set_proxy_auth:
+ * @msg: a #SoupMessage
+ * @auth: a #SoupAuth, or %NULL
+ *
+ * Sets @msg to authenticate to its proxy using @auth, which must have
+ * already been fully authenticated. If @auth is %NULL, @msg will not
+ * authenticate to its proxy.
+ **/
+void
+soup_message_set_proxy_auth (SoupMessage *msg, SoupAuth *auth)
+{
+       SoupMessagePrivate *priv;
+       char *token;
+
+       g_return_if_fail (SOUP_IS_MESSAGE (msg));
+       g_return_if_fail (auth == NULL || SOUP_IS_AUTH (auth));
+       g_return_if_fail (auth == NULL || soup_auth_is_authenticated (auth));
+
+       priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+
+       if (priv->proxy_auth)
+               g_object_unref (priv->proxy_auth);
+       soup_message_remove_header (msg->request_headers,
+                                   "Proxy-Authorization");
+       priv->proxy_auth = auth;
+       if (!priv->proxy_auth)
+               return;
+
+       g_object_ref (priv->proxy_auth);
+       token = soup_auth_get_authorization (auth, msg);
+       soup_message_add_header (msg->request_headers,
+                                "Proxy-Authorization", token);
+       g_free (token);
+}
+
+/**
+ * soup_message_get_proxy_auth:
+ * @msg: a #SoupMessage
+ *
+ * Gets the #SoupAuth used by @msg for authentication to its proxy..
+ *
+ * Return value: the #SoupAuth used by @msg for authentication to its
+ * proxy, or %NULL if @msg isn't authenticated to its proxy.
+ **/
+SoupAuth *
+soup_message_get_proxy_auth (SoupMessage *msg)
+{
+       g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
+
+       return SOUP_MESSAGE_GET_PRIVATE (msg)->proxy_auth;
+}
+
+/**
  * soup_message_cleanup_response:
  * @req: a #SoupMessage
  *
index 457d477..92d2bc6 100644 (file)
@@ -184,6 +184,19 @@ final_finished (SoupMessage *req, gpointer user_data)
        run_queue (sa, FALSE);
 }
 
+static gboolean
+idle_run_queue (gpointer user_data)
+{
+       SoupSessionAsync *sa = user_data;
+
+       g_object_add_weak_pointer (G_OBJECT (sa), (gpointer)&sa);
+       g_object_unref (sa);
+
+       if (sa)
+               run_queue (sa, TRUE);
+       return FALSE;
+}
+
 static void
 queue_message (SoupSession *session, SoupMessage *req,
               SoupMessageCallbackFn callback, gpointer user_data)
@@ -202,7 +215,8 @@ queue_message (SoupSession *session, SoupMessage *req,
 
        SOUP_SESSION_CLASS (soup_session_async_parent_class)->queue_message (session, req, callback, user_data);
 
-       run_queue (sa, TRUE);
+       g_object_ref (sa);
+       g_idle_add (idle_run_queue, sa);
 }
 
 static guint
index 509b76e..2dfe67b 100644 (file)
@@ -19,6 +19,7 @@
 #include "soup-connection-ntlm.h"
 #include "soup-marshal.h"
 #include "soup-message-filter.h"
+#include "soup-message-private.h"
 #include "soup-message-queue.h"
 #include "soup-ssl.h"
 #include "soup-uri.h"
@@ -744,8 +745,7 @@ authenticate_auth (SoupSession *session, SoupAuth *auth,
 
 static gboolean
 update_auth_internal (SoupSession *session, SoupMessage *msg,
-                     const GSList *headers, gboolean proxy,
-                     gboolean got_unauthorized)
+                     const GSList *headers, gboolean proxy)
 {
        SoupSessionHost *host;
        SoupAuth *new_auth, *prior_auth, *old_auth;
@@ -772,20 +772,11 @@ update_auth_internal (SoupSession *session, SoupMessage *msg,
                return FALSE;
 
        /* See if this auth is the same auth we used last time */
-       prior_auth = lookup_auth (session, msg, proxy);
+       prior_auth = proxy ? soup_message_get_proxy_auth (msg) : soup_message_get_auth (msg);
        if (prior_auth &&
            G_OBJECT_TYPE (prior_auth) == G_OBJECT_TYPE (new_auth) &&
            !strcmp (soup_auth_get_realm (prior_auth),
                     soup_auth_get_realm (new_auth))) {
-               if (!got_unauthorized) {
-                       /* The user is just trying to preauthenticate
-                        * using information we already have, so
-                        * there's nothing more that needs to be done.
-                        */
-                       g_object_unref (new_auth);
-                       return TRUE;
-               }
-
                /* The server didn't like the username/password we
                 * provided before. Invalidate it and note this fact.
                 */
@@ -891,7 +882,7 @@ authorize_handler (SoupMessage *msg, gpointer user_data)
        if (!headers)
                return;
 
-       if (update_auth_internal (session, msg, headers, proxy, TRUE))
+       if (update_auth_internal (session, msg, headers, proxy))
                soup_session_requeue_message (session, msg);
 }
 
@@ -926,23 +917,18 @@ redirect_handler (SoupMessage *msg, gpointer user_data)
 static void
 add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
 {
-       const char *header = proxy ? "Proxy-Authorization" : "Authorization";
        SoupAuth *auth;
-       char *token;
 
        auth = lookup_auth (session, msg, proxy);
-       if (!auth)
-               return;
-       if (!soup_auth_is_authenticated (auth) &&
-           !authenticate_auth (session, auth, msg, FALSE, proxy))
-               return;
-
-       token = soup_auth_get_authorization (auth, msg);
-       if (token) {
-               soup_message_remove_header (msg->request_headers, header);
-               soup_message_add_header (msg->request_headers, header, token);
-               g_free (token);
+       if (auth && !soup_auth_is_authenticated (auth)) {
+               if (!authenticate_auth (session, auth, msg, FALSE, proxy))
+                       auth = NULL;
        }
+
+       if (proxy)
+               soup_message_set_proxy_auth (msg, auth);
+       else
+               soup_message_set_auth (msg, auth);
 }
 
 static void
index b586408..40aa6b6 100644 (file)
@@ -14,6 +14,7 @@
 #include "apache-wrapper.h"
 #endif
 
+GMainLoop *loop;
 int errors = 0;
 
 typedef struct {
@@ -243,12 +244,69 @@ reauthenticate (SoupSession *session, SoupMessage *msg,
        }
 }
 
+static void
+bug271540_sent (SoupMessage *msg, gpointer data)
+{
+       int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
+       gboolean *authenticated = data;
+       int auth = identify_auth (msg);
+
+       if (!*authenticated && auth) {
+               printf ("    using auth on message %d before authenticating!!??\n", n);
+               errors++;
+       } else if (*authenticated && !auth) {
+               printf ("    sent unauthenticated message %d after authenticating!\n", n);
+               errors++;
+       }
+}
+
+static void
+bug271540_authenticate (SoupSession *session, SoupMessage *msg,
+                       const char *auth_type, const char *auth_realm,
+                       char **username, char **password, gpointer data)
+{
+       int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
+       gboolean *authenticated = data;
+
+       if (strcmp (auth_type, "Basic") != 0 ||
+           strcmp (auth_realm, "realm1") != 0)
+               return;
+
+       if (!*authenticated) {
+               printf ("    authenticating message %d\n", n);
+               *username = g_strdup ("user1");
+               *password = g_strdup ("realm1");
+               *authenticated = TRUE;
+       } else {
+               printf ("    asked to authenticate message %d after authenticating!\n", n);
+               errors++;
+       }
+}
+
+static void
+bug271540_finished (SoupMessage *msg, gpointer data)
+{
+       int *left = data;
+       int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
+
+       if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
+               printf ("      got status '%d %s' on message %d!\n",
+                       msg->status_code, msg->reason_phrase, n);
+               errors++;
+       }
+
+       (*left)--;
+       if (!*left)
+               g_main_loop_quit (loop);
+}
+
 int
 main (int argc, char **argv)
 {
        SoupSession *session;
        SoupMessage *msg;
        char *base_uri, *uri, *expected;
+       gboolean authenticated;
 #ifdef HAVE_APACHE
        gboolean using_apache = FALSE;
 #endif
@@ -325,6 +383,30 @@ main (int argc, char **argv)
 
                g_object_unref (msg);
        }
+       g_object_unref (session);
+
+       /* And now for a regression test */
+
+       printf ("Regression test for bug 271540:\n");
+       session = soup_session_async_new ();
+
+       authenticated = FALSE;
+       g_signal_connect (session, "authenticate",
+                         G_CALLBACK (bug271540_authenticate), &authenticated);
+
+       uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
+       for (i = 0; i < 10; i++) {
+               msg = soup_message_new (SOUP_METHOD_GET, uri);
+               g_object_set_data (G_OBJECT (msg), "#", GINT_TO_POINTER (i + 1));
+               g_signal_connect (msg, "wrote_headers",
+                                 G_CALLBACK (bug271540_sent), &authenticated);
+
+               soup_session_queue_message (session, msg,
+                                           bug271540_finished, &i);
+       }
+
+       loop = g_main_loop_new (NULL, TRUE);
+       g_main_loop_run (loop);
 
        g_object_unref (session);