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.
#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;
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))
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 */
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)
}
/**
+ * 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
*
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)
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
#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"
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;
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.
*/
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);
}
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
#include "apache-wrapper.h"
#endif
+GMainLoop *loop;
int errors = 0;
typedef struct {
}
}
+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
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);