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