Reapplying patch to disable attempts to use gtk-doc
[profile/ivi/libsoup2.4.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-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"
24 #include "soup-uri.h"
25
26 static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
27 static SoupSessionFeatureInterface *soup_session_feature_default_interface;
28
29 static void attach (SoupSessionFeature *feature, SoupSession *session);
30 static void request_queued  (SoupSessionFeature *feature, SoupSession *session,
31                              SoupMessage *msg);
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);
39
40 enum {
41         AUTHENTICATE,
42         LAST_SIGNAL
43 };
44
45 static guint signals[LAST_SIGNAL] = { 0 };
46
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))
50
51 typedef struct {
52         SoupSession *session;
53         GPtrArray *auth_types;
54
55         SoupAuth *proxy_auth;
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))
59
60 typedef struct {
61         SoupURI     *uri;
62         SoupPathMap *auth_realms;      /* path -> scheme:realm */
63         GHashTable  *auths;            /* scheme:realm -> SoupAuth */
64 } SoupAuthHost;
65
66 static void
67 soup_auth_manager_init (SoupAuthManager *manager)
68 {
69         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
70
71         priv->auth_types = g_ptr_array_new ();
72         priv->auth_hosts = g_hash_table_new (soup_uri_host_hash,
73                                              soup_uri_host_equal);
74 }
75
76 static gboolean
77 foreach_free_host (gpointer key, gpointer value, gpointer data)
78 {
79         SoupAuthHost *host = value;
80
81         if (host->auth_realms)
82                 soup_path_map_free (host->auth_realms);
83         if (host->auths)
84                 g_hash_table_destroy (host->auths);
85
86         soup_uri_free (host->uri);
87         g_slice_free (SoupAuthHost, host);
88
89         return TRUE;
90 }
91
92 static void
93 finalize (GObject *object)
94 {
95         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (object);
96         int i;
97
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);
101
102         g_hash_table_foreach_remove (priv->auth_hosts, foreach_free_host, NULL);
103         g_hash_table_destroy (priv->auth_hosts);
104
105         if (priv->proxy_auth)
106                 g_object_unref (priv->proxy_auth);
107
108         G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object);
109 }
110
111 static void
112 soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class)
113 {
114         GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_class);
115
116         g_type_class_add_private (auth_manager_class, sizeof (SoupAuthManagerPrivate));
117
118         object_class->finalize = finalize;
119
120         signals[AUTHENTICATE] =
121                 g_signal_new ("authenticate",
122                               G_OBJECT_CLASS_TYPE (object_class),
123                               G_SIGNAL_RUN_FIRST,
124                               G_STRUCT_OFFSET (SoupAuthManagerClass, authenticate),
125                               NULL, NULL,
126                               _soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN,
127                               G_TYPE_NONE, 3,
128                               SOUP_TYPE_MESSAGE,
129                               SOUP_TYPE_AUTH,
130                               G_TYPE_BOOLEAN);
131
132 }
133
134 static void
135 soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface,
136                                         gpointer interface_data)
137 {
138         soup_session_feature_default_interface =
139                 g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
140
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;
148 }
149
150 static int
151 auth_type_compare_func (gconstpointer a, gconstpointer b)
152 {
153         SoupAuthClass **auth1 = (SoupAuthClass **)a;
154         SoupAuthClass **auth2 = (SoupAuthClass **)b;
155
156         return (*auth1)->strength - (*auth2)->strength;
157 }
158
159 static gboolean
160 add_feature (SoupSessionFeature *feature, GType type)
161 {
162         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature);
163         SoupAuthClass *auth_class;
164
165         if (!g_type_is_a (type, SOUP_TYPE_AUTH))
166                 return FALSE;
167
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);
171         return TRUE;
172 }
173
174 static gboolean
175 remove_feature (SoupSessionFeature *feature, GType type)
176 {
177         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature);
178         SoupAuthClass *auth_class;
179         int i;
180
181         if (!g_type_is_a (type, SOUP_TYPE_AUTH))
182                 return FALSE;
183
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);
189                         return TRUE;
190                 }
191         }
192
193         return FALSE;
194 }
195
196 static gboolean
197 has_feature (SoupSessionFeature *feature, GType type)
198 {
199         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature);
200         SoupAuthClass *auth_class;
201         int i;
202
203         if (!g_type_is_a (type, SOUP_TYPE_AUTH))
204                 return FALSE;
205
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)
209                         return TRUE;
210         }
211         return FALSE;
212 }
213
214 void
215 soup_auth_manager_emit_authenticate (SoupAuthManager *manager, SoupMessage *msg,
216                                      SoupAuth *auth, gboolean retrying)
217 {
218         g_signal_emit (manager, signals[AUTHENTICATE], 0, msg, auth, retrying);
219 }
220
221 static void
222 attach (SoupSessionFeature *manager, SoupSession *session)
223 {
224         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
225
226         /* FIXME: should support multiple sessions */
227         priv->session = session;
228
229         soup_session_feature_default_interface->attach (manager, session);
230 }
231
232 static inline const char *
233 auth_header_for_message (SoupMessage *msg)
234 {
235         if (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) {
236                 return soup_message_headers_get_list (msg->response_headers,
237                                                       "Proxy-Authenticate");
238         } else {
239                 return soup_message_headers_get_list (msg->response_headers,
240                                                       "WWW-Authenticate");
241         }
242 }
243
244 static GSList *
245 next_challenge_start (GSList *items)
246 {
247         /* The relevant grammar (from httpbis):
248          *
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          *                          "-" / "." / "_" / "~" / "+" / "/" ) *"="
256          *
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:
262          *
263          *   auth-scheme
264          *   auth-scheme 1*SP b64token
265          *   auth-scheme 1*SP auth-param
266          *   auth-param
267          *
268          * where the first three represent the start of a new challenge and
269          * the last one does not.
270          */
271
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, '=');
276
277                 if (!eq) {
278                         /* No "=", so it can't be an auth-param */
279                         return items;
280                 }
281                 if (!sp || sp > eq) {
282                         /* No space, or first space appears after the "=",
283                          * so it must be an auth-param.
284                          */
285                         continue;
286                 }
287                 while (g_ascii_isspace (*++sp))
288                         ;
289                 if (sp == eq) {
290                         /* First "=" appears immediately after the first
291                          * space, so this must be an auth-param with
292                          * space around the "=".
293                          */
294                         continue;
295                 }
296
297                 /* "auth-scheme auth-param" or "auth-scheme b64token" */
298                 return items;
299         }
300
301         return NULL;
302 }
303
304 char *
305 soup_auth_manager_extract_challenge (const char *challenges, const char *scheme)
306 {
307         GSList *items, *i, *next;
308         int schemelen = strlen (scheme);
309         char *item;
310         GString *challenge;
311
312         items = soup_header_parse_list (challenges);
313
314         /* First item will start with the scheme name, followed by
315          * either nothing, or else a space and then the first
316          * auth-param.
317          */
318         for (i = items; i; i = next_challenge_start (i->next)) {
319                 item = i->data;
320                 if (!g_ascii_strncasecmp (item, scheme, schemelen) &&
321                     (!item[schemelen] || g_ascii_isspace (item[schemelen])))
322                         break;
323         }
324         if (!i) {
325                 soup_header_free_list (items);
326                 return NULL;
327         }
328
329         next = next_challenge_start (i->next);
330         challenge = g_string_new (item);
331         for (i = i->next; i != next; i = i->next) {
332                 item = i->data;
333                 g_string_append (challenge, ", ");
334                 g_string_append (challenge, item);
335         }
336
337         soup_header_free_list (items);
338         return g_string_free (challenge, FALSE);
339 }
340
341 static SoupAuth *
342 create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
343 {
344         const char *header;
345         SoupAuthClass *auth_class;
346         char *challenge = NULL;
347         SoupAuth *auth;
348         int i;
349
350         header = auth_header_for_message (msg);
351         if (!header)
352                 return NULL;
353
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);
357                 if (challenge)
358                         break;
359         }
360         if (!challenge)
361                 return NULL;
362
363         auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenge);
364         g_free (challenge);
365         return auth;
366 }
367
368 static gboolean
369 check_auth (SoupMessage *msg, SoupAuth *auth)
370 {
371         const char *header;
372         char *challenge;
373         gboolean ok;
374
375         header = auth_header_for_message (msg);
376         if (!header)
377                 return FALSE;
378
379         challenge = soup_auth_manager_extract_challenge (header, soup_auth_get_scheme_name (auth));
380         if (!challenge)
381                 return FALSE;
382
383         ok = soup_auth_update (auth, msg, challenge);
384         g_free (challenge);
385         return ok;
386 }
387
388 static SoupAuthHost *
389 get_auth_host_for_message (SoupAuthManagerPrivate *priv, SoupMessage *msg)
390 {
391         SoupAuthHost *host;
392         SoupURI *uri = soup_message_get_uri (msg);
393
394         host = g_hash_table_lookup (priv->auth_hosts, uri);
395         if (host)
396                 return host;
397
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);
401
402         return host;
403 }
404
405 static SoupAuth *
406 lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
407 {
408         SoupAuthHost *host;
409         const char *path, *realm;
410
411         host = get_auth_host_for_message (priv, msg);
412         if (!host->auth_realms)
413                 return NULL;
414
415         path = soup_message_get_uri (msg)->path;
416         if (!path)
417                 path = "/";
418         realm = soup_path_map_lookup (host->auth_realms, path);
419         if (realm)
420                 return g_hash_table_lookup (host->auths, realm);
421         else
422                 return NULL;
423 }
424
425 static gboolean
426 authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
427                    SoupMessage *msg, gboolean prior_auth_failed,
428                    gboolean proxy, gboolean can_interact)
429 {
430         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
431         SoupURI *uri;
432
433         if (proxy) {
434                 SoupMessageQueue *queue;
435                 SoupMessageQueueItem *item;
436
437                 queue = soup_session_get_queue (priv->session);
438                 item = soup_message_queue_lookup (queue, msg);
439                 if (item) {
440                         uri = soup_connection_get_proxy_uri (item->conn);
441                         soup_message_queue_item_unref (item);
442                 } else
443                         uri = NULL;
444
445                 if (!uri)
446                         return FALSE;
447         } else
448                 uri = soup_message_get_uri (msg);
449
450         /* If a password is specified explicitly in the URI, use it
451          * even if the auth had previously already been authenticated.
452          */
453         if (uri->password) {
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,
458                                                      prior_auth_failed);
459         }
460
461         return soup_auth_is_authenticated (auth);
462 }
463
464 static void
465 update_auth (SoupMessage *msg, gpointer manager)
466 {
467         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
468         SoupAuthHost *host;
469         SoupAuth *auth, *prior_auth, *old_auth;
470         const char *path;
471         char *auth_info, *old_auth_info;
472         GSList *pspace, *p;
473         gboolean prior_auth_failed = FALSE;
474
475         host = get_auth_host_for_message (priv, msg);
476
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)) {
480                 auth = prior_auth;
481                 if (!soup_auth_is_authenticated (auth))
482                         prior_auth_failed = TRUE;
483         } else {
484                 auth = create_auth (priv, msg);
485                 if (!auth)
486                         return;
487         }
488         auth_info = soup_auth_get_info (auth);
489
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);
494         }
495
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) {
499                 path = p->data;
500                 old_auth_info = soup_path_map_lookup (host->auth_realms, path);
501                 if (old_auth_info) {
502                         if (!strcmp (old_auth_info, auth_info))
503                                 continue;
504                         soup_path_map_remove (host->auth_realms, path);
505                 }
506
507                 soup_path_map_add (host->auth_realms, path,
508                                    g_strdup (auth_info));
509         }
510         soup_auth_free_protection_space (auth, pspace);
511
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.)
515          */
516         old_auth = g_hash_table_lookup (host->auths, auth_info);
517         if (old_auth) {
518                 g_free (auth_info);
519                 if (auth != old_auth && auth != prior_auth) {
520                         g_object_unref (auth);
521                         auth = old_auth;
522                 }
523         } else {
524                 g_hash_table_insert (host->auths, auth_info, auth);
525         }
526
527         /* If we need to authenticate, try to do it. */
528         authenticate_auth (manager, auth, msg,
529                            prior_auth_failed, FALSE, TRUE);
530 }
531
532 static void
533 requeue_if_authenticated (SoupMessage *msg, gpointer manager)
534 {
535         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
536         SoupAuth *auth = lookup_auth (priv, msg);
537
538         if (auth && soup_auth_is_authenticated (auth))
539                 soup_session_requeue_message (priv->session, msg);
540 }
541
542 static void
543 update_proxy_auth (SoupMessage *msg, gpointer manager)
544 {
545         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
546         SoupAuth *prior_auth;
547         gboolean prior_auth_failed = FALSE;
548
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;
554         }
555
556         if (!priv->proxy_auth) {
557                 priv->proxy_auth = create_auth (priv, msg);
558                 if (!priv->proxy_auth)
559                         return;
560         }
561
562         /* If we need to authenticate, try to do it. */
563         authenticate_auth (manager, priv->proxy_auth, msg,
564                            prior_auth_failed, TRUE, TRUE);
565 }
566
567 static void
568 requeue_if_proxy_authenticated (SoupMessage *msg, gpointer manager)
569 {
570         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
571         SoupAuth *auth = priv->proxy_auth;
572
573         if (auth && soup_auth_is_authenticated (auth))
574                 soup_session_requeue_message (priv->session, msg);
575 }
576
577 static void
578 request_queued (SoupSessionFeature *manager, SoupSession *session,
579                 SoupMessage *msg)
580 {
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);
587
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);
594 }
595
596 static void
597 request_started (SoupSessionFeature *feature, SoupSession *session,
598                  SoupMessage *msg, SoupSocket *socket)
599 {
600         SoupAuthManager *manager = SOUP_AUTH_MANAGER (feature);
601         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
602         SoupAuth *auth;
603
604         auth = lookup_auth (priv, msg);
605         if (!auth || !authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE))
606                 auth = NULL;
607         soup_message_set_auth (msg, auth);
608
609         auth = priv->proxy_auth;
610         if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE))
611                 auth = NULL;
612         soup_message_set_proxy_auth (msg, auth);
613 }
614
615 static void
616 request_unqueued (SoupSessionFeature *manager, SoupSession *session,
617                   SoupMessage *msg)
618 {
619         g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA,
620                                               0, 0, NULL, NULL, manager);
621 }