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