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-message-queue.h"
20 #include "soup-path-map.h"
21 #include "soup-session.h"
22 #include "soup-session-feature.h"
23 #include "soup-session-private.h"
26 static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
27 static SoupSessionFeatureInterface *soup_session_feature_default_interface;
29 static void attach (SoupSessionFeature *feature, SoupSession *session);
30 static void request_queued (SoupSessionFeature *feature, SoupSession *session,
32 static void request_started (SoupSessionFeature *feature, SoupSession *session,
33 SoupMessage *msg, SoupSocket *socket);
34 static void request_unqueued (SoupSessionFeature *feature,
35 SoupSession *session, SoupMessage *msg);
36 static gboolean add_feature (SoupSessionFeature *feature, GType type);
37 static gboolean remove_feature (SoupSessionFeature *feature, GType type);
38 static gboolean has_feature (SoupSessionFeature *feature, GType type);
45 static guint signals[LAST_SIGNAL] = { 0 };
47 G_DEFINE_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT,
48 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
49 soup_auth_manager_session_feature_init))
53 GPtrArray *auth_types;
56 GHashTable *auth_hosts;
57 } SoupAuthManagerPrivate;
58 #define SOUP_AUTH_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerPrivate))
62 SoupPathMap *auth_realms; /* path -> scheme:realm */
63 GHashTable *auths; /* scheme:realm -> SoupAuth */
67 soup_auth_manager_init (SoupAuthManager *manager)
69 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
71 priv->auth_types = g_ptr_array_new ();
72 priv->auth_hosts = g_hash_table_new (soup_uri_host_hash,
77 foreach_free_host (gpointer key, gpointer value, gpointer data)
79 SoupAuthHost *host = value;
81 if (host->auth_realms)
82 soup_path_map_free (host->auth_realms);
84 g_hash_table_destroy (host->auths);
86 soup_uri_free (host->uri);
87 g_slice_free (SoupAuthHost, host);
93 finalize (GObject *object)
95 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (object);
98 for (i = 0; i < priv->auth_types->len; i++)
99 g_type_class_unref (priv->auth_types->pdata[i]);
100 g_ptr_array_free (priv->auth_types, TRUE);
102 g_hash_table_foreach_remove (priv->auth_hosts, foreach_free_host, NULL);
103 g_hash_table_destroy (priv->auth_hosts);
105 if (priv->proxy_auth)
106 g_object_unref (priv->proxy_auth);
108 G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object);
112 soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class)
114 GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_class);
116 g_type_class_add_private (auth_manager_class, sizeof (SoupAuthManagerPrivate));
118 object_class->finalize = finalize;
120 signals[AUTHENTICATE] =
121 g_signal_new ("authenticate",
122 G_OBJECT_CLASS_TYPE (object_class),
124 G_STRUCT_OFFSET (SoupAuthManagerClass, authenticate),
126 _soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN,
135 soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface,
136 gpointer interface_data)
138 soup_session_feature_default_interface =
139 g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
141 feature_interface->attach = attach;
142 feature_interface->request_queued = request_queued;
143 feature_interface->request_started = request_started;
144 feature_interface->request_unqueued = request_unqueued;
145 feature_interface->add_feature = add_feature;
146 feature_interface->remove_feature = remove_feature;
147 feature_interface->has_feature = has_feature;
151 auth_type_compare_func (gconstpointer a, gconstpointer b)
153 SoupAuthClass **auth1 = (SoupAuthClass **)a;
154 SoupAuthClass **auth2 = (SoupAuthClass **)b;
156 return (*auth1)->strength - (*auth2)->strength;
160 add_feature (SoupSessionFeature *feature, GType type)
162 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature);
163 SoupAuthClass *auth_class;
165 if (!g_type_is_a (type, SOUP_TYPE_AUTH))
168 auth_class = g_type_class_ref (type);
169 g_ptr_array_add (priv->auth_types, auth_class);
170 g_ptr_array_sort (priv->auth_types, auth_type_compare_func);
175 remove_feature (SoupSessionFeature *feature, GType type)
177 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature);
178 SoupAuthClass *auth_class;
181 if (!g_type_is_a (type, SOUP_TYPE_AUTH))
184 auth_class = g_type_class_peek (type);
185 for (i = 0; i < priv->auth_types->len; i++) {
186 if (priv->auth_types->pdata[i] == (gpointer)auth_class) {
187 g_ptr_array_remove_index (priv->auth_types, i);
188 g_type_class_unref (auth_class);
197 has_feature (SoupSessionFeature *feature, GType type)
199 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature);
200 SoupAuthClass *auth_class;
203 if (!g_type_is_a (type, SOUP_TYPE_AUTH))
206 auth_class = g_type_class_peek (type);
207 for (i = 0; i < priv->auth_types->len; i++) {
208 if (priv->auth_types->pdata[i] == (gpointer)auth_class)
215 soup_auth_manager_emit_authenticate (SoupAuthManager *manager, SoupMessage *msg,
216 SoupAuth *auth, gboolean retrying)
218 g_signal_emit (manager, signals[AUTHENTICATE], 0, msg, auth, retrying);
222 attach (SoupSessionFeature *manager, SoupSession *session)
224 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
226 /* FIXME: should support multiple sessions */
227 priv->session = session;
229 soup_session_feature_default_interface->attach (manager, session);
232 static inline const char *
233 auth_header_for_message (SoupMessage *msg)
235 if (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) {
236 return soup_message_headers_get_list (msg->response_headers,
237 "Proxy-Authenticate");
239 return soup_message_headers_get_list (msg->response_headers,
245 next_challenge_start (GSList *items)
247 /* The relevant grammar (from httpbis):
249 * WWW-Authenticate = 1#challenge
250 * Proxy-Authenticate = 1#challenge
251 * challenge = auth-scheme [ 1*SP ( b64token / #auth-param ) ]
252 * auth-scheme = token
253 * auth-param = token BWS "=" BWS ( token / quoted-string )
254 * b64token = 1*( ALPHA / DIGIT /
255 * "-" / "." / "_" / "~" / "+" / "/" ) *"="
257 * The fact that quoted-strings can contain commas, equals
258 * signs, and auth scheme names makes it tricky to "cheat" on
259 * the parsing. So soup_auth_manager_extract_challenge() will
260 * have used soup_header_parse_list() to split the header into
261 * items. Given the grammar above, the possible items are:
264 * auth-scheme 1*SP b64token
265 * auth-scheme 1*SP auth-param
268 * where the first three represent the start of a new challenge and
269 * the last one does not.
272 for (; items; items = items->next) {
273 const char *item = items->data;
274 const char *sp = strpbrk (item, "\t\r\n ");
275 const char *eq = strchr (item, '=');
278 /* No "=", so it can't be an auth-param */
281 if (!sp || sp > eq) {
282 /* No space, or first space appears after the "=",
283 * so it must be an auth-param.
287 while (g_ascii_isspace (*++sp))
290 /* First "=" appears immediately after the first
291 * space, so this must be an auth-param with
292 * space around the "=".
297 /* "auth-scheme auth-param" or "auth-scheme b64token" */
305 soup_auth_manager_extract_challenge (const char *challenges, const char *scheme)
307 GSList *items, *i, *next;
308 int schemelen = strlen (scheme);
312 items = soup_header_parse_list (challenges);
314 /* First item will start with the scheme name, followed by
315 * either nothing, or else a space and then the first
318 for (i = items; i; i = next_challenge_start (i->next)) {
320 if (!g_ascii_strncasecmp (item, scheme, schemelen) &&
321 (!item[schemelen] || g_ascii_isspace (item[schemelen])))
325 soup_header_free_list (items);
329 next = next_challenge_start (i->next);
330 challenge = g_string_new (item);
331 for (i = i->next; i != next; i = i->next) {
333 g_string_append (challenge, ", ");
334 g_string_append (challenge, item);
337 soup_header_free_list (items);
338 return g_string_free (challenge, FALSE);
342 create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
345 SoupAuthClass *auth_class;
346 char *challenge = NULL;
350 header = auth_header_for_message (msg);
354 for (i = priv->auth_types->len - 1; i >= 0; i--) {
355 auth_class = priv->auth_types->pdata[i];
356 challenge = soup_auth_manager_extract_challenge (header, auth_class->scheme_name);
363 auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenge);
369 check_auth (SoupMessage *msg, SoupAuth *auth)
375 header = auth_header_for_message (msg);
379 challenge = soup_auth_manager_extract_challenge (header, soup_auth_get_scheme_name (auth));
383 ok = soup_auth_update (auth, msg, challenge);
388 static SoupAuthHost *
389 get_auth_host_for_message (SoupAuthManagerPrivate *priv, SoupMessage *msg)
392 SoupURI *uri = soup_message_get_uri (msg);
394 host = g_hash_table_lookup (priv->auth_hosts, uri);
398 host = g_slice_new0 (SoupAuthHost);
399 host->uri = soup_uri_copy_host (uri);
400 g_hash_table_insert (priv->auth_hosts, host->uri, host);
406 lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
409 const char *path, *realm;
411 host = get_auth_host_for_message (priv, msg);
412 if (!host->auth_realms)
415 path = soup_message_get_uri (msg)->path;
418 realm = soup_path_map_lookup (host->auth_realms, path);
420 return g_hash_table_lookup (host->auths, realm);
426 authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
427 SoupMessage *msg, gboolean prior_auth_failed,
428 gboolean proxy, gboolean can_interact)
430 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
434 SoupMessageQueue *queue;
435 SoupMessageQueueItem *item;
437 queue = soup_session_get_queue (priv->session);
438 item = soup_message_queue_lookup (queue, msg);
440 uri = soup_connection_get_proxy_uri (item->conn);
441 soup_message_queue_item_unref (item);
448 uri = soup_message_get_uri (msg);
450 /* If a password is specified explicitly in the URI, use it
451 * even if the auth had previously already been authenticated.
454 if (!prior_auth_failed)
455 soup_auth_authenticate (auth, uri->user, uri->password);
456 } else if (!soup_auth_is_authenticated (auth) && can_interact) {
457 soup_auth_manager_emit_authenticate (manager, msg, auth,
461 return soup_auth_is_authenticated (auth);
465 update_auth (SoupMessage *msg, gpointer manager)
467 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
469 SoupAuth *auth, *prior_auth, *old_auth;
471 char *auth_info, *old_auth_info;
473 gboolean prior_auth_failed = FALSE;
475 host = get_auth_host_for_message (priv, msg);
477 /* See if we used auth last time */
478 prior_auth = soup_message_get_auth (msg);
479 if (prior_auth && check_auth (msg, prior_auth)) {
481 if (!soup_auth_is_authenticated (auth))
482 prior_auth_failed = TRUE;
484 auth = create_auth (priv, msg);
488 auth_info = soup_auth_get_info (auth);
490 if (!host->auth_realms) {
491 host->auth_realms = soup_path_map_new (g_free);
492 host->auths = g_hash_table_new_full (g_str_hash, g_str_equal,
493 g_free, g_object_unref);
496 /* Record where this auth realm is used. */
497 pspace = soup_auth_get_protection_space (auth, soup_message_get_uri (msg));
498 for (p = pspace; p; p = p->next) {
500 old_auth_info = soup_path_map_lookup (host->auth_realms, path);
502 if (!strcmp (old_auth_info, auth_info))
504 soup_path_map_remove (host->auth_realms, path);
507 soup_path_map_add (host->auth_realms, path,
508 g_strdup (auth_info));
510 soup_auth_free_protection_space (auth, pspace);
512 /* Now, make sure the auth is recorded. (If there's a
513 * pre-existing auth, we keep that rather than the new one,
514 * since the old one might already be authenticated.)
516 old_auth = g_hash_table_lookup (host->auths, auth_info);
519 if (auth != old_auth && auth != prior_auth) {
520 g_object_unref (auth);
524 g_hash_table_insert (host->auths, auth_info, auth);
527 /* If we need to authenticate, try to do it. */
528 authenticate_auth (manager, auth, msg,
529 prior_auth_failed, FALSE, TRUE);
533 requeue_if_authenticated (SoupMessage *msg, gpointer manager)
535 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
536 SoupAuth *auth = lookup_auth (priv, msg);
538 if (auth && soup_auth_is_authenticated (auth))
539 soup_session_requeue_message (priv->session, msg);
543 update_proxy_auth (SoupMessage *msg, gpointer manager)
545 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
546 SoupAuth *prior_auth;
547 gboolean prior_auth_failed = FALSE;
549 /* See if we used auth last time */
550 prior_auth = soup_message_get_proxy_auth (msg);
551 if (prior_auth && check_auth (msg, prior_auth)) {
552 if (!soup_auth_is_authenticated (prior_auth))
553 prior_auth_failed = TRUE;
556 if (!priv->proxy_auth) {
557 priv->proxy_auth = create_auth (priv, msg);
558 if (!priv->proxy_auth)
562 /* If we need to authenticate, try to do it. */
563 authenticate_auth (manager, priv->proxy_auth, msg,
564 prior_auth_failed, TRUE, TRUE);
568 requeue_if_proxy_authenticated (SoupMessage *msg, gpointer manager)
570 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
571 SoupAuth *auth = priv->proxy_auth;
573 if (auth && soup_auth_is_authenticated (auth))
574 soup_session_requeue_message (priv->session, msg);
578 request_queued (SoupSessionFeature *manager, SoupSession *session,
581 soup_message_add_status_code_handler (
582 msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
583 G_CALLBACK (update_auth), manager);
584 soup_message_add_status_code_handler (
585 msg, "got_body", SOUP_STATUS_UNAUTHORIZED,
586 G_CALLBACK (requeue_if_authenticated), manager);
588 soup_message_add_status_code_handler (
589 msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED,
590 G_CALLBACK (update_proxy_auth), manager);
591 soup_message_add_status_code_handler (
592 msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED,
593 G_CALLBACK (requeue_if_proxy_authenticated), manager);
597 request_started (SoupSessionFeature *feature, SoupSession *session,
598 SoupMessage *msg, SoupSocket *socket)
600 SoupAuthManager *manager = SOUP_AUTH_MANAGER (feature);
601 SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
604 auth = lookup_auth (priv, msg);
605 if (!auth || !authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE))
607 soup_message_set_auth (msg, auth);
609 auth = priv->proxy_auth;
610 if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE))
612 soup_message_set_proxy_auth (msg, auth);
616 request_unqueued (SoupSessionFeature *manager, SoupSession *session,
619 g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA,
620 0, 0, NULL, NULL, manager);