1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-auth-manager.c: SoupAuth manager for SoupSession
5 * Copyright (C) 2007 Red Hat, Inc.
14 #include "soup-auth-manager.h"
15 #include "soup-address.h"
16 #include "soup-headers.h"
17 #include "soup-marshal.h"
18 #include "soup-message-private.h"
19 #include "soup-path-map.h"
20 #include "soup-session.h"
21 #include "soup-session-feature.h"
24 static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
25 static SoupSessionFeatureInterface *soup_session_feature_default_interface;
27 static void attach (SoupSessionFeature *feature, SoupSession *session);
28 static void request_queued (SoupSessionFeature *feature, SoupSession *session,
30 static void request_started (SoupSessionFeature *feature, SoupSession *session,
31 SoupMessage *msg, SoupSocket *socket);
32 static void request_unqueued (SoupSessionFeature *feature,
33 SoupSession *session, SoupMessage *msg);
40 static guint signals[LAST_SIGNAL] = { 0 };
42 G_DEFINE_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT,
43 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
44 soup_auth_manager_session_feature_init))
48 GPtrArray *auth_types;
51 GHashTable *auth_hosts;
52 } SoupAuthManagerPrivate;
53 #define SOUP_AUTH_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerPrivate))
57 SoupPathMap *auth_realms; /* path -> scheme:realm */
58 GHashTable *auths; /* scheme:realm -> SoupAuth */
62 soup_auth_manager_init (SoupAuthManager *manager)
64 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
66 priv->auth_types = g_ptr_array_new ();
67 priv->auth_hosts = g_hash_table_new (soup_address_hash_by_name,
68 soup_address_equal_by_name);
72 foreach_free_host (gpointer key, gpointer value, gpointer data)
74 SoupAuthHost *host = value;
76 if (host->auth_realms)
77 soup_path_map_free (host->auth_realms);
79 g_hash_table_destroy (host->auths);
81 g_object_unref (host->addr);
82 g_slice_free (SoupAuthHost, host);
88 finalize (GObject *object)
90 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (object);
93 for (i = 0; i < priv->auth_types->len; i++)
94 g_type_class_unref (priv->auth_types->pdata[i]);
95 g_ptr_array_free (priv->auth_types, TRUE);
97 g_hash_table_foreach_remove (priv->auth_hosts, foreach_free_host, NULL);
98 g_hash_table_destroy (priv->auth_hosts);
100 if (priv->proxy_auth)
101 g_object_unref (priv->proxy_auth);
103 G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object);
107 soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class)
109 GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_class);
111 g_type_class_add_private (auth_manager_class, sizeof (SoupAuthManagerPrivate));
113 object_class->finalize = finalize;
115 signals[AUTHENTICATE] =
116 g_signal_new ("authenticate",
117 G_OBJECT_CLASS_TYPE (object_class),
119 G_STRUCT_OFFSET (SoupAuthManagerClass, authenticate),
121 soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN,
130 soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface,
131 gpointer interface_data)
133 soup_session_feature_default_interface =
134 g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
136 feature_interface->attach = attach;
137 feature_interface->request_queued = request_queued;
138 feature_interface->request_started = request_started;
139 feature_interface->request_unqueued = request_unqueued;
143 auth_type_compare_func (gconstpointer a, gconstpointer b)
145 SoupAuthClass **auth1 = (SoupAuthClass **)a;
146 SoupAuthClass **auth2 = (SoupAuthClass **)b;
148 return (*auth2)->strength - (*auth1)->strength;
152 soup_auth_manager_add_type (SoupAuthManager *manager, GType type)
154 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
155 SoupAuthClass *auth_class;
157 g_return_if_fail (g_type_is_a (type, SOUP_TYPE_AUTH));
159 auth_class = g_type_class_ref (type);
160 g_ptr_array_add (priv->auth_types, auth_class);
161 g_ptr_array_sort (priv->auth_types, auth_type_compare_func);
165 soup_auth_manager_remove_type (SoupAuthManager *manager, GType type)
167 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
168 SoupAuthClass *auth_class;
171 g_return_if_fail (g_type_is_a (type, SOUP_TYPE_AUTH));
173 auth_class = g_type_class_peek (type);
174 for (i = 0; i < priv->auth_types->len; i++) {
175 if (priv->auth_types->pdata[i] == (gpointer)auth_class) {
176 g_ptr_array_remove_index (priv->auth_types, i);
177 g_type_class_unref (auth_class);
184 soup_auth_manager_emit_authenticate (SoupAuthManager *manager, SoupMessage *msg,
185 SoupAuth *auth, gboolean retrying)
187 g_signal_emit (manager, signals[AUTHENTICATE], 0, msg, auth, retrying);
191 attach (SoupSessionFeature *manager, SoupSession *session)
193 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
195 /* FIXME: should support multiple sessions */
196 priv->session = session;
198 soup_session_feature_default_interface->attach (manager, session);
201 static inline const char *
202 auth_header_for_message (SoupMessage *msg)
204 if (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) {
205 return soup_message_headers_get (msg->response_headers,
206 "Proxy-Authenticate");
208 return soup_message_headers_get (msg->response_headers,
214 extract_challenge (const char *challenges, const char *scheme)
217 int schemelen = strlen (scheme);
218 char *item, *space, *equals;
221 /* The relevant grammar:
223 * WWW-Authenticate = 1#challenge
224 * Proxy-Authenticate = 1#challenge
225 * challenge = auth-scheme 1#auth-param
226 * auth-scheme = token
227 * auth-param = token "=" ( token | quoted-string )
229 * The fact that quoted-strings can contain commas, equals
230 * signs, and auth scheme names makes it tricky to "cheat" on
231 * the parsing. We just use soup_header_parse_list(), and then
232 * reassemble the pieces after we find the one we want.
235 items = soup_header_parse_list (challenges);
237 /* First item will start with the scheme name, followed by a
238 * space and then the first auth-param.
240 for (i = items; i; i = i->next) {
242 if (!g_ascii_strncasecmp (item, scheme, schemelen) &&
243 g_ascii_isspace (item[schemelen]))
247 soup_header_free_list (items);
251 /* The challenge extends from this item until the end, or until
252 * the next item that has a space before an equals sign.
254 challenge = g_string_new (item);
255 for (i = i->next; i; i = i->next) {
257 space = strpbrk (item, " \t");
258 equals = strchr (item, '=');
259 if (!equals || (space && equals > space))
262 g_string_append (challenge, ", ");
263 g_string_append (challenge, item);
266 soup_header_free_list (items);
267 return g_string_free (challenge, FALSE);
271 create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
274 SoupAuthClass *auth_class;
275 char *challenge = NULL;
279 header = auth_header_for_message (msg);
283 for (i = priv->auth_types->len - 1; i >= 0; i--) {
284 auth_class = priv->auth_types->pdata[i];
285 challenge = extract_challenge (header, auth_class->scheme_name);
292 auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenge);
298 check_auth (SoupMessage *msg, SoupAuth *auth)
304 header = auth_header_for_message (msg);
308 challenge = extract_challenge (header, soup_auth_get_scheme_name (auth));
312 ok = soup_auth_update (auth, msg, challenge);
317 static SoupAuthHost *
318 get_auth_host_for_message (SoupAuthManagerPrivate *priv, SoupMessage *msg)
321 SoupAddress *addr = soup_message_get_address (msg);
323 host = g_hash_table_lookup (priv->auth_hosts, addr);
327 host = g_slice_new0 (SoupAuthHost);
328 host->addr = g_object_ref (addr);
329 g_hash_table_insert (priv->auth_hosts, host->addr, host);
335 lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
338 const char *path, *realm;
340 host = get_auth_host_for_message (priv, msg);
341 if (!host->auth_realms)
344 path = soup_message_get_uri (msg)->path;
347 realm = soup_path_map_lookup (host->auth_realms, path);
349 return g_hash_table_lookup (host->auths, realm);
355 authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
356 SoupMessage *msg, gboolean prior_auth_failed,
359 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
362 if (soup_auth_is_authenticated (auth))
366 g_object_get (G_OBJECT (priv->session),
367 SOUP_SESSION_PROXY_URI, &uri,
370 uri = soup_uri_copy (soup_message_get_uri (msg));
372 if (uri->password && !prior_auth_failed) {
373 soup_auth_authenticate (auth, uri->user, uri->password);
379 soup_auth_manager_emit_authenticate (manager, msg, auth,
381 return soup_auth_is_authenticated (auth);
385 update_auth (SoupMessage *msg, gpointer manager)
387 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
389 SoupAuth *auth, *prior_auth, *old_auth;
391 char *auth_info, *old_auth_info;
393 gboolean prior_auth_failed = FALSE;
395 host = get_auth_host_for_message (priv, msg);
397 /* See if we used auth last time */
398 prior_auth = soup_message_get_auth (msg);
399 if (prior_auth && check_auth (msg, prior_auth)) {
401 if (!soup_auth_is_authenticated (auth))
402 prior_auth_failed = TRUE;
404 auth = create_auth (priv, msg);
408 auth_info = soup_auth_get_info (auth);
410 if (!host->auth_realms) {
411 host->auth_realms = soup_path_map_new (g_free);
412 host->auths = g_hash_table_new_full (g_str_hash, g_str_equal,
413 g_free, g_object_unref);
416 /* Record where this auth realm is used. */
417 pspace = soup_auth_get_protection_space (auth, soup_message_get_uri (msg));
418 for (p = pspace; p; p = p->next) {
420 old_auth_info = soup_path_map_lookup (host->auth_realms, path);
422 if (!strcmp (old_auth_info, auth_info))
424 soup_path_map_remove (host->auth_realms, path);
427 soup_path_map_add (host->auth_realms, path,
428 g_strdup (auth_info));
430 soup_auth_free_protection_space (auth, pspace);
432 /* Now, make sure the auth is recorded. (If there's a
433 * pre-existing auth, we keep that rather than the new one,
434 * since the old one might already be authenticated.)
436 old_auth = g_hash_table_lookup (host->auths, auth_info);
439 if (auth != old_auth && auth != prior_auth) {
440 g_object_unref (auth);
444 g_hash_table_insert (host->auths, auth_info, auth);
447 /* If we need to authenticate, try to do it. */
448 authenticate_auth (manager, auth, msg,
449 prior_auth_failed, FALSE);
453 requeue_if_authenticated (SoupMessage *msg, gpointer manager)
455 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
456 SoupAuth *auth = lookup_auth (priv, msg);
458 if (auth && soup_auth_is_authenticated (auth))
459 soup_session_requeue_message (priv->session, msg);
463 update_proxy_auth (SoupMessage *msg, gpointer manager)
465 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
466 SoupAuth *prior_auth;
467 gboolean prior_auth_failed = FALSE;
469 /* See if we used auth last time */
470 prior_auth = soup_message_get_proxy_auth (msg);
471 if (prior_auth && check_auth (msg, prior_auth)) {
472 if (!soup_auth_is_authenticated (prior_auth))
473 prior_auth_failed = TRUE;
476 if (!priv->proxy_auth) {
477 priv->proxy_auth = create_auth (priv, msg);
478 if (!priv->proxy_auth)
482 /* If we need to authenticate, try to do it. */
483 authenticate_auth (manager, priv->proxy_auth, msg,
484 prior_auth_failed, TRUE);
488 requeue_if_proxy_authenticated (SoupMessage *msg, gpointer manager)
490 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
491 SoupAuth *auth = priv->proxy_auth;
493 if (auth && soup_auth_is_authenticated (auth))
494 soup_session_requeue_message (priv->session, msg);
498 request_queued (SoupSessionFeature *manager, SoupSession *session,
501 soup_message_add_status_code_handler (
502 msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
503 G_CALLBACK (update_auth), manager);
504 soup_message_add_status_code_handler (
505 msg, "got_body", SOUP_STATUS_UNAUTHORIZED,
506 G_CALLBACK (requeue_if_authenticated), manager);
508 soup_message_add_status_code_handler (
509 msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED,
510 G_CALLBACK (update_proxy_auth), manager);
511 soup_message_add_status_code_handler (
512 msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED,
513 G_CALLBACK (requeue_if_proxy_authenticated), manager);
517 request_started (SoupSessionFeature *feature, SoupSession *session,
518 SoupMessage *msg, SoupSocket *socket)
520 SoupAuthManager *manager = SOUP_AUTH_MANAGER (feature);
521 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
524 auth = lookup_auth (priv, msg);
525 if (!auth || !authenticate_auth (manager, auth, msg, FALSE, FALSE))
527 soup_message_set_auth (msg, auth);
529 auth = priv->proxy_auth;
530 if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE))
532 soup_message_set_proxy_auth (msg, auth);
536 request_unqueued (SoupSessionFeature *manager, SoupSession *session,
539 g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA,
540 0, 0, NULL, NULL, manager);