SoupAuthManager: deal with "disappearing" auth headers
[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.h"
16 #include "soup-connection-auth.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-private.h"
22
23 /**
24  * SECTION:soup-auth-manager
25  * @short_description: HTTP client-side authentication handler
26  * @see_also: #SoupSession, #SoupAuth
27  *
28  * #SoupAuthManager is the #SoupSessionFeature that handles HTTP
29  * authentication for a #SoupSession.
30  *
31  * A #SoupAuthManager is added to the session by default, and normally
32  * you don't need to worry about it at all. However, if you want to
33  * disable HTTP authentication, you can remove the feature from the
34  * session with soup_session_remove_feature_by_type(), or disable it on
35  * individual requests with soup_message_disable_feature().
36  *
37  * Since: 2.42
38  **/
39
40 /**
41  * SOUP_TYPE_AUTH_MANAGER:
42  *
43  * The #GType of #SoupAuthManager; you can use this with
44  * soup_session_remove_feature_by_type() or
45  * soup_message_disable_feature().
46  *
47  * (Although this type has only been publicly visible since libsoup
48  * 2.42, it has always existed in the background, and you can use
49  * <literal><code>g_type_from_name ("SoupAuthManager")</code></literal>
50  * to get its #GType in earlier releases.)
51  *
52  * Since: 2.42
53  */
54 static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
55 static SoupSessionFeatureInterface *soup_session_feature_default_interface;
56
57 enum {
58         AUTHENTICATE,
59         LAST_SIGNAL
60 };
61
62 static guint signals[LAST_SIGNAL] = { 0 };
63
64 G_DEFINE_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT,
65                          G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
66                                                 soup_auth_manager_session_feature_init))
67
68 struct SoupAuthManagerPrivate {
69         SoupSession *session;
70         GPtrArray *auth_types;
71         gboolean auto_ntlm;
72
73         GMutex lock;
74         SoupAuth *proxy_auth;
75         GHashTable *auth_hosts;
76 };
77
78 typedef struct {
79         SoupURI     *uri;
80         SoupPathMap *auth_realms;      /* path -> scheme:realm */
81         GHashTable  *auths;            /* scheme:realm -> SoupAuth */
82 } SoupAuthHost;
83
84 static void soup_auth_host_free (SoupAuthHost *host);
85 static SoupAuth *record_auth_for_uri (SoupAuthManagerPrivate *priv,
86                                       SoupURI *uri, SoupAuth *auth,
87                                       gboolean prior_auth_failed);
88
89 static void
90 soup_auth_manager_init (SoupAuthManager *manager)
91 {
92         SoupAuthManagerPrivate *priv;
93
94         priv = manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerPrivate);
95
96         priv->auth_types = g_ptr_array_new_with_free_func ((GDestroyNotify)g_type_class_unref);
97         priv->auth_hosts = g_hash_table_new_full (soup_uri_host_hash,
98                                                   soup_uri_host_equal,
99                                                   NULL,
100                                                   (GDestroyNotify)soup_auth_host_free);
101         g_mutex_init (&priv->lock);
102 }
103
104 static void
105 soup_auth_manager_finalize (GObject *object)
106 {
107         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (object)->priv;
108
109         g_ptr_array_free (priv->auth_types, TRUE);
110
111         g_hash_table_destroy (priv->auth_hosts);
112
113         g_clear_object (&priv->proxy_auth);
114
115         g_mutex_clear (&priv->lock);
116
117         G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object);
118 }
119
120 static void
121 soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class)
122 {
123         GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_class);
124
125         g_type_class_add_private (auth_manager_class, sizeof (SoupAuthManagerPrivate));
126
127         object_class->finalize = soup_auth_manager_finalize;
128
129         /**
130          * SoupAuthManager::authenticate:
131          * @manager: the #SoupAuthManager
132          * @msg: the #SoupMessage being sent
133          * @auth: the #SoupAuth to authenticate
134          * @retrying: %TRUE if this is the second (or later) attempt
135          *
136          * Emitted when the manager requires the application to
137          * provide authentication credentials.
138          *
139          * #SoupSession connects to this signal and emits its own
140          * #SoupSession::authenticate signal when it is emitted, so
141          * you shouldn't need to use this signal directly.
142          */
143         signals[AUTHENTICATE] =
144                 g_signal_new ("authenticate",
145                               G_OBJECT_CLASS_TYPE (object_class),
146                               G_SIGNAL_RUN_FIRST,
147                               G_STRUCT_OFFSET (SoupAuthManagerClass, authenticate),
148                               NULL, NULL,
149                               _soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN,
150                               G_TYPE_NONE, 3,
151                               SOUP_TYPE_MESSAGE,
152                               SOUP_TYPE_AUTH,
153                               G_TYPE_BOOLEAN);
154
155 }
156
157 static int
158 auth_type_compare_func (gconstpointer a, gconstpointer b)
159 {
160         SoupAuthClass **auth1 = (SoupAuthClass **)a;
161         SoupAuthClass **auth2 = (SoupAuthClass **)b;
162
163         return (*auth1)->strength - (*auth2)->strength;
164 }
165
166 static gboolean
167 soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type)
168 {
169         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
170         SoupAuthClass *auth_class;
171
172         if (!g_type_is_a (type, SOUP_TYPE_AUTH))
173                 return FALSE;
174
175         auth_class = g_type_class_ref (type);
176         g_ptr_array_add (priv->auth_types, auth_class);
177         g_ptr_array_sort (priv->auth_types, auth_type_compare_func);
178
179         /* Plain SoupSession does not get the backward-compat
180          * auto-NTLM behavior; SoupSession subclasses do.
181          */
182         if (type == SOUP_TYPE_AUTH_NTLM &&
183             G_TYPE_FROM_INSTANCE (priv->session) != SOUP_TYPE_SESSION)
184                 priv->auto_ntlm = TRUE;
185
186         return TRUE;
187 }
188
189 static gboolean
190 soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type)
191 {
192         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
193         SoupAuthClass *auth_class;
194         int i;
195
196         if (!g_type_is_a (type, SOUP_TYPE_AUTH))
197                 return FALSE;
198
199         auth_class = g_type_class_peek (type);
200
201         for (i = 0; i < priv->auth_types->len; i++) {
202                 if (priv->auth_types->pdata[i] == (gpointer)auth_class) {
203                         if (type == SOUP_TYPE_AUTH_NTLM)
204                                 priv->auto_ntlm = FALSE;
205
206                         g_ptr_array_remove_index (priv->auth_types, i);
207                         return TRUE;
208                 }
209         }
210
211         return FALSE;
212 }
213
214 static gboolean
215 soup_auth_manager_has_feature (SoupSessionFeature *feature, GType type)
216 {
217         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
218         SoupAuthClass *auth_class;
219         int i;
220
221         if (!g_type_is_a (type, SOUP_TYPE_AUTH))
222                 return FALSE;
223
224         auth_class = g_type_class_peek (type);
225         for (i = 0; i < priv->auth_types->len; i++) {
226                 if (priv->auth_types->pdata[i] == (gpointer)auth_class)
227                         return TRUE;
228         }
229         return FALSE;
230 }
231
232 static void
233 soup_auth_manager_attach (SoupSessionFeature *feature, SoupSession *session)
234 {
235         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
236
237         /* FIXME: should support multiple sessions */
238         priv->session = session;
239
240         soup_session_feature_default_interface->attach (feature, session);
241 }
242
243 static inline const char *
244 auth_header_for_message (SoupMessage *msg)
245 {
246         if (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) {
247                 return soup_message_headers_get_list (msg->response_headers,
248                                                       "Proxy-Authenticate");
249         } else {
250                 return soup_message_headers_get_list (msg->response_headers,
251                                                       "WWW-Authenticate");
252         }
253 }
254
255 static GSList *
256 next_challenge_start (GSList *items)
257 {
258         /* The relevant grammar (from httpbis):
259          *
260          * WWW-Authenticate   = 1#challenge
261          * Proxy-Authenticate = 1#challenge
262          * challenge          = auth-scheme [ 1*SP ( b64token / #auth-param ) ]
263          * auth-scheme        = token
264          * auth-param         = token BWS "=" BWS ( token / quoted-string )
265          * b64token           = 1*( ALPHA / DIGIT /
266          *                          "-" / "." / "_" / "~" / "+" / "/" ) *"="
267          *
268          * The fact that quoted-strings can contain commas, equals
269          * signs, and auth scheme names makes it tricky to "cheat" on
270          * the parsing. So soup_auth_manager_extract_challenge() will
271          * have used soup_header_parse_list() to split the header into
272          * items. Given the grammar above, the possible items are:
273          *
274          *   auth-scheme
275          *   auth-scheme 1*SP b64token
276          *   auth-scheme 1*SP auth-param
277          *   auth-param
278          *
279          * where the first three represent the start of a new challenge and
280          * the last one does not.
281          */
282
283         for (; items; items = items->next) {
284                 const char *item = items->data;
285                 const char *sp = strpbrk (item, "\t\r\n ");
286                 const char *eq = strchr (item, '=');
287
288                 if (!eq) {
289                         /* No "=", so it can't be an auth-param */
290                         return items;
291                 }
292                 if (!sp || sp > eq) {
293                         /* No space, or first space appears after the "=",
294                          * so it must be an auth-param.
295                          */
296                         continue;
297                 }
298                 while (g_ascii_isspace (*++sp))
299                         ;
300                 if (sp == eq) {
301                         /* First "=" appears immediately after the first
302                          * space, so this must be an auth-param with
303                          * space around the "=".
304                          */
305                         continue;
306                 }
307
308                 /* "auth-scheme auth-param" or "auth-scheme b64token" */
309                 return items;
310         }
311
312         return NULL;
313 }
314
315 static char *
316 soup_auth_manager_extract_challenge (const char *challenges, const char *scheme)
317 {
318         GSList *items, *i, *next;
319         int schemelen = strlen (scheme);
320         char *item;
321         GString *challenge;
322
323         items = soup_header_parse_list (challenges);
324
325         /* First item will start with the scheme name, followed by
326          * either nothing, or else a space and then the first
327          * auth-param.
328          */
329         for (i = items; i; i = next_challenge_start (i->next)) {
330                 item = i->data;
331                 if (!g_ascii_strncasecmp (item, scheme, schemelen) &&
332                     (!item[schemelen] || g_ascii_isspace (item[schemelen])))
333                         break;
334         }
335         if (!i) {
336                 soup_header_free_list (items);
337                 return NULL;
338         }
339
340         next = next_challenge_start (i->next);
341         challenge = g_string_new (item);
342         for (i = i->next; i != next; i = i->next) {
343                 item = i->data;
344                 g_string_append (challenge, ", ");
345                 g_string_append (challenge, item);
346         }
347
348         soup_header_free_list (items);
349         return g_string_free (challenge, FALSE);
350 }
351
352 static SoupAuth *
353 create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
354 {
355         const char *header;
356         SoupAuthClass *auth_class;
357         char *challenge = NULL;
358         SoupAuth *auth;
359         int i;
360
361         header = auth_header_for_message (msg);
362         if (!header)
363                 return NULL;
364
365         for (i = priv->auth_types->len - 1; i >= 0; i--) {
366                 auth_class = priv->auth_types->pdata[i];
367                 challenge = soup_auth_manager_extract_challenge (header, auth_class->scheme_name);
368                 if (challenge)
369                         break;
370         }
371         if (!challenge)
372                 return NULL;
373
374         auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenge);
375         g_free (challenge);
376         return auth;
377 }
378
379 static gboolean
380 check_auth (SoupMessage *msg, SoupAuth *auth)
381 {
382         const char *header, *scheme;
383         char *challenge = NULL;
384         gboolean ok = TRUE;
385
386         scheme = soup_auth_get_scheme_name (auth);
387
388         header = auth_header_for_message (msg);
389         if (header)
390                 challenge = soup_auth_manager_extract_challenge (header, scheme);
391         if (!challenge) {
392                 ok = FALSE;
393                 challenge = g_strdup (scheme);
394         }
395
396         if (!soup_auth_update (auth, msg, challenge))
397                 ok = FALSE;
398         g_free (challenge);
399         return ok;
400 }
401
402 static SoupAuthHost *
403 get_auth_host_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri)
404 {
405         SoupAuthHost *host;
406
407         host = g_hash_table_lookup (priv->auth_hosts, uri);
408         if (host)
409                 return host;
410
411         host = g_slice_new0 (SoupAuthHost);
412         host->uri = soup_uri_copy_host (uri);
413         g_hash_table_insert (priv->auth_hosts, host->uri, host);
414
415         return host;
416 }
417
418 static void
419 soup_auth_host_free (SoupAuthHost *host)
420 {
421         g_clear_pointer (&host->auth_realms, soup_path_map_free);
422         g_clear_pointer (&host->auths, g_hash_table_destroy);
423
424         soup_uri_free (host->uri);
425         g_slice_free (SoupAuthHost, host);
426 }
427
428 static gboolean
429 make_auto_ntlm_auth (SoupAuthManagerPrivate *priv, SoupAuthHost *host)
430 {
431         SoupAuth *auth;
432
433         if (!priv->auto_ntlm)
434                 return FALSE;
435
436         auth = g_object_new (SOUP_TYPE_AUTH_NTLM,
437                              SOUP_AUTH_HOST, host->uri->host,
438                              NULL);
439         record_auth_for_uri (priv, host->uri, auth, FALSE);
440         g_object_unref (auth);
441         return TRUE;
442 }
443
444 static SoupAuth *
445 lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
446 {
447         SoupAuthHost *host;
448         const char *path, *realm;
449
450         host = get_auth_host_for_uri (priv, soup_message_get_uri (msg));
451         if (!host->auth_realms && !make_auto_ntlm_auth (priv, host))
452                 return NULL;
453
454         path = soup_message_get_uri (msg)->path;
455         if (!path)
456                 path = "/";
457         realm = soup_path_map_lookup (host->auth_realms, path);
458         if (realm)
459                 return g_hash_table_lookup (host->auths, realm);
460         else
461                 return NULL;
462 }
463
464 static void
465 authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
466                    SoupMessage *msg, gboolean prior_auth_failed,
467                    gboolean proxy, gboolean can_interact)
468 {
469         SoupAuthManagerPrivate *priv = manager->priv;
470         SoupURI *uri;
471
472         if (proxy) {
473                 SoupMessageQueue *queue;
474                 SoupMessageQueueItem *item;
475
476                 queue = soup_session_get_queue (priv->session);
477                 item = soup_message_queue_lookup (queue, msg);
478                 if (item) {
479                         uri = soup_connection_get_proxy_uri (item->conn);
480                         soup_message_queue_item_unref (item);
481                 } else
482                         uri = NULL;
483
484                 if (!uri)
485                         return;
486         } else
487                 uri = soup_message_get_uri (msg);
488
489         /* If a password is specified explicitly in the URI, use it
490          * even if the auth had previously already been authenticated.
491          */
492         if (uri->password && uri->user) {
493                 soup_auth_authenticate (auth, uri->user, uri->password);
494                 soup_uri_set_password (uri, NULL);
495                 soup_uri_set_user (uri, NULL);
496         } else if (!soup_auth_is_authenticated (auth) && can_interact) {
497                 g_signal_emit (manager, signals[AUTHENTICATE], 0,
498                                msg, auth, prior_auth_failed);
499         }
500 }
501
502 static SoupAuth *
503 record_auth_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri,
504                      SoupAuth *auth, gboolean prior_auth_failed)
505 {
506         SoupAuthHost *host;
507         SoupAuth *old_auth;
508         const char *path;
509         char *auth_info, *old_auth_info;
510         GSList *pspace, *p;
511
512         host = get_auth_host_for_uri (priv, uri);
513         auth_info = soup_auth_get_info (auth);
514
515         if (!host->auth_realms) {
516                 host->auth_realms = soup_path_map_new (g_free);
517                 host->auths = g_hash_table_new_full (g_str_hash, g_str_equal,
518                                                      g_free, g_object_unref);
519         }
520
521         /* Record where this auth realm is used. */
522         pspace = soup_auth_get_protection_space (auth, uri);
523         for (p = pspace; p; p = p->next) {
524                 path = p->data;
525                 old_auth_info = soup_path_map_lookup (host->auth_realms, path);
526                 if (old_auth_info) {
527                         if (!strcmp (old_auth_info, auth_info))
528                                 continue;
529                         soup_path_map_remove (host->auth_realms, path);
530                 }
531
532                 soup_path_map_add (host->auth_realms, path,
533                                    g_strdup (auth_info));
534         }
535         soup_auth_free_protection_space (auth, pspace);
536
537         /* Now, make sure the auth is recorded. (If there's a
538          * pre-existing good auth, we keep that rather than the new one,
539          * since the old one might already be authenticated.)
540          */
541         old_auth = g_hash_table_lookup (host->auths, auth_info);
542         if (old_auth && (old_auth != auth || !prior_auth_failed)) {
543                 g_free (auth_info);
544                 return old_auth;
545         } else {
546                 g_hash_table_insert (host->auths, auth_info,
547                                      g_object_ref (auth));
548                 return auth;
549         }
550 }
551
552 static void
553 auth_got_headers (SoupMessage *msg, gpointer manager)
554 {
555         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
556         SoupAuth *auth, *prior_auth, *new_auth;
557         gboolean prior_auth_failed = FALSE;
558
559         g_mutex_lock (&priv->lock);
560
561         /* See if we used auth last time */
562         prior_auth = soup_message_get_auth (msg);
563         if (prior_auth && check_auth (msg, prior_auth)) {
564                 auth = g_object_ref (prior_auth);
565                 if (!soup_auth_is_ready (auth, msg))
566                         prior_auth_failed = TRUE;
567         } else {
568                 auth = create_auth (priv, msg);
569                 if (!auth) {
570                         g_mutex_unlock (&priv->lock);
571                         return;
572                 }
573         }
574
575         new_auth = record_auth_for_uri (priv, soup_message_get_uri (msg),
576                                         auth, prior_auth_failed);
577         g_object_unref (auth);
578
579         /* If we need to authenticate, try to do it. */
580         authenticate_auth (manager, new_auth, msg,
581                            prior_auth_failed, FALSE, TRUE);
582         g_mutex_unlock (&priv->lock);
583 }
584
585 static void
586 auth_got_body (SoupMessage *msg, gpointer manager)
587 {
588         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
589         SoupAuth *auth;
590
591         g_mutex_lock (&priv->lock);
592         auth = lookup_auth (priv, msg);
593         if (auth && soup_auth_is_ready (auth, msg)) {
594                 if (SOUP_IS_CONNECTION_AUTH (auth)) {
595                         SoupMessageFlags flags;
596
597                         flags = soup_message_get_flags (msg);
598                         soup_message_set_flags (msg, flags & ~SOUP_MESSAGE_NEW_CONNECTION);
599                 }
600
601                 soup_session_requeue_message (priv->session, msg);
602         }
603         g_mutex_unlock (&priv->lock);
604 }
605
606 static void
607 proxy_auth_got_headers (SoupMessage *msg, gpointer manager)
608 {
609         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
610         SoupAuth *prior_auth;
611         gboolean prior_auth_failed = FALSE;
612
613         g_mutex_lock (&priv->lock);
614
615         /* See if we used auth last time */
616         prior_auth = soup_message_get_proxy_auth (msg);
617         if (prior_auth && check_auth (msg, prior_auth)) {
618                 if (!soup_auth_is_ready (prior_auth, msg))
619                         prior_auth_failed = TRUE;
620         }
621
622         if (!priv->proxy_auth) {
623                 priv->proxy_auth = create_auth (priv, msg);
624                 if (!priv->proxy_auth) {
625                         g_mutex_unlock (&priv->lock);
626                         return;
627                 }
628         }
629
630         /* If we need to authenticate, try to do it. */
631         authenticate_auth (manager, priv->proxy_auth, msg,
632                            prior_auth_failed, TRUE, TRUE);
633         g_mutex_unlock (&priv->lock);
634 }
635
636 static void
637 proxy_auth_got_body (SoupMessage *msg, gpointer manager)
638 {
639         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
640         SoupAuth *auth;
641
642         g_mutex_lock (&priv->lock);
643         auth = priv->proxy_auth;
644
645         if (auth && soup_auth_is_ready (auth, msg))
646                 soup_session_requeue_message (priv->session, msg);
647         g_mutex_unlock (&priv->lock);
648 }
649
650 static void
651 soup_auth_manager_request_queued (SoupSessionFeature *manager,
652                                   SoupSession *session,
653                                   SoupMessage *msg)
654 {
655         soup_message_add_status_code_handler (
656                 msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
657                 G_CALLBACK (auth_got_headers), manager);
658         soup_message_add_status_code_handler (
659                 msg, "got_body", SOUP_STATUS_UNAUTHORIZED,
660                 G_CALLBACK (auth_got_body), manager);
661
662         soup_message_add_status_code_handler (
663                 msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED,
664                 G_CALLBACK (proxy_auth_got_headers), manager);
665         soup_message_add_status_code_handler (
666                 msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED,
667                 G_CALLBACK (proxy_auth_got_body), manager);
668 }
669
670 static void
671 soup_auth_manager_request_started (SoupSessionFeature *feature,
672                                    SoupSession *session,
673                                    SoupMessage *msg,
674                                    SoupSocket *socket)
675 {
676         SoupAuthManager *manager = SOUP_AUTH_MANAGER (feature);
677         SoupAuthManagerPrivate *priv = manager->priv;
678         SoupAuth *auth;
679
680         g_mutex_lock (&priv->lock);
681
682         auth = lookup_auth (priv, msg);
683         if (auth) {
684                 authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE);
685                 if (!soup_auth_is_ready (auth, msg))
686                         auth = NULL;
687         }
688         soup_message_set_auth (msg, auth);
689
690         auth = priv->proxy_auth;
691         if (auth) {
692                 authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE);
693                 if (!soup_auth_is_ready (auth, msg))
694                         auth = NULL;
695         }
696         soup_message_set_proxy_auth (msg, auth);
697
698         g_mutex_unlock (&priv->lock);
699 }
700
701 static void
702 soup_auth_manager_request_unqueued (SoupSessionFeature *manager,
703                                     SoupSession *session,
704                                     SoupMessage *msg)
705 {
706         g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA,
707                                               0, 0, NULL, NULL, manager);
708 }
709
710 /**
711  * soup_auth_manager_use_auth:
712  * @manager: a #SoupAuthManager
713  * @uri: the #SoupURI under which @auth is to be used
714  * @auth: the #SoupAuth to use
715  *
716  * Records that @auth is to be used under @uri, as though a
717  * WWW-Authenticate header had been received at that URI. This can be
718  * used to "preload" @manager's auth cache, to avoid an extra HTTP
719  * round trip in the case where you know ahead of time that a 401
720  * response will be returned.
721  *
722  * This is only useful for authentication types where the initial
723  * Authorization header does not depend on any additional information
724  * from the server. (Eg, Basic or NTLM, but not Digest.)
725  *
726  * Since: 2.42
727  */
728 void
729 soup_auth_manager_use_auth (SoupAuthManager *manager,
730                             SoupURI         *uri,
731                             SoupAuth        *auth)
732 {
733         SoupAuthManagerPrivate *priv = manager->priv;
734
735         g_mutex_lock (&priv->lock);
736         record_auth_for_uri (priv, uri, auth, FALSE);
737         g_mutex_unlock (&priv->lock);
738 }
739
740 static void
741 soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface,
742                                         gpointer interface_data)
743 {
744         soup_session_feature_default_interface =
745                 g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
746
747         feature_interface->attach = soup_auth_manager_attach;
748         feature_interface->request_queued = soup_auth_manager_request_queued;
749         feature_interface->request_started = soup_auth_manager_request_started;
750         feature_interface->request_unqueued = soup_auth_manager_request_unqueued;
751         feature_interface->add_feature = soup_auth_manager_add_feature;
752         feature_interface->remove_feature = soup_auth_manager_remove_feature;
753         feature_interface->has_feature = soup_auth_manager_has_feature;
754 }