summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Winship <danw@src.gnome.org>2003-08-11 13:50:51 +0000
committerDan Winship <danw@src.gnome.org>2003-08-11 13:50:51 +0000
commit3760e63a50f9192e2dce8636e6b7a09c9d38355e (patch)
tree914e832ccee01393f8e5b5a15fd8872d16d5d92b
parente4807fb950ddfdf4196b9765f036265a0b83e7f9 (diff)
downloadlibsoup-2-0-branch-base.tar.gz
These are all really SoupContext functions, so move them to soup-context.clibsoup-2-0-branch-base
* 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
-rw-r--r--ChangeLog65
-rw-r--r--libsoup/soup-auth.c250
-rw-r--r--libsoup/soup-auth.h58
-rw-r--r--libsoup/soup-context.c252
-rw-r--r--libsoup/soup-context.h4
-rw-r--r--libsoup/soup-message.c152
-rw-r--r--libsoup/soup-private.h22
-rw-r--r--libsoup/soup-queue.c33
-rw-r--r--tests/.cvsignore1
-rw-r--r--tests/Makefile.am11
-rw-r--r--tests/auth-test.c273
11 files changed, 813 insertions, 308 deletions
diff --git a/ChangeLog b/ChangeLog
index 94ab74e4..7a69dde1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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")
diff --git a/libsoup/soup-auth.c b/libsoup/soup-auth.c
index c8e33052..e9615ebe 100644
--- a/libsoup/soup-auth.c
+++ b/libsoup/soup-auth.c
@@ -62,6 +62,21 @@ basic_parse_func (SoupAuth *auth, const char *header)
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 @@ basic_init_func (SoupAuth *auth, const SoupUri *uri)
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 @@ soup_auth_new_basic (void)
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 @@ typedef struct {
char *nonce;
QOPType qop_options;
AlgorithmType algorithm;
+ char *domain;
/* These are generated by the client */
char *cnonce;
@@ -331,6 +364,7 @@ digest_parse_func (SoupAuth *auth, const char *header)
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 @@ digest_parse_func (SoupAuth *auth, const char *header)
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 || !*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 @@ digest_init_func (SoupAuth *auth, const SoupUri *uri)
/* 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 @@ digest_free (SoupAuth *auth)
SoupAuthDigest *digest = (SoupAuthDigest *) auth;
g_free (digest->user);
+ g_free (digest->domain);
g_free (digest->nonce);
g_free (digest->cnonce);
@@ -417,9 +507,12 @@ soup_auth_new_digest (void)
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 @@ static gchar *
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 @@ ntlm_parse (SoupAuth *sa, const char *header)
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 @@ ntlm_init (SoupAuth *sa, const SoupUri *uri)
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 @@ ntlm_free (SoupAuth *sa)
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 @@ ntlm_new (void)
* 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 @@ typedef struct {
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 @@ soup_auth_initialize (SoupAuth *auth, const SoupUri *uri)
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)
{
@@ -744,3 +779,22 @@ soup_auth_free (SoupAuth *auth)
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);
+}
diff --git a/libsoup/soup-auth.h b/libsoup/soup-auth.h
index e1c919d9..756029ea 100644
--- a/libsoup/soup-auth.h
+++ b/libsoup/soup-auth.h
@@ -18,49 +18,47 @@
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;
+ SoupAuthType type;
+ char *realm;
+ gboolean authenticated;
- SoupAuthStatus status;
- SoupMessage *controlling_msg;
+ void (*parse_func) (SoupAuth *auth,
+ const gchar *header);
- void (*parse_func) (SoupAuth *auth,
- const gchar *header);
+ void (*init_func) (SoupAuth *auth,
+ const SoupUri *uri);
- void (*init_func) (SoupAuth *auth,
- const SoupUri *uri);
+ gboolean (*invalidate_func) (SoupAuth *auth);
- char *(*auth_func) (SoupAuth *auth,
- SoupMessage *message);
+ char *(*auth_func) (SoupAuth *auth,
+ SoupMessage *message);
- void (*free_func) (SoupAuth *auth);
+ GSList *(*pspace_func) (SoupAuth *auth,
+ const SoupUri *source_uri);
+
+ void (*free_func) (SoupAuth *auth);
};
-SoupAuth *soup_auth_lookup (SoupContext *ctx);
+SoupAuth *soup_auth_new_from_header_list (const SoupUri *uri,
+ const GSList *header);
+
+SoupAuth *soup_auth_new_ntlm (void);
-void soup_auth_set_context (SoupAuth *auth,
- SoupContext *ctx);
+void soup_auth_initialize (SoupAuth *auth,
+ const SoupUri *uri);
-void soup_auth_invalidate (SoupAuth *auth,
- SoupContext *ctx);
+gboolean soup_auth_invalidate (SoupAuth *auth);
-SoupAuth *soup_auth_new_from_header_list (const SoupUri *uri,
- const GSList *header);
+void soup_auth_free (SoupAuth *auth);
-void soup_auth_initialize (SoupAuth *auth,
- const SoupUri *uri);
+gchar *soup_auth_authorize (SoupAuth *auth,
+ SoupMessage *msg);
-void soup_auth_free (SoupAuth *auth);
+GSList *soup_auth_get_protection_space (SoupAuth *auth,
+ const SoupUri *source_uri);
+void soup_auth_free_protection_space (SoupAuth *auth,
+ GSList *space);
-gchar *soup_auth_authorize (SoupAuth *auth,
- SoupMessage *msg);
#endif /* SOUP_AUTH_H */
diff --git a/libsoup/soup-context.c b/libsoup/soup-context.c
index e5dc6e9a..4c4cd30e 100644
--- a/libsoup/soup-context.c
+++ b/libsoup/soup-context.c
@@ -193,13 +193,18 @@ soup_context_ref (SoupContext *ctx)
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 @@ soup_context_unref (SoupContext *ctx)
/*
* 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 @@ connection_free (SoupConnection *conn)
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);
@@ -770,3 +776,223 @@ soup_connection_purge_idle (void)
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);
+}
diff --git a/libsoup/soup-context.h b/libsoup/soup-context.h
index 581dd841..79853d11 100644
--- a/libsoup/soup-context.h
+++ b/libsoup/soup-context.h
@@ -65,4 +65,8 @@ void soup_connection_release (SoupConnection *conn);
void soup_connection_purge_idle (void);
+
+void soup_context_preauthenticate (SoupContext *ctx,
+ const char *header);
+
#endif /*SOUP_CONTEXT_H*/
diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c
index 92079c1d..254394e7 100644
--- a/libsoup/soup-message.c
+++ b/libsoup/soup-message.c
@@ -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 @@ soup_message_send (SoupMessage *msg)
}
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);
-
- soup_message_requeue (msg);
-
- return;
-
- THROW_CANT_AUTHENTICATE:
- soup_message_set_error (msg,
- proxy ?
+ if (soup_context_update_auth (ctx, msg))
+ soup_message_requeue (msg);
+ else {
+ soup_message_set_error (msg,
+ proxy ?
SOUP_ERROR_CANT_AUTHENTICATE_PROXY :
SOUP_ERROR_CANT_AUTHENTICATE);
+ }
}
static void
diff --git a/libsoup/soup-private.h b/libsoup/soup-private.h
index 50db591d..d67535c4 100644
--- a/libsoup/soup-private.h
+++ b/libsoup/soup-private.h
@@ -46,9 +46,11 @@ extern gpointer soup_auth_fn_user_data;
typedef struct {
gchar *host;
- GSList *connections; /* CONTAINS: SoupConnection */
- GHashTable *contexts; /* KEY: uri->path, VALUE: SoupContext */
- GHashTable *valid_auths; /* KEY: uri->path, VALUE: SoupAuth */
+ GSList *connections; /* CONTAINS: SoupConnection */
+ GHashTable *contexts; /* KEY: uri->path, VALUE: SoupContext */
+ 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 @@ struct _SoupMessagePrivate {
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);
diff --git a/libsoup/soup-queue.c b/libsoup/soup-queue.c
index 76955839..7e5060c0 100644
--- a/libsoup/soup-queue.c
+++ b/libsoup/soup-queue.c
@@ -295,23 +295,22 @@ soup_encode_http_auth (SoupMessage *msg, GString *header, gboolean proxy_auth)
ctx = proxy_auth ? soup_get_proxy () : msg->context;
- if (msg->connection->auth)
- auth = msg->connection->auth;
- else
- auth = soup_auth_lookup (ctx);
-
- if (auth) {
- token = soup_auth_authorize (auth, msg);
- if (token) {
- g_string_sprintfa (header,
- "%s: %s\r\n",
- proxy_auth ?
- "Proxy-Authorization" :
- "Authorization",
- token);
- g_free (token);
- }
- }
+ auth = soup_context_lookup_auth (ctx, msg);
+ if (!auth)
+ return;
+ if (!auth->authenticated &&
+ !soup_context_authenticate_auth (ctx, auth))
+ return;
+
+ token = soup_auth_authorize (auth, msg);
+ if (token) {
+ g_string_sprintfa (header, "%s: %s\r\n",
+ proxy_auth ?
+ "Proxy-Authorization" :
+ "Authorization",
+ token);
+ g_free (token);
+ }
}
struct SoupUsedHeaders {
diff --git a/tests/.cvsignore b/tests/.cvsignore
index ec574b30..c7b7d697 100644
--- a/tests/.cvsignore
+++ b/tests/.cvsignore
@@ -2,3 +2,4 @@ Makefile
Makefile.in
get
timeserver
+auth-test
diff --git a/tests/Makefile.am b/tests/Makefile.am
index bd8803be..60bf9635 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,12 +2,17 @@ INCLUDES = \
-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
diff --git a/tests/auth-test.c b/tests/auth-test.c
new file mode 100644
index 00000000..54b5219b
--- /dev/null
+++ b/tests/auth-test.c
@@ -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;
+}