Change the SoupURI properties to SoupAddress properties.
[platform/upstream/libsoup.git] / libsoup / soup-auth-manager.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-auth-manager.c: SoupAuth manager for SoupSession
4  *
5  * Copyright (C) 2007 Red Hat, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <string.h>
13
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"
22 #include "soup-uri.h"
23
24 static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
25 static SoupSessionFeatureInterface *soup_session_feature_default_interface;
26
27 static void attach (SoupSessionFeature *feature, SoupSession *session);
28 static void request_queued  (SoupSessionFeature *feature, SoupSession *session,
29                              SoupMessage *msg);
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);
34
35 enum {
36         AUTHENTICATE,
37         LAST_SIGNAL
38 };
39
40 static guint signals[LAST_SIGNAL] = { 0 };
41
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))
45
46 typedef struct {
47         SoupSession *session;
48         GPtrArray *auth_types;
49
50         SoupAuth *proxy_auth;
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))
54
55 typedef struct {
56         SoupAddress *addr;
57         SoupPathMap *auth_realms;      /* path -> scheme:realm */
58         GHashTable  *auths;            /* scheme:realm -> SoupAuth */
59 } SoupAuthHost;
60
61 static void
62 soup_auth_manager_init (SoupAuthManager *manager)
63 {
64         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
65
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);
69 }
70
71 static gboolean
72 foreach_free_host (gpointer key, gpointer value, gpointer data)
73 {
74         SoupAuthHost *host = value;
75
76         if (host->auth_realms)
77                 soup_path_map_free (host->auth_realms);
78         if (host->auths)
79                 g_hash_table_destroy (host->auths);
80
81         g_object_unref (host->addr);
82         g_slice_free (SoupAuthHost, host);
83
84         return TRUE;
85 }
86
87 static void
88 finalize (GObject *object)
89 {
90         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (object);
91         int i;
92
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);
96
97         g_hash_table_foreach_remove (priv->auth_hosts, foreach_free_host, NULL);
98         g_hash_table_destroy (priv->auth_hosts);
99
100         if (priv->proxy_auth)
101                 g_object_unref (priv->proxy_auth);
102
103         G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object);
104 }
105
106 static void
107 soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class)
108 {
109         GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_class);
110
111         g_type_class_add_private (auth_manager_class, sizeof (SoupAuthManagerPrivate));
112
113         object_class->finalize = finalize;
114
115         signals[AUTHENTICATE] =
116                 g_signal_new ("authenticate",
117                               G_OBJECT_CLASS_TYPE (object_class),
118                               G_SIGNAL_RUN_FIRST,
119                               G_STRUCT_OFFSET (SoupAuthManagerClass, authenticate),
120                               NULL, NULL,
121                               soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN,
122                               G_TYPE_NONE, 3,
123                               SOUP_TYPE_MESSAGE,
124                               SOUP_TYPE_AUTH,
125                               G_TYPE_BOOLEAN);
126
127 }
128
129 static void
130 soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface,
131                                         gpointer interface_data)
132 {
133         soup_session_feature_default_interface =
134                 g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
135
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;
140 }
141
142 static int
143 auth_type_compare_func (gconstpointer a, gconstpointer b)
144 {
145         SoupAuthClass **auth1 = (SoupAuthClass **)a;
146         SoupAuthClass **auth2 = (SoupAuthClass **)b;
147
148         return (*auth2)->strength - (*auth1)->strength;
149 }
150
151 void
152 soup_auth_manager_add_type (SoupAuthManager *manager, GType type)
153 {
154         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
155         SoupAuthClass *auth_class;
156
157         g_return_if_fail (g_type_is_a (type, SOUP_TYPE_AUTH));
158
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);
162 }
163
164 void
165 soup_auth_manager_remove_type (SoupAuthManager *manager, GType type)
166 {
167         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
168         SoupAuthClass *auth_class;
169         int i;
170
171         g_return_if_fail (g_type_is_a (type, SOUP_TYPE_AUTH));
172
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);
178                         return;
179                 }
180         }
181 }
182
183 void
184 soup_auth_manager_emit_authenticate (SoupAuthManager *manager, SoupMessage *msg,
185                                      SoupAuth *auth, gboolean retrying)
186 {
187         g_signal_emit (manager, signals[AUTHENTICATE], 0, msg, auth, retrying);
188 }
189
190 static void
191 attach (SoupSessionFeature *manager, SoupSession *session)
192 {
193         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
194
195         /* FIXME: should support multiple sessions */
196         priv->session = session;
197
198         soup_session_feature_default_interface->attach (manager, session);
199 }
200
201 static inline const char *
202 auth_header_for_message (SoupMessage *msg)
203 {
204         if (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) {
205                 return soup_message_headers_get (msg->response_headers,
206                                                  "Proxy-Authenticate");
207         } else {
208                 return soup_message_headers_get (msg->response_headers,
209                                                  "WWW-Authenticate");
210         }
211 }
212
213 static char *
214 extract_challenge (const char *challenges, const char *scheme)
215 {
216         GSList *items, *i;
217         int schemelen = strlen (scheme);
218         char *item, *space, *equals;
219         GString *challenge;
220
221         /* The relevant grammar:
222          *
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 )
228          *
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.
233          */
234
235         items = soup_header_parse_list (challenges);
236
237         /* First item will start with the scheme name, followed by a
238          * space and then the first auth-param.
239          */
240         for (i = items; i; i = i->next) {
241                 item = i->data;
242                 if (!g_ascii_strncasecmp (item, scheme, schemelen) &&
243                     g_ascii_isspace (item[schemelen]))
244                         break;
245         }
246         if (!i) {
247                 soup_header_free_list (items);
248                 return NULL;
249         }
250
251         /* The challenge extends from this item until the end, or until
252          * the next item that has a space before an equals sign.
253          */
254         challenge = g_string_new (item);
255         for (i = i->next; i; i = i->next) {
256                 item = i->data;
257                 space = strpbrk (item, " \t");
258                 equals = strchr (item, '=');
259                 if (!equals || (space && equals > space))
260                         break;
261
262                 g_string_append (challenge, ", ");
263                 g_string_append (challenge, item);
264         }
265
266         soup_header_free_list (items);
267         return g_string_free (challenge, FALSE);
268 }
269
270 static SoupAuth *
271 create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
272 {
273         const char *header;
274         SoupAuthClass *auth_class;
275         char *challenge = NULL;
276         SoupAuth *auth;
277         int i;
278
279         header = auth_header_for_message (msg);
280         if (!header)
281                 return NULL;
282
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);
286                 if (challenge)
287                         break;
288         }
289         if (!challenge)
290                 return NULL;
291
292         auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenge);
293         g_free (challenge);
294         return auth;
295 }
296
297 static gboolean
298 check_auth (SoupMessage *msg, SoupAuth *auth)
299 {
300         const char *header;
301         char *challenge;
302         gboolean ok;
303
304         header = auth_header_for_message (msg);
305         if (!header)
306                 return FALSE;
307
308         challenge = extract_challenge (header, soup_auth_get_scheme_name (auth));
309         if (!challenge)
310                 return FALSE;
311
312         ok = soup_auth_update (auth, msg, challenge);
313         g_free (challenge);
314         return ok;
315 }
316
317 static SoupAuthHost *
318 get_auth_host_for_message (SoupAuthManagerPrivate *priv, SoupMessage *msg)
319 {
320         SoupAuthHost *host;
321         SoupAddress *addr = soup_message_get_address (msg);
322
323         host = g_hash_table_lookup (priv->auth_hosts, addr);
324         if (host)
325                 return host;
326
327         host = g_slice_new0 (SoupAuthHost);
328         host->addr = g_object_ref (addr);
329         g_hash_table_insert (priv->auth_hosts, host->addr, host);
330
331         return host;
332 }
333
334 static SoupAuth *
335 lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
336 {
337         SoupAuthHost *host;
338         const char *path, *realm;
339
340         host = get_auth_host_for_message (priv, msg);
341         if (!host->auth_realms)
342                 return NULL;
343
344         path = soup_message_get_uri (msg)->path;
345         if (!path)
346                 path = "/";
347         realm = soup_path_map_lookup (host->auth_realms, path);
348         if (realm)
349                 return g_hash_table_lookup (host->auths, realm);
350         else
351                 return NULL;
352 }
353
354 static gboolean
355 authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
356                    SoupMessage *msg, gboolean prior_auth_failed,
357                    gboolean proxy)
358 {
359         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
360         SoupURI *uri;
361
362         if (soup_auth_is_authenticated (auth))
363                 return TRUE;
364
365         if (proxy) {
366                 g_object_get (G_OBJECT (priv->session),
367                               SOUP_SESSION_PROXY_URI, &uri,
368                               NULL);
369         } else
370                 uri = soup_uri_copy (soup_message_get_uri (msg));
371
372         if (uri->password && !prior_auth_failed) {
373                 soup_auth_authenticate (auth, uri->user, uri->password);
374                 soup_uri_free (uri);
375                 return TRUE;
376         }
377         soup_uri_free (uri);
378
379         soup_auth_manager_emit_authenticate (manager, msg, auth,
380                                              prior_auth_failed);
381         return soup_auth_is_authenticated (auth);
382 }
383
384 static void
385 update_auth (SoupMessage *msg, gpointer manager)
386 {
387         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
388         SoupAuthHost *host;
389         SoupAuth *auth, *prior_auth, *old_auth;
390         const char *path;
391         char *auth_info, *old_auth_info;
392         GSList *pspace, *p;
393         gboolean prior_auth_failed = FALSE;
394
395         host = get_auth_host_for_message (priv, msg);
396
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)) {
400                 auth = prior_auth;
401                 if (!soup_auth_is_authenticated (auth))
402                         prior_auth_failed = TRUE;
403         } else {
404                 auth = create_auth (priv, msg);
405                 if (!auth)
406                         return;
407         }
408         auth_info = soup_auth_get_info (auth);
409
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);
414         }
415
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) {
419                 path = p->data;
420                 old_auth_info = soup_path_map_lookup (host->auth_realms, path);
421                 if (old_auth_info) {
422                         if (!strcmp (old_auth_info, auth_info))
423                                 continue;
424                         soup_path_map_remove (host->auth_realms, path);
425                 }
426
427                 soup_path_map_add (host->auth_realms, path,
428                                    g_strdup (auth_info));
429         }
430         soup_auth_free_protection_space (auth, pspace);
431
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.)
435          */
436         old_auth = g_hash_table_lookup (host->auths, auth_info);
437         if (old_auth) {
438                 g_free (auth_info);
439                 if (auth != old_auth && auth != prior_auth) {
440                         g_object_unref (auth);
441                         auth = old_auth;
442                 }
443         } else {
444                 g_hash_table_insert (host->auths, auth_info, auth);
445         }
446
447         /* If we need to authenticate, try to do it. */
448         authenticate_auth (manager, auth, msg,
449                            prior_auth_failed, FALSE);
450 }
451
452 static void
453 requeue_if_authenticated (SoupMessage *msg, gpointer manager)
454 {
455         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
456         SoupAuth *auth = lookup_auth (priv, msg);
457
458         if (auth && soup_auth_is_authenticated (auth))
459                 soup_session_requeue_message (priv->session, msg);
460 }
461
462 static void
463 update_proxy_auth (SoupMessage *msg, gpointer manager)
464 {
465         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
466         SoupAuth *prior_auth;
467         gboolean prior_auth_failed = FALSE;
468
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;
474         }
475
476         if (!priv->proxy_auth) {
477                 priv->proxy_auth = create_auth (priv, msg);
478                 if (!priv->proxy_auth)
479                         return;
480         }
481
482         /* If we need to authenticate, try to do it. */
483         authenticate_auth (manager, priv->proxy_auth, msg,
484                            prior_auth_failed, TRUE);
485 }
486
487 static void
488 requeue_if_proxy_authenticated (SoupMessage *msg, gpointer manager)
489 {
490         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
491         SoupAuth *auth = priv->proxy_auth;
492
493         if (auth && soup_auth_is_authenticated (auth))
494                 soup_session_requeue_message (priv->session, msg);
495 }
496
497 static void
498 request_queued (SoupSessionFeature *manager, SoupSession *session,
499                 SoupMessage *msg)
500 {
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);
507
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);
514 }
515
516 static void
517 request_started (SoupSessionFeature *feature, SoupSession *session,
518                  SoupMessage *msg, SoupSocket *socket)
519 {
520         SoupAuthManager *manager = SOUP_AUTH_MANAGER (feature);
521         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
522         SoupAuth *auth;
523
524         auth = lookup_auth (priv, msg);
525         if (!auth || !authenticate_auth (manager, auth, msg, FALSE, FALSE))
526                 auth = NULL;
527         soup_message_set_auth (msg, auth);
528
529         auth = priv->proxy_auth;
530         if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE))
531                 auth = NULL;
532         soup_message_set_proxy_auth (msg, auth);
533 }
534
535 static void
536 request_unqueued (SoupSessionFeature *manager, SoupSession *session,
537                   SoupMessage *msg)
538 {
539         g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA,
540                                               0, 0, NULL, NULL, manager);
541 }