Coverage Report

Created: 2026-10-17 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libsoup/libsoup/auth/soup-auth-manager.c
Line
Count
Source
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; 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-connection.h"
19
#include "soup-message-headers-private.h"
20
#include "soup-path-map.h"
21
#include "soup-session-private.h"
22
#include "soup-session-feature-private.h"
23
#include "soup-uri-utils-private.h"
24
25
/**
26
 * SoupAuthManager:
27
 *
28
 * HTTP client-side authentication handler.
29
 *
30
 * [class@AuthManager] is the [iface@SessionFeature] that handles HTTP
31
 * authentication for a [class@Session].
32
 *
33
 * A [class@AuthManager] is added to the session by default, and normally
34
 * you don't need to worry about it at all. However, if you want to
35
 * disable HTTP authentication, you can remove the feature from the
36
 * session with [method@Session.remove_feature_by_type] or disable it on
37
 * individual requests with [method@Message.disable_feature].
38
 *
39
 * You can use this with [method@Session.remove_feature_by_type] or
40
 * [method@Message.disable_feature].
41
 *
42
 * (Although this type has only been publicly visible since libsoup 2.42, it has
43
 * always existed in the background, and you can use `g_type_from_name
44
 * ("SoupAuthManager")` to get its [alias@GObject.Type] in earlier releases.)
45
 **/
46
static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
47
48
struct _SoupAuthManager {
49
        GObject parent_instance;
50
};
51
52
typedef struct {
53
  SoupSession *session;
54
  GPtrArray *auth_types;
55
56
  SoupAuth *proxy_auth;
57
  char *proxy_auth_authority;
58
        GMutex mutex;
59
  GHashTable *auth_hosts;
60
} SoupAuthManagerPrivate;
61
62
typedef struct {
63
  GUri        *uri;
64
  SoupPathMap *auth_realms;      /* path -> scheme:realm */
65
  GHashTable  *auths;            /* scheme:realm -> SoupAuth */
66
} SoupAuthHost;
67
68
0
G_DEFINE_FINAL_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT,
69
0
                               G_ADD_PRIVATE (SoupAuthManager)
70
0
             G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
71
0
                  soup_auth_manager_session_feature_init))
72
0
73
0
static void soup_auth_host_free (SoupAuthHost *host);
74
0
static SoupAuth *record_auth_for_uri (SoupAuthManagerPrivate *priv,
75
0
              GUri *uri, SoupAuth *auth,
76
0
              gboolean prior_auth_failed);
77
0
78
0
static void
79
0
soup_auth_manager_init (SoupAuthManager *manager)
80
0
{
81
0
  SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager);
82
83
0
  priv->auth_types = g_ptr_array_new_with_free_func ((GDestroyNotify)g_type_class_unref);
84
0
  priv->auth_hosts = g_hash_table_new_full (soup_uri_host_hash,
85
0
              soup_uri_host_equal,
86
0
              NULL,
87
0
              (GDestroyNotify)soup_auth_host_free);
88
0
        g_mutex_init (&priv->mutex);
89
0
}
90
91
static void
92
soup_auth_manager_finalize (GObject *object)
93
0
{
94
0
  SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private ((SoupAuthManager*)object);
95
96
0
  g_ptr_array_free (priv->auth_types, TRUE);
97
98
0
  g_hash_table_destroy (priv->auth_hosts);
99
100
0
  g_clear_object (&priv->proxy_auth);
101
0
  g_clear_pointer (&priv->proxy_auth_authority, g_free);
102
103
0
        g_mutex_clear (&priv->mutex);
104
105
0
  G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object);
106
0
}
107
108
static void
109
soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class)
110
0
{
111
0
  GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_class);
112
113
0
  object_class->finalize = soup_auth_manager_finalize;
114
0
}
115
116
static int
117
auth_type_compare_func (gconstpointer a, gconstpointer b)
118
0
{
119
0
  SoupAuthClass **auth1 = (SoupAuthClass **)a;
120
0
  SoupAuthClass **auth2 = (SoupAuthClass **)b;
121
122
0
  return (*auth1)->strength - (*auth2)->strength;
123
0
}
124
125
static gboolean
126
soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type)
127
0
{
128
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private ((SoupAuthManager*)feature);
129
0
  SoupAuthClass *auth_class;
130
131
0
  if (!g_type_is_a (type, SOUP_TYPE_AUTH))
132
0
    return FALSE;
133
134
0
  auth_class = g_type_class_ref (type);
135
0
  g_ptr_array_add (priv->auth_types, auth_class);
136
0
  g_ptr_array_sort (priv->auth_types, auth_type_compare_func);
137
138
0
  return TRUE;
139
0
}
140
141
static gboolean
142
soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type)
143
0
{
144
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private ((SoupAuthManager*)feature);
145
0
  SoupAuthClass *auth_class;
146
0
  guint i;
147
148
0
  if (!g_type_is_a (type, SOUP_TYPE_AUTH))
149
0
    return FALSE;
150
151
0
  auth_class = g_type_class_peek (type);
152
153
0
  for (i = 0; i < priv->auth_types->len; i++) {
154
0
    if (priv->auth_types->pdata[i] == (gpointer)auth_class) {
155
0
      g_ptr_array_remove_index (priv->auth_types, i);
156
0
      return TRUE;
157
0
    }
158
0
  }
159
160
0
  return FALSE;
161
0
}
162
163
static gboolean
164
soup_auth_manager_has_feature (SoupSessionFeature *feature, GType type)
165
0
{
166
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private ((SoupAuthManager*)feature);
167
0
  SoupAuthClass *auth_class;
168
0
  guint i;
169
170
0
  if (!g_type_is_a (type, SOUP_TYPE_AUTH))
171
0
    return FALSE;
172
173
0
  auth_class = g_type_class_peek (type);
174
0
  for (i = 0; i < priv->auth_types->len; i++) {
175
0
    if (priv->auth_types->pdata[i] == (gpointer)auth_class)
176
0
      return TRUE;
177
0
  }
178
0
  return FALSE;
179
0
}
180
181
static void
182
soup_auth_manager_attach (SoupSessionFeature *feature, SoupSession *session)
183
0
{
184
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private ((SoupAuthManager*)feature);
185
186
  /* FIXME: should support multiple sessions */
187
0
  priv->session = session;
188
0
}
189
190
static inline const char *
191
auth_header_for_message (SoupMessage *msg)
192
0
{
193
0
  if (soup_message_get_status (msg) == SOUP_STATUS_PROXY_UNAUTHORIZED) {
194
0
    return soup_message_headers_get_list_common (soup_message_get_response_headers (msg),
195
0
                                                             SOUP_HEADER_PROXY_AUTHENTICATE);
196
0
  } else {
197
0
    return soup_message_headers_get_list_common (soup_message_get_response_headers (msg),
198
0
                                                             SOUP_HEADER_WWW_AUTHENTICATE);
199
0
  }
200
0
}
201
202
static GSList *
203
next_challenge_start (GSList *items)
204
0
{
205
  /* The relevant grammar (from httpbis):
206
   *
207
   * WWW-Authenticate   = 1#challenge
208
   * Proxy-Authenticate = 1#challenge
209
   * challenge          = auth-scheme [ 1*SP ( b64token / #auth-param ) ]
210
   * auth-scheme        = token
211
   * auth-param         = token BWS "=" BWS ( token / quoted-string )
212
   * b64token           = 1*( ALPHA / DIGIT /
213
   *                          "-" / "." / "_" / "~" / "+" / "/" ) *"="
214
   *
215
   * The fact that quoted-strings can contain commas, equals
216
   * signs, and auth scheme names makes it tricky to "cheat" on
217
   * the parsing. So soup_auth_manager_extract_challenge() will
218
   * have used soup_header_parse_list() to split the header into
219
   * items. Given the grammar above, the possible items are:
220
   *
221
   *   auth-scheme
222
   *   auth-scheme 1*SP b64token
223
   *   auth-scheme 1*SP auth-param
224
   *   auth-param
225
   *
226
   * where the first three represent the start of a new challenge and
227
   * the last one does not.
228
   */
229
230
0
  for (; items; items = items->next) {
231
0
    const char *item = items->data;
232
0
    const char *sp = strpbrk (item, "\t\r\n ");
233
0
    const char *eq = strchr (item, '=');
234
235
0
    if (!eq) {
236
      /* No "=", so it can't be an auth-param */
237
0
      return items;
238
0
    }
239
0
    if (!sp || sp > eq) {
240
      /* No space, or first space appears after the "=",
241
       * so it must be an auth-param.
242
       */
243
0
      continue;
244
0
    }
245
0
    while (g_ascii_isspace (*++sp))
246
0
      ;
247
0
    if (sp == eq) {
248
      /* First "=" appears immediately after the first
249
       * space, so this must be an auth-param with
250
       * space around the "=".
251
       */
252
0
      continue;
253
0
    }
254
255
    /* "auth-scheme auth-param" or "auth-scheme b64token" */
256
0
    return items;
257
0
  }
258
259
0
  return NULL;
260
0
}
261
262
static GStrv
263
soup_auth_manager_extract_challenges (const char *challenges, const char *scheme)
264
0
{
265
0
        GPtrArray *challenge_list = g_ptr_array_new ();
266
0
  GSList *items, *i, *next;
267
0
  int schemelen = strlen (scheme);
268
0
  char *item;
269
0
  GString *challenge;
270
271
0
  i = items = soup_header_parse_list (challenges);
272
273
        /* We need to split this list into individual challenges. */
274
0
        while (i) {
275
                /* First item will start with the scheme name, followed by
276
                * either nothing, or else a space and then the first
277
                * auth-param.
278
                */
279
0
                for (; i; i = next_challenge_start (i->next)) {
280
0
                        item = i->data;
281
0
                        if (!g_ascii_strncasecmp (item, scheme, schemelen) &&
282
0
                            (!item[schemelen] || g_ascii_isspace (item[schemelen])))
283
0
                                break;
284
0
                }
285
0
                if (!i)
286
0
                        break;
287
288
0
                next = next_challenge_start (i->next);
289
0
                challenge = g_string_new (item);
290
0
                for (i = i->next; i != next; i = i->next) {
291
0
                        item = i->data;
292
0
                        g_string_append (challenge, ", ");
293
0
                        g_string_append (challenge, item);
294
0
                }
295
296
0
                i = next;
297
0
                g_ptr_array_add (challenge_list, g_string_free (challenge, FALSE));
298
0
        };
299
300
0
  soup_header_free_list (items);
301
302
0
        if (challenge_list->len)
303
0
                g_ptr_array_add (challenge_list, NULL); /* Trailing NULL for GStrv. */
304
0
        return (GStrv)g_ptr_array_free (challenge_list, FALSE);
305
0
}
306
307
static SoupAuth *
308
create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
309
0
{
310
0
  const char *header;
311
0
  SoupAuthClass *auth_class;
312
0
        GStrv challenges;
313
0
  SoupAuth *auth = NULL;
314
0
  int i;
315
316
0
  header = auth_header_for_message (msg);
317
0
  if (!header)
318
0
    return NULL;
319
320
0
  for (i = priv->auth_types->len - 1; i >= 0; i--) {
321
0
    auth_class = priv->auth_types->pdata[i];
322
0
                challenges = soup_auth_manager_extract_challenges (header, auth_class->scheme_name);
323
0
                if (!challenges)
324
0
                        continue;
325
326
0
                for (int j = 0; challenges[j]; j++) {
327
                        /* TODO: We use the first successfully parsed auth, in the future this should
328
                         * prioritise more secure ones when they are supported. */
329
0
                        auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenges[j]);
330
0
                        if (auth) {
331
0
                                g_strfreev (challenges);
332
0
                                return auth;
333
0
                        }
334
0
                }
335
336
0
    g_strfreev (challenges);
337
0
  }
338
339
0
  return NULL;
340
0
}
341
342
static gboolean
343
check_auth (SoupMessage *msg, SoupAuth *auth)
344
0
{
345
0
  const char *header, *scheme;
346
0
        GStrv challenges = NULL;
347
0
  gboolean ok = TRUE;
348
0
        gboolean a_challenge_was_ok = FALSE;
349
350
0
  scheme = soup_auth_get_scheme_name (auth);
351
352
0
  header = auth_header_for_message (msg);
353
0
  if (header)
354
0
                challenges = soup_auth_manager_extract_challenges (header, scheme);
355
0
  if (!challenges) {
356
0
                challenges = g_new0 (char*, 2);
357
0
                challenges[0] = g_strdup (scheme);
358
0
                ok = FALSE;
359
0
  }
360
361
0
        for (int i = 0; challenges[i]; i++) {
362
0
                if (soup_auth_update (auth, msg, challenges[i])) {
363
0
                        a_challenge_was_ok = TRUE;
364
0
                        break;
365
0
                }
366
0
        }
367
368
0
        if (!a_challenge_was_ok)
369
0
                ok = FALSE;
370
371
0
        g_strfreev (challenges);
372
0
  return ok;
373
0
}
374
375
static SoupAuthHost *
376
get_auth_host_for_uri (SoupAuthManagerPrivate *priv, GUri *uri)
377
0
{
378
0
  SoupAuthHost *host;
379
380
0
  host = g_hash_table_lookup (priv->auth_hosts, uri);
381
0
  if (host)
382
0
    return host;
383
384
0
  host = g_slice_new0 (SoupAuthHost);
385
0
  host->uri = soup_uri_copy_host (uri);
386
0
  g_hash_table_insert (priv->auth_hosts, host->uri, host);
387
388
0
  return host;
389
0
}
390
391
static void
392
soup_auth_host_free (SoupAuthHost *host)
393
0
{
394
0
  g_clear_pointer (&host->auth_realms, soup_path_map_free);
395
0
  g_clear_pointer (&host->auths, g_hash_table_destroy);
396
397
0
  g_uri_unref (host->uri);
398
0
  g_slice_free (SoupAuthHost, host);
399
0
}
400
401
static void
402
update_authorization_header (SoupMessage *msg, SoupAuth *auth, gboolean is_proxy)
403
0
{
404
0
        SoupHeaderName authorization_header = is_proxy ? SOUP_HEADER_PROXY_AUTHORIZATION : SOUP_HEADER_AUTHORIZATION;
405
0
  char *token;
406
407
0
  if (soup_message_get_auth (msg))
408
0
    soup_message_headers_remove_common (soup_message_get_request_headers (msg), authorization_header);
409
410
0
  if (!auth)
411
0
    return;
412
413
0
  token = soup_auth_get_authorization (auth, msg);
414
0
  if (!token)
415
0
    return;
416
417
0
  soup_message_headers_replace_common (soup_message_get_request_headers (msg), authorization_header, token, SOUP_HEADER_VALUE_TRUSTED);
418
0
  g_free (token);
419
0
}
420
421
static SoupAuth *
422
lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
423
0
{
424
0
  SoupAuthHost *host;
425
0
  const char *path, *realm;
426
0
  SoupAuth *auth;
427
0
  GUri *uri;
428
429
  /* If the message already has a ready auth, use that instead */
430
0
  auth = soup_message_get_auth (msg);
431
0
  if (auth && soup_auth_is_ready (auth, msg))
432
0
    return auth;
433
434
0
  if (soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE))
435
0
    return NULL;
436
437
0
  uri = soup_message_get_uri_for_auth (msg);
438
0
  if (!uri)
439
0
    return NULL;
440
441
0
  host = get_auth_host_for_uri (priv, uri);
442
0
  if (!host->auth_realms)
443
0
    return NULL;
444
445
0
  path = g_uri_get_path (uri);
446
0
  if (!path)
447
0
    path = "/";
448
0
  realm = soup_path_map_lookup (host->auth_realms, path);
449
0
  if (realm)
450
0
    return g_hash_table_lookup (host->auths, realm);
451
452
0
  return NULL;
453
0
}
454
455
static char *
456
get_proxy_authority (SoupMessage *msg)
457
0
{
458
0
  SoupConnection *conn = soup_message_get_connection (msg);
459
0
  GUri *proxy_uri;
460
0
  char *authority = NULL;
461
462
0
  if (!conn)
463
0
    return NULL;
464
465
0
  proxy_uri = soup_connection_get_proxy_uri (conn);
466
0
  if (proxy_uri)
467
0
    authority = g_strdup_printf ("%s:%d", g_uri_get_host (proxy_uri), g_uri_get_port (proxy_uri));
468
469
0
  g_object_unref (conn);
470
471
0
  return authority;
472
0
}
473
474
static gboolean
475
proxy_auth_matches_msg (SoupAuthManagerPrivate *priv, SoupMessage *msg)
476
0
{
477
0
  char *authority = get_proxy_authority (msg);
478
0
  gboolean matches;
479
480
0
  matches = authority && priv->proxy_auth_authority &&
481
0
      g_strcmp0 (authority, priv->proxy_auth_authority) == 0;
482
0
  g_free (authority);
483
484
0
  return matches;
485
0
}
486
487
static SoupAuth *
488
lookup_proxy_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
489
0
{
490
0
  SoupAuth *auth;
491
492
  /* If the message already has a ready auth, use that instead, but only
493
   * if it is still for the proxy this connection actually goes through.
494
   */
495
0
  auth = soup_message_get_proxy_auth (msg);
496
0
  if (auth && soup_auth_is_ready (auth, msg) && proxy_auth_matches_msg (priv, msg))
497
0
    return auth;
498
499
0
  if (soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE))
500
0
    return NULL;
501
502
  /* Only reuse the cached proxy credentials for the same proxy they were
503
   * obtained from, otherwise switching proxies would leak them.
504
   */
505
0
  if (priv->proxy_auth && proxy_auth_matches_msg (priv, msg))
506
0
    return priv->proxy_auth;
507
508
0
  return NULL;
509
0
}
510
511
static void
512
async_auth_finished (SoupAuth    *auth,
513
         GParamSpec  *pspec,
514
         SoupMessage *msg)
515
0
{
516
0
  SoupSession *session;
517
518
0
  session = g_object_steal_data (G_OBJECT (msg), "auth-msg-session");
519
0
  if (!session)
520
0
    return;
521
522
0
  soup_session_unpause_message (session, msg);
523
0
  g_object_unref (session);
524
0
}
525
526
static void
527
authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
528
       SoupMessage *msg, gboolean prior_auth_failed,
529
       gboolean proxy, gboolean can_interact)
530
0
{
531
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager);
532
0
  GUri *uri;
533
534
0
  if (!soup_auth_can_authenticate (auth))
535
0
    return;
536
537
0
  uri = soup_message_get_uri_for_auth (msg);
538
539
  /* If a password is specified explicitly in the URI, use it
540
   * even if the auth had previously already been authenticated.
541
   */
542
0
  if (g_uri_get_user (uri)) {
543
0
    const char *password = g_uri_get_password (uri);
544
0
    soup_auth_authenticate (auth, g_uri_get_user (uri), password ? password : "");
545
546
0
                GUri *new_uri = soup_uri_copy (uri, SOUP_URI_USER, NULL, SOUP_URI_PASSWORD, NULL, SOUP_URI_NONE);
547
0
                soup_message_set_uri (msg, new_uri); // QUESTION: This didn't emit a signal previously
548
0
                g_uri_unref (new_uri);
549
0
  } else if (!soup_auth_is_authenticated (auth) && can_interact) {
550
0
    SoupMessage *original_msg;
551
0
    gboolean handled;
552
553
0
    original_msg = soup_session_get_original_message_for_authentication (priv->session,
554
0
                         msg);
555
0
    handled = soup_message_authenticate (original_msg, auth, prior_auth_failed);
556
0
    if (handled && !soup_auth_is_authenticated (auth) && !soup_auth_is_cancelled (auth)) {
557
0
      soup_session_pause_message (priv->session, msg);
558
0
      g_object_set_data_full (G_OBJECT (msg), "auth-msg-session",
559
0
            g_object_ref (priv->session),
560
0
            g_object_unref);
561
0
      g_signal_connect_object (auth, "notify::is-authenticated",
562
0
             G_CALLBACK (async_auth_finished),
563
0
             msg, 0);
564
0
      g_signal_connect_object (auth, "notify::is-cancelled",
565
0
             G_CALLBACK (async_auth_finished),
566
0
             msg, 0);
567
0
    }
568
0
  }
569
0
}
570
571
static SoupAuth *
572
record_auth_for_uri (SoupAuthManagerPrivate *priv, GUri *uri,
573
         SoupAuth *auth, gboolean prior_auth_failed)
574
0
{
575
0
  SoupAuthHost *host;
576
0
  SoupAuth *old_auth;
577
0
  const char *path;
578
0
  char *auth_info, *old_auth_info;
579
0
  GSList *pspace, *p;
580
581
0
  host = get_auth_host_for_uri (priv, uri);
582
0
  auth_info = soup_auth_get_info (auth);
583
584
0
  if (!host->auth_realms) {
585
0
    host->auth_realms = soup_path_map_new (g_free);
586
0
    host->auths = g_hash_table_new_full (g_str_hash, g_str_equal,
587
0
                 g_free, g_object_unref);
588
0
  }
589
590
  /* Record where this auth realm is used. */
591
0
  pspace = soup_auth_get_protection_space (auth, uri);
592
0
  for (p = pspace; p; p = p->next) {
593
0
    path = p->data;
594
0
    old_auth_info = soup_path_map_lookup (host->auth_realms, path);
595
0
    if (old_auth_info) {
596
0
      if (!strcmp (old_auth_info, auth_info))
597
0
        continue;
598
0
      soup_path_map_remove (host->auth_realms, path);
599
0
    }
600
601
0
    soup_path_map_add (host->auth_realms, path,
602
0
           g_strdup (auth_info));
603
0
  }
604
0
  soup_auth_free_protection_space (auth, pspace);
605
606
  /* Now, make sure the auth is recorded. (If there's a
607
   * pre-existing good auth, we keep that rather than the new one,
608
   * since the old one might already be authenticated.)
609
   */
610
0
  old_auth = g_hash_table_lookup (host->auths, auth_info);
611
0
  if (old_auth && (old_auth != auth || !prior_auth_failed) && !soup_auth_is_cancelled (old_auth)) {
612
0
    g_free (auth_info);
613
0
    return old_auth;
614
0
  } else {
615
0
    g_hash_table_insert (host->auths, auth_info,
616
0
             g_object_ref (auth));
617
0
    return auth;
618
0
  }
619
0
}
620
621
static void
622
auth_got_headers (SoupMessage *msg, gpointer manager)
623
0
{
624
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager);
625
0
  SoupAuth *auth, *prior_auth;
626
0
  gboolean prior_auth_failed = FALSE;
627
628
0
        g_mutex_lock (&priv->mutex);
629
630
  /* See if we used auth last time */
631
0
  prior_auth = soup_message_get_auth (msg);
632
0
  if (prior_auth && check_auth (msg, prior_auth)) {
633
0
    auth = g_object_ref (prior_auth);
634
0
    if (!soup_auth_is_ready (auth, msg))
635
0
      prior_auth_failed = TRUE;
636
0
  } else {
637
0
    auth = create_auth (priv, msg);
638
0
    if (!auth) {
639
0
                        g_mutex_unlock (&priv->mutex);
640
0
      return;
641
0
                }
642
0
  }
643
644
0
  if (!soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) {
645
0
    SoupAuth *new_auth;
646
647
0
    new_auth = record_auth_for_uri (priv, soup_message_get_uri_for_auth (msg),
648
0
            auth, prior_auth_failed);
649
0
    g_set_object (&auth, new_auth);
650
0
  }
651
652
0
        g_mutex_unlock (&priv->mutex);
653
654
  /* If we need to authenticate, try to do it. */
655
0
  authenticate_auth (manager, auth, msg,
656
0
         prior_auth_failed, FALSE, TRUE);
657
0
  soup_message_set_auth (msg, auth);
658
0
  g_object_unref (auth);
659
0
}
660
661
static void
662
auth_got_body (SoupMessage *msg, gpointer manager)
663
0
{
664
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager);
665
0
  SoupAuth *auth;
666
667
0
        g_mutex_lock (&priv->mutex);
668
669
0
  auth = lookup_auth (priv, msg);
670
0
  if (auth && soup_auth_is_ready (auth, msg)) {
671
0
    if (SOUP_IS_CONNECTION_AUTH (auth))
672
0
      soup_message_remove_flags (msg, SOUP_MESSAGE_NEW_CONNECTION);
673
674
    /* When not using cached credentials, update the Authorization header
675
     * right before requeuing the message.
676
     */
677
0
    if (soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE))
678
0
      update_authorization_header (msg, auth, FALSE);
679
680
0
    soup_session_requeue_message (priv->session, msg);
681
0
  }
682
683
0
        g_mutex_unlock (&priv->mutex);
684
0
}
685
686
static void
687
proxy_auth_got_headers (SoupMessage *msg, gpointer manager)
688
0
{
689
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager);
690
0
  SoupAuth *auth = NULL, *prior_auth;
691
0
  gboolean prior_auth_failed = FALSE;
692
693
0
        g_mutex_lock (&priv->mutex);
694
695
  /* See if we used auth last time */
696
0
  prior_auth = soup_message_get_proxy_auth (msg);
697
0
  if (prior_auth && check_auth (msg, prior_auth)) {
698
0
    if (!soup_auth_is_ready (prior_auth, msg))
699
0
      prior_auth_failed = TRUE;
700
0
  }
701
702
0
  if (!soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE) &&
703
0
      priv->proxy_auth && proxy_auth_matches_msg (priv, msg))
704
0
    auth = g_object_ref (priv->proxy_auth);
705
706
0
  if (!auth) {
707
0
    auth = create_auth (priv, msg);
708
0
    if (!auth) {
709
0
                        g_mutex_unlock (&priv->mutex);
710
0
      return;
711
0
                }
712
713
0
    if (!soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) {
714
0
      g_set_object (&priv->proxy_auth, auth);
715
0
      g_free (priv->proxy_auth_authority);
716
0
      priv->proxy_auth_authority = get_proxy_authority (msg);
717
0
    }
718
0
  }
719
720
0
        g_mutex_unlock (&priv->mutex);
721
722
  /* If we need to authenticate, try to do it. */
723
0
  authenticate_auth (manager, auth, msg,
724
0
         prior_auth_failed, TRUE, TRUE);
725
0
  soup_message_set_proxy_auth (msg, auth);
726
0
  g_object_unref (auth);
727
0
}
728
729
static void
730
proxy_auth_got_body (SoupMessage *msg, gpointer manager)
731
0
{
732
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager);
733
0
  SoupAuth *auth;
734
735
0
        g_mutex_lock (&priv->mutex);
736
737
0
  auth = lookup_proxy_auth (priv, msg);
738
0
  if (auth && soup_auth_is_ready (auth, msg)) {
739
    /* When not using cached credentials, update the Authorization header
740
     * right before requeuing the message.
741
     */
742
0
    if (soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE))
743
0
      update_authorization_header (msg, auth, TRUE);
744
0
    soup_session_requeue_message (priv->session, msg);
745
0
  }
746
747
0
        g_mutex_unlock (&priv->mutex);
748
0
}
749
750
static void
751
auth_msg_starting (SoupMessage *msg, gpointer manager)
752
0
{
753
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager);
754
0
  SoupAuth *auth;
755
0
  SoupConnection *conn;
756
0
  gboolean tunnelled;
757
758
0
  if (soup_message_query_flags (msg, SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE))
759
0
    return;
760
761
0
        g_mutex_lock (&priv->mutex);
762
763
0
  if (soup_message_get_method (msg) != SOUP_METHOD_CONNECT) {
764
0
    auth = lookup_auth (priv, msg);
765
0
    if (auth) {
766
0
      authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE);
767
0
      if (!soup_auth_is_ready (auth, msg))
768
0
        auth = NULL;
769
0
    }
770
0
    soup_message_set_auth (msg, auth);
771
0
    update_authorization_header (msg, auth, FALSE);
772
0
  }
773
774
  /* Proxy-Authorization must only be sent to the proxy itself, never on
775
   * a request travelling through an established CONNECT tunnel to the
776
   * origin server, which would leak the proxy credentials to the origin.
777
   * The CONNECT request that sets the tunnel up is addressed to the
778
   * proxy, so it still needs the header.
779
   */
780
0
  conn = soup_message_get_connection (msg);
781
0
  tunnelled = conn && soup_connection_is_tunnelled (conn) &&
782
0
        soup_message_get_method (msg) != SOUP_METHOD_CONNECT;
783
0
  g_clear_object (&conn);
784
0
  if (tunnelled) {
785
0
    soup_message_set_proxy_auth (msg, NULL);
786
0
    update_authorization_header (msg, NULL, TRUE);
787
0
    g_mutex_unlock (&priv->mutex);
788
0
    return;
789
0
  }
790
791
0
  auth = lookup_proxy_auth (priv, msg);
792
0
  if (auth) {
793
0
    authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE);
794
0
    if (!soup_auth_is_ready (auth, msg))
795
0
      auth = NULL;
796
0
  }
797
0
  soup_message_set_proxy_auth (msg, auth);
798
0
  update_authorization_header (msg, auth, TRUE);
799
800
0
        g_mutex_unlock (&priv->mutex);
801
0
}
802
803
static void
804
soup_auth_manager_request_queued (SoupSessionFeature *manager,
805
          SoupMessage        *msg)
806
0
{
807
0
  g_signal_connect (msg, "starting",
808
0
        G_CALLBACK (auth_msg_starting), manager);
809
810
0
  soup_message_add_status_code_handler (
811
0
    msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
812
0
    G_CALLBACK (auth_got_headers), manager);
813
0
  soup_message_add_status_code_handler (
814
0
    msg, "got_body", SOUP_STATUS_UNAUTHORIZED,
815
0
    G_CALLBACK (auth_got_body), manager);
816
817
0
  soup_message_add_status_code_handler (
818
0
    msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED,
819
0
    G_CALLBACK (proxy_auth_got_headers), manager);
820
0
  soup_message_add_status_code_handler (
821
0
    msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED,
822
0
    G_CALLBACK (proxy_auth_got_body), manager);
823
0
}
824
825
static void
826
soup_auth_manager_request_unqueued (SoupSessionFeature *manager,
827
            SoupMessage        *msg)
828
0
{
829
0
  g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA,
830
0
                0, 0, NULL, NULL, manager);
831
0
}
832
833
/**
834
 * soup_auth_manager_use_auth:
835
 * @manager: a #SoupAuthManager
836
 * @uri: the #GUri under which @auth is to be used
837
 * @auth: the #SoupAuth to use
838
 *
839
 * Records that @auth is to be used under @uri, as though a
840
 * WWW-Authenticate header had been received at that URI.
841
 *
842
 * This can be used to "preload" @manager's auth cache, to avoid an extra HTTP
843
 * round trip in the case where you know ahead of time that a 401 response will
844
 * be returned.
845
 *
846
 * This is only useful for authentication types where the initial
847
 * Authorization header does not depend on any additional information
848
 * from the server. (Eg, Basic or NTLM, but not Digest.)
849
 *
850
 */
851
void
852
soup_auth_manager_use_auth (SoupAuthManager *manager,
853
          GUri            *uri,
854
          SoupAuth        *auth)
855
0
{
856
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager);
857
858
0
        g_mutex_lock (&priv->mutex);
859
0
  record_auth_for_uri (priv, uri, auth, FALSE);
860
0
        g_mutex_unlock (&priv->mutex);
861
0
}
862
863
/**
864
 * soup_auth_manager_clear_cached_credentials:
865
 * @manager: a #SoupAuthManager
866
 *
867
 * Clear all credentials cached by @manager.
868
 *
869
 */
870
void
871
soup_auth_manager_clear_cached_credentials (SoupAuthManager *manager)
872
0
{
873
0
        SoupAuthManagerPrivate *priv = soup_auth_manager_get_instance_private (manager);
874
875
0
  g_return_if_fail (SOUP_IS_AUTH_MANAGER (manager));
876
877
0
        g_mutex_lock (&priv->mutex);
878
0
  g_hash_table_remove_all (priv->auth_hosts);
879
0
        g_mutex_unlock (&priv->mutex);
880
0
}
881
882
static void
883
soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface,
884
          gpointer interface_data)
885
0
{
886
0
  feature_interface->attach = soup_auth_manager_attach;
887
0
  feature_interface->request_queued = soup_auth_manager_request_queued;
888
0
  feature_interface->request_unqueued = soup_auth_manager_request_unqueued;
889
0
  feature_interface->add_feature = soup_auth_manager_add_feature;
890
0
  feature_interface->remove_feature = soup_auth_manager_remove_feature;
891
0
  feature_interface->has_feature = soup_auth_manager_has_feature;
892
0
}