Remove extern "C" wrapping other includes
[platform/upstream/libsoup.git] / tests / auth-test.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 #include "test-utils.h"
4
5 static const char *base_uri;
6 static GMainLoop *loop;
7
8 typedef struct {
9         /* Explanation of what you should see */
10         const char *explanation;
11
12         /* URL to test against */
13         const char *url;
14
15         /* Provided passwords, 1 character each. ('1', '2', and '3'
16          * mean the correct passwords for "realm1", "realm2", and
17          * "realm3" respectively. '4' means "use the wrong password".)
18          * The first password (if present) will be used by
19          * authenticate(), and the second (if present) will be used by
20          * reauthenticate().
21          */
22         const char *provided;
23
24         /* Whether to pass user and password in the URL or not.
25          */
26         gboolean url_auth;
27
28         /* Expected passwords, 1 character each. (As with the provided
29          * passwords, with the addition that '0' means "no
30          * Authorization header expected".) Used to verify that soup
31          * used the password it was supposed to at each step.
32          */
33         const char *expected;
34
35         /* What the final status code should be. */
36         guint final_status;
37 } SoupAuthTest;
38
39 static SoupAuthTest main_tests[] = {
40         { "No auth available, should fail",
41           "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
42
43         { "Should fail with no auth, fail again with bad password, and give up",
44           "Basic/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
45
46         { "Auth provided this time, so should succeed",
47           "Basic/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
48
49         { "Now should automatically reuse previous auth",
50           "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
51
52         { "Subdir should also automatically reuse auth",
53           "Basic/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
54
55         { "Subdir should retry last auth, but will fail this time",
56           "Basic/realm1/realm2/", "", FALSE, "1", SOUP_STATUS_UNAUTHORIZED },
57
58         { "Now should use provided auth",
59           "Basic/realm1/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
60
61         { "Reusing last auth. Should succeed on first try",
62           "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
63
64         { "Reuse will fail, but 2nd try will succeed because it's a known realm",
65           "Basic/realm1/realm2/realm1/", "", FALSE, "21", SOUP_STATUS_OK },
66
67         { "Should succeed on first try. (Known realm with cached password)",
68           "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
69
70         { "Fail once, then use typoed password, then use right password",
71           "Basic/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
72
73
74         { "No auth available, should fail",
75           "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
76
77         { "Should fail with no auth, fail again with bad password, and give up",
78           "Digest/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
79
80         { "Known realm, auth provided, so should succeed",
81           "Digest/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
82
83         { "Now should automatically reuse previous auth",
84           "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
85
86         { "Subdir should also automatically reuse auth",
87           "Digest/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
88
89         { "Password provided, should succeed",
90           "Digest/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
91
92         { "Should already know correct domain and use provided auth on first try",
93           "Digest/realm1/realm2/", "2", FALSE, "2", SOUP_STATUS_OK },
94
95         { "Reusing last auth. Should succeed on first try",
96           "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
97
98         { "Should succeed on first try because of earlier domain directive",
99           "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
100
101         { "Fail once, then use typoed password, then use right password",
102           "Digest/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
103
104
105         { "Make sure we haven't forgotten anything",
106           "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
107
108         { "Make sure we haven't forgotten anything",
109           "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
110
111         { "Make sure we haven't forgotten anything",
112           "Basic/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
113
114         { "Make sure we haven't forgotten anything",
115           "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
116
117         { "Make sure we haven't forgotten anything",
118           "Basic/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
119
120
121         { "Make sure we haven't forgotten anything",
122           "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
123
124         { "Make sure we haven't forgotten anything",
125           "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
126
127         { "Make sure we haven't forgotten anything",
128           "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
129
130         { "Make sure we haven't forgotten anything",
131           "Digest/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
132
133         { "Make sure we haven't forgotten anything",
134           "Digest/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
135
136         { "Now the server will reject the formerly-good password",
137           "Basic/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
138
139         { "Make sure we've forgotten it",
140           "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
141
142         { "Likewise, reject the formerly-good Digest password",
143           "Digest/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
144
145         { "Make sure we've forgotten it",
146           "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
147
148         { "Fail with URI-embedded password, then use right password in the authenticate signal",
149           "Basic/realm3/", "43", TRUE, "43", SOUP_STATUS_OK },
150
151         { NULL }
152 };
153
154 static const char *auths[] = {
155         "no password", "password 1",
156         "password 2", "password 3",
157         "intentionally wrong password",
158 };
159
160 static int
161 identify_auth (SoupMessage *msg)
162 {
163         const char *header;
164         int num;
165
166         header = soup_message_headers_get_one (msg->request_headers,
167                                                "Authorization");
168         if (!header)
169                 return 0;
170
171         if (!g_ascii_strncasecmp (header, "Basic ", 6)) {
172                 char *token;
173                 gsize len;
174
175                 token = (char *)g_base64_decode (header + 6, &len);
176                 num = token[len - 1] - '0';
177                 g_free (token);
178         } else {
179                 const char *user;
180
181                 user = strstr (header, "username=\"user");
182                 if (user)
183                         num = user[14] - '0';
184                 else
185                         num = 0;
186         }
187
188         g_assert (num >= 0 && num <= 4);
189
190         return num;
191 }
192
193 static void
194 handler (SoupMessage *msg, gpointer data)
195 {
196         char *expected = data;
197         int auth, exp;
198
199         auth = identify_auth (msg);
200
201         debug_printf (1, "  %d %s (using %s)\n",
202                       msg->status_code, msg->reason_phrase,
203                       auths[auth]);
204
205         if (*expected) {
206                 exp = *expected - '0';
207                 soup_test_assert (auth == exp,
208                                   "expected %s", auths[exp]);
209                 memmove (expected, expected + 1, strlen (expected));
210         } else {
211                 soup_test_assert (*expected,
212                                   "expected to be finished");
213         }
214 }
215
216 static void
217 authenticate (SoupSession *session, SoupMessage *msg,
218               SoupAuth *auth, gboolean retrying, gpointer data)
219 {
220         SoupAuthTest *test = data;
221         char *username, *password;
222         char num;
223
224         if (!test->provided[0])
225                 return;
226         if (retrying) {
227                 if (!test->provided[1])
228                         return;
229                 num = test->provided[1];
230         } else
231                 num = test->provided[0];
232
233         username = g_strdup_printf ("user%c", num);
234         password = g_strdup_printf ("realm%c", num);
235         soup_auth_authenticate (auth, username, password);
236         g_free (username);
237         g_free (password);
238 }
239
240 static void
241 bug271540_sent (SoupMessage *msg, gpointer data)
242 {
243         int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
244         gboolean *authenticated = data;
245         int auth = identify_auth (msg);
246
247         soup_test_assert (*authenticated || !auth,
248                           "using auth on message %d before authenticating", n);
249         soup_test_assert (!*authenticated || auth,
250                           "sent unauthenticated message %d after authenticating", n);
251 }
252
253 static void
254 bug271540_authenticate (SoupSession *session, SoupMessage *msg,
255                         SoupAuth *auth, gboolean retrying, gpointer data)
256 {
257         int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
258         gboolean *authenticated = data;
259
260         if (strcmp (soup_auth_get_scheme_name (auth), "Basic") != 0 ||
261             strcmp (soup_auth_get_realm (auth), "realm1") != 0)
262                 return;
263
264         if (!*authenticated) {
265                 debug_printf (1, "    authenticating message %d\n", n);
266                 soup_auth_authenticate (auth, "user1", "realm1");
267                 *authenticated = TRUE;
268         } else {
269                 soup_test_assert (!*authenticated,
270                                   "asked to authenticate message %d after authenticating", n);
271         }
272 }
273
274 static void
275 bug271540_finished (SoupSession *session, SoupMessage *msg, gpointer data)
276 {
277         int *left = data;
278
279         soup_test_assert_message_status (msg, SOUP_STATUS_OK);
280
281         (*left)--;
282         if (!*left)
283                 g_main_loop_quit (loop);
284 }
285
286 static void
287 do_pipelined_auth_test (void)
288 {
289         SoupSession *session;
290         SoupMessage *msg;
291         gboolean authenticated;
292         char *uri;
293         int i;
294
295         g_test_bug ("271540");
296
297         SOUP_TEST_SKIP_IF_NO_APACHE;
298
299         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
300
301         authenticated = FALSE;
302         g_signal_connect (session, "authenticate",
303                           G_CALLBACK (bug271540_authenticate), &authenticated);
304
305         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
306         for (i = 0; i < 10; i++) {
307                 msg = soup_message_new (SOUP_METHOD_GET, uri);
308                 g_object_set_data (G_OBJECT (msg), "#", GINT_TO_POINTER (i + 1));
309                 g_signal_connect (msg, "wrote_headers",
310                                   G_CALLBACK (bug271540_sent), &authenticated);
311
312                 soup_session_queue_message (session, msg,
313                                             bug271540_finished, &i);
314         }
315         g_free (uri);
316
317         loop = g_main_loop_new (NULL, TRUE);
318         g_main_loop_run (loop);
319         g_main_loop_unref (loop);
320
321         soup_test_session_abort_unref (session);
322 }
323
324 /* We test two different things here:
325  *
326  *   1. If we get a 401 response with "WWW-Authenticate: Digest
327  *      stale=true...", we should retry and succeed *without* the
328  *      session asking for a password again.
329  *
330  *   2. If we get a successful response with "Authentication-Info:
331  *      nextnonce=...", we should update the nonce automatically so as
332  *      to avoid getting a stale nonce error on the next request.
333  *
334  * In our Apache config, /Digest/realm1 and /Digest/realm1/expire are
335  * set up to use the same auth info, but only the latter has an
336  * AuthDigestNonceLifetime (of 2 seconds). The way nonces work in
337  * Apache, a nonce received from /Digest/realm1 will still expire in
338  * /Digest/realm1/expire, but it won't issue a nextnonce for a request
339  * in /Digest/realm1. This lets us test both behaviors.
340  *
341  * The expected conversation is:
342  *
343  * First message
344  *   GET /Digest/realm1
345  *
346  *   401 Unauthorized
347  *   WWW-Authenticate: Digest nonce=A
348  *
349  *   [emit 'authenticate']
350  *
351  *   GET /Digest/realm1
352  *   Authorization: Digest nonce=A
353  *
354  *   200 OK
355  *   [No Authentication-Info]
356  *
357  * [sleep 2 seconds: nonce A is no longer valid, but we have no
358  * way of knowing that]
359  *
360  * Second message
361  *   GET /Digest/realm1/expire/
362  *   Authorization: Digest nonce=A
363  *
364  *   401 Unauthorized
365  *   WWW-Authenticate: Digest stale=true nonce=B
366  *
367  *   GET /Digest/realm1/expire/
368  *   Authorization: Digest nonce=B
369  *
370  *   200 OK
371  *   Authentication-Info: nextnonce=C
372  *
373  * [sleep 1 second]
374  *
375  * Third message
376  *   GET /Digest/realm1/expire/
377  *   Authorization: Digest nonce=C
378  *   [nonce=B would work here too]
379  *
380  *   200 OK
381  *   Authentication-Info: nextnonce=D
382  *
383  * [sleep 1 second; nonces B and C are no longer valid, but D is]
384  *
385  * Fourth message
386  *   GET /Digest/realm1/expire/
387  *   Authorization: Digest nonce=D
388  *
389  *   200 OK
390  *   Authentication-Info: nextnonce=D
391  *
392  */
393
394 static void
395 digest_nonce_authenticate (SoupSession *session, SoupMessage *msg,
396                            SoupAuth *auth, gboolean retrying, gpointer data)
397 {
398         if (retrying)
399                 return;
400
401         if (strcmp (soup_auth_get_scheme_name (auth), "Digest") != 0 ||
402             strcmp (soup_auth_get_realm (auth), "realm1") != 0)
403                 return;
404
405         soup_auth_authenticate (auth, "user1", "realm1");
406 }
407
408 static void
409 digest_nonce_unauthorized (SoupMessage *msg, gpointer data)
410 {
411         gboolean *got_401 = data;
412         *got_401 = TRUE;
413 }
414
415 static void
416 do_digest_nonce_test (SoupSession *session,
417                       const char *nth, const char *uri, gboolean use_auth_cache,
418                       gboolean expect_401, gboolean expect_signal)
419 {
420         SoupMessage *msg;
421         gboolean got_401;
422
423         msg = soup_message_new (SOUP_METHOD_GET, uri);
424         if (!use_auth_cache) {
425                 SoupMessageFlags flags = soup_message_get_flags (msg);
426
427                 soup_message_set_flags (msg, flags | SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE);
428         }
429         if (expect_signal) {
430                 g_signal_connect (session, "authenticate",
431                                   G_CALLBACK (digest_nonce_authenticate),
432                                   NULL);
433         }
434         soup_message_add_status_code_handler (msg, "got_headers",
435                                               SOUP_STATUS_UNAUTHORIZED,
436                                               G_CALLBACK (digest_nonce_unauthorized),
437                                               &got_401);
438         got_401 = FALSE;
439         soup_session_send_message (session, msg);
440         soup_test_assert (got_401 == expect_401,
441                           "%s request %s a 401 Unauthorized!\n", nth,
442                           got_401 ? "got" : "did not get");
443         soup_test_assert_message_status (msg, SOUP_STATUS_OK);
444
445         if (expect_signal) {
446                 g_signal_handlers_disconnect_by_func (session,
447                                                       G_CALLBACK (digest_nonce_authenticate),
448                                                       NULL);
449         }
450
451         g_object_unref (msg);
452 }
453
454 static void
455 do_digest_expiration_test (void)
456 {
457         SoupSession *session;
458         char *uri;
459
460         SOUP_TEST_SKIP_IF_NO_APACHE;
461
462         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
463
464         uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
465         do_digest_nonce_test (session, "First", uri, TRUE, TRUE, TRUE);
466         g_free (uri);
467         g_usleep (2 * G_USEC_PER_SEC);
468         uri = g_strconcat (base_uri, "Digest/realm1/expire/", NULL);
469         do_digest_nonce_test (session, "Second", uri, TRUE, TRUE, FALSE);
470         g_usleep (1 * G_USEC_PER_SEC);
471         do_digest_nonce_test (session, "Third", uri, TRUE, FALSE, FALSE);
472         g_usleep (1 * G_USEC_PER_SEC);
473         do_digest_nonce_test (session, "Fourth", uri, TRUE, FALSE, FALSE);
474         g_free (uri);
475
476         soup_test_session_abort_unref (session);
477 }
478
479 /* Async auth test. We queue three requests to /Basic/realm1, ensuring
480  * that they are sent in order. The first and third ones will be
481  * paused from the authentication callback. The second will be allowed
482  * to fail. Shortly after the third one requests auth, we'll provide
483  * the auth and unpause the two remaining messages, allowing them to
484  * succeed.
485  */
486
487 static void
488 async_authenticate (SoupSession *session, SoupMessage *msg,
489                     SoupAuth *auth, gboolean retrying, gpointer data)
490 {
491         SoupAuth **saved_auth = data;
492         int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
493
494         debug_printf (2, "  async_authenticate msg%d\n", id);
495
496         /* The session will try to authenticate msg3 *before* sending
497          * it, because it already knows it's going to need the auth.
498          * Ignore that.
499          */
500         if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
501                 debug_printf (2, "    (ignoring)\n");
502                 return;
503         }
504
505         soup_session_pause_message (session, msg);
506         if (saved_auth)
507                 *saved_auth = g_object_ref (auth);
508         g_main_loop_quit (loop);
509 }
510
511 static void
512 async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
513 {
514         int *remaining = user_data;
515         int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
516
517         debug_printf (2, "  async_finished msg%d\n", id);
518
519         (*remaining)--;
520         if (!*remaining)
521                 g_main_loop_quit (loop);
522 }
523
524 static void
525 async_authenticate_assert_once (SoupSession *session, SoupMessage *msg,
526                                 SoupAuth *auth, gboolean retrying, gpointer data)
527 {
528         gboolean *been_here = data;
529
530         debug_printf (2, "  async_authenticate_assert_once\n");
531
532         soup_test_assert (!*been_here,
533                           "async_authenticate_assert_once called twice");
534         *been_here = TRUE;
535 }
536
537 static void
538 async_authenticate_assert_once_and_stop (SoupSession *session, SoupMessage *msg,
539                                          SoupAuth *auth, gboolean retrying, gpointer data)
540 {
541         gboolean *been_here = data;
542
543         debug_printf (2, "  async_authenticate_assert_once_and_stop\n");
544
545         soup_test_assert (!*been_here,
546                           "async_authenticate_assert_once called twice");
547         *been_here = TRUE;
548
549         soup_session_pause_message (session, msg);
550         g_main_loop_quit (loop);
551 }
552
553 static void
554 do_async_auth_good_password_test (void)
555 {
556         SoupSession *session;
557         SoupMessage *msg1, *msg2, *msg3, msg2_bak;
558         guint auth_id;
559         char *uri;
560         SoupAuth *auth = NULL;
561         int remaining;
562
563         SOUP_TEST_SKIP_IF_NO_APACHE;
564
565         loop = g_main_loop_new (NULL, TRUE);
566         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
567         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
568         remaining = 0;
569
570         msg1 = soup_message_new ("GET", uri);
571         g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
572         auth_id = g_signal_connect (session, "authenticate",
573                                     G_CALLBACK (async_authenticate), &auth);
574         g_object_ref (msg1);
575         remaining++;
576         soup_session_queue_message (session, msg1, async_finished, &remaining);
577         g_main_loop_run (loop);
578         g_signal_handler_disconnect (session, auth_id);
579
580         /* async_authenticate will pause msg1 and quit loop */
581
582         msg2 = soup_message_new ("GET", uri);
583         g_object_set_data (G_OBJECT (msg2), "id", GINT_TO_POINTER (2));
584         soup_session_send_message (session, msg2);
585
586         soup_test_assert_message_status (msg2, SOUP_STATUS_UNAUTHORIZED);
587
588         /* msg2 should be done at this point; assuming everything is
589          * working correctly, the session won't look at it again; we
590          * ensure that if it does, it will crash the test program.
591          */
592         memcpy (&msg2_bak, msg2, sizeof (SoupMessage));
593         memset (msg2, 0, sizeof (SoupMessage));
594
595         msg3 = soup_message_new ("GET", uri);
596         g_object_set_data (G_OBJECT (msg3), "id", GINT_TO_POINTER (3));
597         auth_id = g_signal_connect (session, "authenticate",
598                                     G_CALLBACK (async_authenticate), NULL);
599         g_object_ref (msg3);
600         remaining++;
601         soup_session_queue_message (session, msg3, async_finished, &remaining);
602         g_main_loop_run (loop);
603         g_signal_handler_disconnect (session, auth_id);
604
605         /* async_authenticate will pause msg3 and quit loop */
606
607         /* Now do the auth, and restart */
608         if (auth) {
609                 soup_auth_authenticate (auth, "user1", "realm1");
610                 g_object_unref (auth);
611                 soup_session_unpause_message (session, msg1);
612                 soup_session_unpause_message (session, msg3);
613
614                 g_main_loop_run (loop);
615
616                 /* async_finished will quit the loop */
617         } else
618                 soup_test_assert (auth, "msg1 didn't get authenticate signal");
619
620         soup_test_assert_message_status (msg1, SOUP_STATUS_OK);
621         soup_test_assert_message_status (msg3, SOUP_STATUS_OK);
622
623         soup_test_session_abort_unref (session);
624
625         g_object_unref (msg1);
626         g_object_unref (msg3);
627         memcpy (msg2, &msg2_bak, sizeof (SoupMessage));
628         g_object_unref (msg2);
629
630         g_free (uri);
631         g_main_loop_unref (loop);
632 }
633
634 static void
635 do_async_auth_bad_password_test (void)
636 {
637         SoupSession *session;
638         SoupMessage *msg;
639         guint auth_id;
640         char *uri;
641         SoupAuth *auth = NULL;
642         int remaining;
643         gboolean been_there;
644
645         /* Test that giving the wrong password doesn't cause multiple
646          * authenticate signals the second time.
647          */
648         g_test_bug ("522601");
649
650         SOUP_TEST_SKIP_IF_NO_APACHE;
651
652         loop = g_main_loop_new (NULL, TRUE);
653         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
654         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
655         remaining = 0;
656         auth = NULL;
657
658         msg = soup_message_new ("GET", uri);
659         g_object_set_data (G_OBJECT (msg), "id", GINT_TO_POINTER (1));
660         auth_id = g_signal_connect (session, "authenticate",
661                                     G_CALLBACK (async_authenticate), &auth);
662         g_object_ref (msg);
663         remaining++;
664         soup_session_queue_message (session, msg, async_finished, &remaining);
665         g_main_loop_run (loop);
666         g_signal_handler_disconnect (session, auth_id);
667         soup_auth_authenticate (auth, "user1", "wrong");
668         g_object_unref (auth);
669         soup_session_unpause_message (session, msg);
670
671         been_there = FALSE;
672         auth_id = g_signal_connect (session, "authenticate",
673                                     G_CALLBACK (async_authenticate_assert_once),
674                                     &been_there);
675         g_main_loop_run (loop);
676         g_signal_handler_disconnect (session, auth_id);
677
678         soup_test_assert (been_there,
679                           "authenticate not emitted");
680
681         soup_test_session_abort_unref (session);
682         g_object_unref (msg);
683
684         g_free (uri);
685         g_main_loop_unref (loop);
686 }
687
688 static void
689 do_async_auth_no_password_test (void)
690 {
691         SoupSession *session;
692         SoupMessage *msg;
693         guint auth_id;
694         char *uri;
695         int remaining;
696         gboolean been_there;
697
698         /* Test that giving no password doesn't cause multiple
699          * authenticate signals the second time.
700          */
701         g_test_bug ("583462");
702
703         SOUP_TEST_SKIP_IF_NO_APACHE;
704
705         loop = g_main_loop_new (NULL, TRUE);
706         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
707         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
708         remaining = 0;
709
710         /* Send a message that doesn't actually authenticate
711          */
712         msg = soup_message_new ("GET", uri);
713         g_object_set_data (G_OBJECT (msg), "id", GINT_TO_POINTER (1));
714         auth_id = g_signal_connect (session, "authenticate",
715                                     G_CALLBACK (async_authenticate), NULL);
716         g_object_ref (msg);
717         remaining++;
718         soup_session_queue_message (session, msg, async_finished, &remaining);
719         g_main_loop_run (loop);
720         g_signal_handler_disconnect (session, auth_id);
721         soup_session_unpause_message (session, msg);
722         g_main_loop_run (loop);
723         g_object_unref(msg);
724
725         /* Now send a second message */
726         msg = soup_message_new ("GET", uri);
727         g_object_set_data (G_OBJECT (msg), "id", GINT_TO_POINTER (2));
728         g_object_ref (msg);
729         been_there = FALSE;
730         auth_id = g_signal_connect (session, "authenticate",
731                                     G_CALLBACK (async_authenticate_assert_once_and_stop),
732                                     &been_there);
733         remaining++;
734         soup_session_queue_message (session, msg, async_finished, &remaining);
735         g_main_loop_run (loop);
736         soup_session_unpause_message (session, msg);
737
738         g_main_loop_run (loop);
739         g_signal_handler_disconnect (session, auth_id);
740
741         soup_test_session_abort_unref (session);
742         g_object_unref (msg);
743
744         g_free (uri);
745         g_main_loop_unref (loop);
746 }
747
748 typedef struct {
749         const char *password;
750         struct {
751                 const char *headers;
752                 const char *response;
753         } round[2];
754 } SelectAuthData;
755
756 static void
757 select_auth_authenticate (SoupSession *session, SoupMessage *msg,
758                           SoupAuth *auth, gboolean retrying, gpointer data)
759 {
760         SelectAuthData *sad = data;
761         const char *header, *basic, *digest;
762         int round = retrying ? 1 : 0;
763
764         header = soup_message_headers_get_list (msg->response_headers,
765                                                 "WWW-Authenticate");
766         basic = strstr (header, "Basic");
767         digest = strstr (header, "Digest");
768         if (basic && digest) {
769                 if (basic < digest)
770                         sad->round[round].headers = "Basic, Digest";
771                 else
772                         sad->round[round].headers = "Digest, Basic";
773         } else if (basic)
774                 sad->round[round].headers = "Basic";
775         else if (digest)
776                 sad->round[round].headers = "Digest";
777
778         sad->round[round].response = soup_auth_get_scheme_name (auth);
779         if (sad->password && !retrying)
780                 soup_auth_authenticate (auth, "user", sad->password);
781 }
782
783 static void
784 select_auth_test_one (SoupURI *uri,
785                       gboolean disable_digest, const char *password,
786                       const char *first_headers, const char *first_response,
787                       const char *second_headers, const char *second_response,
788                       guint final_status)
789 {
790         SelectAuthData sad;
791         SoupMessage *msg;
792         SoupSession *session;
793
794         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
795         if (disable_digest)
796                 soup_session_remove_feature_by_type (session, SOUP_TYPE_AUTH_DIGEST);
797
798         g_signal_connect (session, "authenticate",
799                           G_CALLBACK (select_auth_authenticate), &sad);
800         memset (&sad, 0, sizeof (sad));
801         sad.password = password;
802
803         msg = soup_message_new_from_uri ("GET", uri);
804         soup_session_send_message (session, msg);
805
806         soup_test_assert (strcmp (sad.round[0].headers, first_headers) == 0,
807                           "Header order wrong: expected %s, got %s",
808                           first_headers, sad.round[0].headers);
809         soup_test_assert (strcmp (sad.round[0].response, first_response) == 0,
810                           "Selected auth type wrong: expected %s, got %s",
811                           first_response, sad.round[0].response);
812
813         soup_test_assert (sad.round[1].headers || !second_headers,
814                           "Expected a second round");
815         soup_test_assert (!sad.round[1].headers || second_headers,
816                           "Didn't expect a second round");
817         if (second_headers && second_response) {
818                 soup_test_assert (strcmp (sad.round[1].headers, second_headers) == 0,
819                                   "Second round header order wrong: expected %s, got %s\n",
820                                   second_headers, sad.round[1].headers);
821                 soup_test_assert (strcmp (sad.round[1].response, second_response) == 0,
822                                   "Second round selected auth type wrong: expected %s, got %s\n",
823                                   second_response, sad.round[1].response);
824         }
825
826         soup_test_assert_message_status (msg, final_status);
827
828         g_object_unref (msg);
829         soup_test_session_abort_unref (session);
830 }
831
832 static void
833 server_callback (SoupServer *server, SoupMessage *msg,
834                  const char *path, GHashTable *query,
835                  SoupClientContext *context, gpointer data)
836 {
837         soup_message_set_response (msg, "text/plain",
838                                    SOUP_MEMORY_STATIC,
839                                    "OK\r\n", 4);
840         soup_message_set_status (msg, SOUP_STATUS_OK);
841 }
842
843 static gboolean
844 server_basic_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
845                             const char *username, const char *password, gpointer data)
846 {
847         if (strcmp (username, "user") != 0)
848                 return FALSE;
849         return strcmp (password, "good-basic") == 0;
850 }
851
852 static char *
853 server_digest_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
854                              const char *username, gpointer data)
855 {
856         if (strcmp (username, "user") != 0)
857                 return NULL;
858         return soup_auth_domain_digest_encode_password ("user",
859                                                         "auth-test",
860                                                         "good");
861 }
862
863 static void
864 do_select_auth_test (void)
865 {
866         SoupServer *server;
867         SoupAuthDomain *basic_auth_domain, *digest_auth_domain;
868         SoupURI *uri;
869
870         g_test_bug ("562339");
871
872         /* It doesn't seem to be possible to configure Apache to serve
873          * multiple auth types for a single URL. So we have to use
874          * SoupServer here. We know that SoupServer handles the server
875          * side of this scenario correctly, because we test it against
876          * curl in server-auth-test.
877          */
878         server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
879         soup_server_add_handler (server, NULL,
880                                  server_callback, NULL, NULL);
881         uri = soup_test_server_get_uri (server, "http", NULL);
882
883         basic_auth_domain = soup_auth_domain_basic_new (
884                 SOUP_AUTH_DOMAIN_REALM, "auth-test",
885                 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
886                 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
887                 NULL);
888         soup_server_add_auth_domain (server, basic_auth_domain);
889
890         digest_auth_domain = soup_auth_domain_digest_new (
891                 SOUP_AUTH_DOMAIN_REALM, "auth-test",
892                 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
893                 SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, server_digest_auth_callback,
894                 NULL);
895         soup_server_add_auth_domain (server, digest_auth_domain);
896
897         debug_printf (1, "  Testing with no auth\n");
898         select_auth_test_one (uri, FALSE, NULL,
899                               "Basic, Digest", "Digest",
900                               NULL, NULL,
901                               SOUP_STATUS_UNAUTHORIZED);
902
903         debug_printf (1, "  Testing with bad password\n");
904         select_auth_test_one (uri, FALSE, "bad",
905                               "Basic, Digest", "Digest",
906                               "Basic, Digest", "Digest",
907                               SOUP_STATUS_UNAUTHORIZED);
908
909         debug_printf (1, "  Testing with good password\n");
910         select_auth_test_one (uri, FALSE, "good",
911                               "Basic, Digest", "Digest",
912                               NULL, NULL,
913                               SOUP_STATUS_OK);
914
915         /* Test with Digest disabled in the client. */
916         debug_printf (1, "  Testing without Digest with no auth\n");
917         select_auth_test_one (uri, TRUE, NULL,
918                               "Basic, Digest", "Basic",
919                               NULL, NULL,
920                               SOUP_STATUS_UNAUTHORIZED);
921
922         debug_printf (1, "  Testing without Digest with bad password\n");
923         select_auth_test_one (uri, TRUE, "bad",
924                               "Basic, Digest", "Basic",
925                               "Basic, Digest", "Basic",
926                               SOUP_STATUS_UNAUTHORIZED);
927
928         debug_printf (1, "  Testing without Digest with good password\n");
929         select_auth_test_one (uri, TRUE, "good-basic",
930                               "Basic, Digest", "Basic",
931                               NULL, NULL,
932                               SOUP_STATUS_OK);
933
934         /* Now flip the order of the domains, verify that this flips
935          * the order of the headers, and make sure that digest auth
936          * *still* gets used.
937          */
938
939         soup_server_remove_auth_domain (server, basic_auth_domain);
940         soup_server_remove_auth_domain (server, digest_auth_domain);
941         soup_server_add_auth_domain (server, digest_auth_domain);
942         soup_server_add_auth_domain (server, basic_auth_domain);
943
944         debug_printf (1, "  Testing flipped with no auth\n");
945         select_auth_test_one (uri, FALSE, NULL,
946                               "Digest, Basic", "Digest",
947                               NULL, NULL,
948                               SOUP_STATUS_UNAUTHORIZED);
949
950         debug_printf (1, "  Testing flipped with bad password\n");
951         select_auth_test_one (uri, FALSE, "bad",
952                               "Digest, Basic", "Digest",
953                               "Digest, Basic", "Digest",
954                               SOUP_STATUS_UNAUTHORIZED);
955
956         debug_printf (1, "  Testing flipped with good password\n");
957         select_auth_test_one (uri, FALSE, "good",
958                               "Digest, Basic", "Digest",
959                               NULL, NULL,
960                               SOUP_STATUS_OK);
961
962         g_object_unref (basic_auth_domain);
963         g_object_unref (digest_auth_domain);
964         soup_uri_free (uri);
965         soup_test_server_quit_unref (server);
966 }
967
968 static void
969 sneakily_close_connection (SoupMessage *msg, gpointer user_data)
970 {
971         /* Sneakily close the connection after the response, by
972          * tricking soup-message-io into thinking that had been
973          * the plan all along.
974          */
975         soup_message_headers_append (msg->response_headers,
976                                      "Connection", "close");
977 }
978
979 static void
980 auth_close_request_started (SoupServer *server, SoupMessage *msg,
981                             SoupClientContext *client, gpointer user_data)
982 {
983         g_signal_connect (msg, "wrote-headers",
984                           G_CALLBACK (sneakily_close_connection), NULL);
985 }
986
987 typedef struct {
988         SoupSession *session;
989         SoupMessage *msg;
990         SoupAuth *auth;
991 } AuthCloseData;
992
993 static gboolean
994 auth_close_idle_authenticate (gpointer user_data)
995 {
996         AuthCloseData *acd = user_data;
997
998         soup_auth_authenticate (acd->auth, "user", "good-basic");
999         soup_session_unpause_message (acd->session, acd->msg);
1000
1001         g_object_unref (acd->auth);
1002         return FALSE;
1003 }
1004
1005 static void
1006 auth_close_authenticate (SoupSession *session, SoupMessage *msg,
1007                          SoupAuth *auth, gboolean retrying, gpointer data)
1008 {
1009         AuthCloseData *acd = data;
1010
1011         soup_session_pause_message (session, msg);
1012         acd->auth = g_object_ref (auth);
1013         g_idle_add (auth_close_idle_authenticate, acd);
1014 }
1015
1016 static void
1017 do_auth_close_test (void)
1018 {
1019         SoupServer *server;
1020         SoupAuthDomain *basic_auth_domain;
1021         SoupURI *uri;
1022         AuthCloseData acd;
1023
1024         server = soup_test_server_new (SOUP_TEST_SERVER_DEFAULT);
1025         soup_server_add_handler (server, NULL,
1026                                  server_callback, NULL, NULL);
1027
1028         uri = soup_test_server_get_uri (server, "http", NULL);
1029         soup_uri_set_path (uri, "/close");
1030
1031         basic_auth_domain = soup_auth_domain_basic_new (
1032                 SOUP_AUTH_DOMAIN_REALM, "auth-test",
1033                 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
1034                 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
1035                 NULL);
1036         soup_server_add_auth_domain (server, basic_auth_domain);
1037         g_object_unref (basic_auth_domain);
1038
1039         g_signal_connect (server, "request-started",
1040                           G_CALLBACK (auth_close_request_started), NULL);
1041
1042         acd.session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1043         g_signal_connect (acd.session, "authenticate",
1044                           G_CALLBACK (auth_close_authenticate), &acd);
1045
1046         acd.msg = soup_message_new_from_uri ("GET", uri);
1047         soup_uri_free (uri);
1048         soup_session_send_message (acd.session, acd.msg);
1049
1050         soup_test_assert_message_status (acd.msg, SOUP_STATUS_OK);
1051
1052         g_object_unref (acd.msg);
1053         soup_test_session_abort_unref (acd.session);
1054         soup_test_server_quit_unref (server);
1055 }
1056
1057 static gboolean
1058 infinite_cancel (gpointer session)
1059 {
1060         soup_session_abort (session);
1061         return FALSE;
1062 }
1063
1064 static void
1065 infinite_authenticate (SoupSession *session, SoupMessage *msg,
1066                        SoupAuth *auth, gboolean retrying, gpointer data)
1067 {
1068         soup_auth_authenticate (auth, "user", "bad");
1069 }
1070
1071 static void
1072 do_infinite_auth_test (void)
1073 {
1074         SoupSession *session;
1075         SoupMessage *msg;
1076         char *uri;
1077         int timeout;
1078
1079         SOUP_TEST_SKIP_IF_NO_APACHE;
1080
1081         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1082         g_signal_connect (session, "authenticate",
1083                           G_CALLBACK (infinite_authenticate), NULL);
1084
1085         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
1086         msg = soup_message_new ("GET", uri);
1087         g_free (uri);
1088
1089         timeout = g_timeout_add (500, infinite_cancel, session);
1090         g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING,
1091                                "*stuck in infinite loop*");
1092         soup_session_send_message (session, msg);
1093         g_test_assert_expected_messages ();
1094
1095         soup_test_assert (msg->status_code != SOUP_STATUS_CANCELLED,
1096                           "Got stuck in loop");
1097         soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED);
1098
1099         g_source_remove (timeout);
1100         soup_test_session_abort_unref (session);
1101         g_object_unref (msg);
1102 }
1103
1104 static void
1105 disappear_request_read (SoupServer *server, SoupMessage *msg,
1106                         SoupClientContext *context, gpointer user_data)
1107 {
1108         /* Remove the WWW-Authenticate header if this was a failed attempt */
1109         if (soup_message_headers_get_one (msg->request_headers, "Authorization") &&
1110             msg->status_code == SOUP_STATUS_UNAUTHORIZED)
1111                 soup_message_headers_remove (msg->response_headers, "WWW-Authenticate");
1112 }
1113
1114 static void
1115 disappear_authenticate (SoupSession *session, SoupMessage *msg,
1116                         SoupAuth *auth, gboolean retrying, gpointer data)
1117 {
1118         int *counter = data;
1119
1120         (*counter)++;
1121         if (!retrying)
1122                 soup_auth_authenticate (auth, "user", "bad");
1123 }
1124
1125 static void
1126 do_disappearing_auth_test (void)
1127 {
1128         SoupServer *server;
1129         SoupAuthDomain *auth_domain;
1130         SoupURI *uri;
1131         SoupMessage *msg;
1132         SoupSession *session;
1133         int counter;
1134
1135         g_test_bug_base ("https://bugzilla.redhat.com/");
1136         g_test_bug ("916224");
1137
1138         server = soup_test_server_new (FALSE);
1139         soup_server_add_handler (server, NULL,
1140                                  server_callback, NULL, NULL);
1141         uri = soup_test_server_get_uri (server, "http", NULL);
1142
1143         auth_domain = soup_auth_domain_basic_new (
1144                                                   SOUP_AUTH_DOMAIN_REALM, "auth-test",
1145                                                   SOUP_AUTH_DOMAIN_ADD_PATH, "/",
1146                                                   SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
1147                                                   NULL);
1148         soup_server_add_auth_domain (server, auth_domain);
1149         g_signal_connect (server, "request-read",
1150                           G_CALLBACK (disappear_request_read), NULL);
1151
1152         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1153
1154         counter = 0;
1155         g_signal_connect (session, "authenticate",
1156                           G_CALLBACK (disappear_authenticate), &counter);
1157
1158         msg = soup_message_new_from_uri ("GET", uri);
1159         soup_session_send_message (session, msg);
1160
1161         soup_test_assert (counter <= 2,
1162                           "Got stuck in loop");
1163         soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED);
1164
1165         g_object_unref (msg);
1166         soup_test_session_abort_unref (session);
1167
1168         g_object_unref (auth_domain);
1169         soup_uri_free (uri);
1170         soup_test_server_quit_unref (server);
1171 }
1172
1173 static SoupAuthTest relogin_tests[] = {
1174         { "Auth provided via URL, should succeed",
1175           "Basic/realm12/", "1", TRUE, "01", SOUP_STATUS_OK },
1176
1177         { "Now should automatically reuse previous auth",
1178           "Basic/realm12/", "", FALSE, "1", SOUP_STATUS_OK },
1179
1180         { "Different auth provided via URL for the same realm, should succeed",
1181           "Basic/realm12/", "2", TRUE, "2", SOUP_STATUS_OK },
1182
1183         { "Subdir should also automatically reuse auth",
1184           "Basic/realm12/subdir/", "", FALSE, "2", SOUP_STATUS_OK },
1185
1186         { "Should fail with no auth",
1187           "Basic/realm12/", "4", TRUE, "4", SOUP_STATUS_UNAUTHORIZED },
1188
1189         { "Make sure we've forgotten it",
1190           "Basic/realm12/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
1191
1192         { "Should fail with no auth, fail again with bad password, and give up",
1193           "Basic/realm12/", "3", FALSE, "03", SOUP_STATUS_UNAUTHORIZED },
1194
1195         { NULL }
1196 };
1197
1198 /* https://bugzilla.gnome.org/show_bug.cgi?id=755617 */
1199 static SoupAuthTest basic_root_pspace_test[] = {
1200         { "Auth provided via URL, should succeed",
1201           "BasicRoot", "1", TRUE, "01", SOUP_STATUS_OK },
1202
1203         { "Parent dir should automatically reuse auth",
1204           "/", "1", FALSE, "1", SOUP_STATUS_OK },
1205
1206         { NULL }
1207 };
1208
1209 static void
1210 do_batch_tests (gconstpointer data)
1211 {
1212         const SoupAuthTest *current_tests = data;
1213         SoupSession *session;
1214         SoupMessage *msg;
1215         char *expected, *uristr;
1216         SoupURI *base;
1217         guint signal;
1218         int i;
1219
1220         SOUP_TEST_SKIP_IF_NO_APACHE;
1221
1222         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1223         base = soup_uri_new (base_uri);
1224
1225         for (i = 0; current_tests[i].url; i++) {
1226                 SoupURI *soup_uri = soup_uri_new_with_base (base, current_tests[i].url);
1227
1228                 debug_printf (1, "Test %d: %s\n", i + 1, current_tests[i].explanation);
1229
1230                 if (current_tests[i].url_auth) {
1231                         gchar *username = g_strdup_printf ("user%c", current_tests[i].provided[0]);
1232                         gchar *password = g_strdup_printf ("realm%c", current_tests[i].provided[0]);
1233                         soup_uri_set_user (soup_uri, username);
1234                         soup_uri_set_password (soup_uri, password);
1235                         g_free (username);
1236                         g_free (password);
1237                 }
1238
1239                 msg = soup_message_new_from_uri (SOUP_METHOD_GET, soup_uri);
1240                 soup_uri_free (soup_uri);
1241                 if (!msg) {
1242                         g_printerr ("auth-test: Could not parse URI\n");
1243                         exit (1);
1244                 }
1245
1246                 uristr = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1247                 debug_printf (1, "  GET %s\n", uristr);
1248                 g_free (uristr);
1249
1250                 expected = g_strdup (current_tests[i].expected);
1251                 soup_message_add_status_code_handler (
1252                         msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
1253                         G_CALLBACK (handler), expected);
1254                 soup_message_add_status_code_handler (
1255                         msg, "got_headers", SOUP_STATUS_OK,
1256                         G_CALLBACK (handler), expected);
1257
1258                 signal = g_signal_connect (session, "authenticate",
1259                                            G_CALLBACK (authenticate),
1260                                            (gpointer)&current_tests[i]);
1261                 soup_session_send_message (session, msg);
1262                 g_signal_handler_disconnect (session, signal);
1263
1264                 soup_test_assert_message_status (msg, current_tests[i].final_status);
1265                 soup_test_assert (!*expected,
1266                                   "expected %d more round(s)\n",
1267                                   (int)strlen (expected));
1268
1269                 g_free (expected);
1270                 debug_printf (1, "\n");
1271
1272                 g_object_unref (msg);
1273         }
1274         soup_uri_free (base);
1275
1276         soup_test_session_abort_unref (session);
1277 }
1278
1279 static void
1280 do_clear_credentials_test (void)
1281 {
1282         SoupSession *session;
1283         SoupAuthManager *manager;
1284         char *uri;
1285
1286         SOUP_TEST_SKIP_IF_NO_APACHE;
1287
1288         session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
1289
1290         uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
1291         do_digest_nonce_test (session, "First", uri, TRUE, TRUE, TRUE);
1292
1293         manager = SOUP_AUTH_MANAGER (soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER));
1294         soup_auth_manager_clear_cached_credentials (manager);
1295
1296         do_digest_nonce_test (session, "Second", uri, TRUE, TRUE, TRUE);
1297         g_free (uri);
1298
1299         soup_test_session_abort_unref (session);
1300 }
1301
1302 static void
1303 do_message_do_not_use_auth_cache_test (void)
1304 {
1305         SoupSession *session;
1306         SoupAuthManager *manager;
1307         SoupMessage *msg;
1308         SoupMessageFlags flags;
1309         SoupURI *soup_uri;
1310         char *uri;
1311
1312         SOUP_TEST_SKIP_IF_NO_APACHE;
1313
1314         session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
1315
1316         uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
1317
1318         /* First check that cached credentials are not used */
1319         do_digest_nonce_test (session, "First", uri, TRUE, TRUE, TRUE);
1320         do_digest_nonce_test (session, "Second", uri, TRUE, FALSE, FALSE);
1321         do_digest_nonce_test (session, "Third", uri, FALSE, TRUE, TRUE);
1322
1323         /* Passing credentials in the URI should always authenticate
1324          * no matter whether the cache is used or not
1325          */
1326         soup_uri = soup_uri_new (uri);
1327         soup_uri_set_user (soup_uri, "user1");
1328         soup_uri_set_password (soup_uri, "realm1");
1329         msg = soup_message_new_from_uri (SOUP_METHOD_GET, soup_uri);
1330         flags = soup_message_get_flags (msg);
1331         soup_message_set_flags (msg, flags | SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE);
1332         soup_session_send_message (session, msg);
1333         soup_test_assert_message_status (msg, SOUP_STATUS_OK);
1334         g_object_unref (msg);
1335         soup_uri_free (soup_uri);
1336
1337         manager = SOUP_AUTH_MANAGER (soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER));
1338
1339         soup_auth_manager_clear_cached_credentials (manager);
1340
1341         /* Now check that credentials are not stored */
1342         do_digest_nonce_test (session, "First", uri, FALSE, TRUE, TRUE);
1343         do_digest_nonce_test (session, "Second", uri, TRUE, TRUE, TRUE);
1344         do_digest_nonce_test (session, "Third", uri, TRUE, FALSE, FALSE);
1345
1346         /* Credentials were stored for uri, but if we set SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE flag,
1347          * and we don't have the authenticate signal, it should respond with 401
1348          */
1349         msg = soup_message_new (SOUP_METHOD_GET, uri);
1350         flags = soup_message_get_flags (msg);
1351         soup_message_set_flags (msg, flags | SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE);
1352         soup_session_send_message (session, msg);
1353         soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED);
1354         g_object_unref (msg);
1355         g_free (uri);
1356
1357         soup_test_session_abort_unref (session);
1358 }
1359
1360 static void
1361 async_no_auth_cache_authenticate (SoupSession *session, SoupMessage *msg,
1362                                   SoupAuth *auth, gboolean retrying, SoupAuth **auth_out)
1363 {
1364         debug_printf (1, "  async_no_auth_cache_authenticate\n");
1365
1366         soup_session_pause_message (session, msg);
1367         *auth_out = g_object_ref (auth);
1368         g_main_loop_quit (loop);
1369 }
1370
1371 static void
1372 async_no_auth_cache_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
1373 {
1374         debug_printf (1, "  async_no_auth_cache_finished\n");
1375
1376         g_main_loop_quit (loop);
1377 }
1378
1379 static void
1380 do_async_message_do_not_use_auth_cache_test (void)
1381 {
1382         SoupSession *session;
1383         SoupMessage *msg;
1384         char *uri;
1385         SoupAuth *auth = NULL;
1386         SoupMessageFlags flags;
1387
1388         SOUP_TEST_SKIP_IF_NO_APACHE;
1389
1390         loop = g_main_loop_new (NULL, TRUE);
1391         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1392         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
1393
1394         msg = soup_message_new ("GET", uri);
1395         g_free (uri);
1396         g_signal_connect (session, "authenticate",
1397                           G_CALLBACK (async_no_auth_cache_authenticate), &auth);
1398         flags = soup_message_get_flags (msg);
1399         soup_message_set_flags (msg, flags | SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE);
1400         g_object_ref (msg);
1401         soup_session_queue_message (session, msg, async_no_auth_cache_finished, NULL);
1402         g_main_loop_run (loop);
1403
1404         soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED);
1405
1406         soup_test_assert (auth, "msg didn't get authenticate signal");
1407         soup_auth_authenticate (auth, "user1", "realm1");
1408         g_object_unref (auth);
1409
1410         soup_session_unpause_message (session, msg);
1411         g_main_loop_run (loop);
1412
1413         soup_test_assert_message_status (msg, SOUP_STATUS_OK);
1414
1415         soup_test_session_abort_unref (session);
1416         g_object_unref (msg);
1417         g_main_loop_unref (loop);
1418 }
1419
1420 static void
1421 has_authorization_header_authenticate (SoupSession *session, SoupMessage *msg,
1422                                        SoupAuth *auth, gboolean retrying, gpointer data)
1423 {
1424         SoupAuth **saved_auth = data;
1425
1426         soup_auth_authenticate (auth, "user1", "realm1");
1427         *saved_auth = g_object_ref (auth);
1428 }
1429
1430 static void
1431 has_authorization_header_authenticate_assert (SoupSession *session, SoupMessage *msg,
1432                                               SoupAuth *auth, gboolean retrying, gpointer data)
1433 {
1434         soup_test_assert (FALSE, "authenticate emitted unexpectedly");
1435 }
1436
1437 static void
1438 do_message_has_authorization_header_test (void)
1439 {
1440         SoupSession *session;
1441         SoupMessage *msg;
1442         SoupAuthManager *manager;
1443         SoupAuth *auth = NULL;
1444         char *token;
1445         guint auth_id;
1446         char *uri;
1447
1448         g_test_bug ("775882");
1449
1450         SOUP_TEST_SKIP_IF_NO_APACHE;
1451
1452         session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
1453         uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
1454
1455         msg = soup_message_new ("GET", uri);
1456         auth_id = g_signal_connect (session, "authenticate",
1457                           G_CALLBACK (has_authorization_header_authenticate), &auth);
1458         soup_session_send_message (session, msg);
1459         soup_test_assert_message_status (msg, SOUP_STATUS_OK);
1460         soup_test_assert (SOUP_IS_AUTH (auth), "Expected a SoupAuth");
1461         token = soup_auth_get_authorization (auth, msg);
1462         g_object_unref (auth);
1463         g_object_unref (msg);
1464         g_signal_handler_disconnect (session, auth_id);
1465
1466         manager = SOUP_AUTH_MANAGER (soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER));
1467         soup_auth_manager_clear_cached_credentials (manager);
1468
1469         msg = soup_message_new ("GET", uri);
1470         soup_message_headers_replace (msg->request_headers, "Authorization", token);
1471         auth_id = g_signal_connect (session, "authenticate",
1472                                     G_CALLBACK (has_authorization_header_authenticate_assert),
1473                                     NULL);
1474         soup_session_send_message (session, msg);
1475         soup_test_assert_message_status (msg, SOUP_STATUS_OK);
1476         g_object_unref (msg);
1477
1478         /* Check that we can also provide our own Authorization header when not using credentials cache. */
1479         soup_auth_manager_clear_cached_credentials (manager);
1480         msg = soup_message_new ("GET", uri);
1481         soup_message_headers_replace (msg->request_headers, "Authorization", token);
1482         soup_message_set_flags (msg, soup_message_get_flags (msg) | SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE);
1483         soup_session_send_message (session, msg);
1484         soup_test_assert_message_status (msg, SOUP_STATUS_OK);
1485         g_object_unref (msg);
1486         g_free (token);
1487         g_signal_handler_disconnect (session, auth_id);
1488
1489         g_free (uri);
1490         soup_test_session_abort_unref (session);
1491 }
1492
1493 static void
1494 cancel_after_retry_authenticate (SoupSession  *session,
1495                                  SoupMessage  *msg,
1496                                  SoupAuth     *auth,
1497                                  gboolean      retrying,
1498                                  GCancellable *cancellable)
1499 {
1500         if (retrying) {
1501                 soup_session_cancel_message (session, msg, SOUP_STATUS_CANCELLED);
1502                 g_cancellable_cancel (cancellable);
1503         } else
1504                 soup_auth_authenticate (auth, "user1", "wrong");
1505 }
1506
1507 static void
1508 request_send_cb (SoupSession  *session,
1509                  GAsyncResult *result,
1510                  GMainLoop    *loop)
1511 {
1512         GInputStream *stream;
1513         GError *error = NULL;
1514
1515         stream = soup_session_send_finish (session, result, &error);
1516         g_assert_null (stream);
1517         g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
1518
1519         g_main_loop_quit (loop);
1520 }
1521
1522 static void
1523 do_cancel_after_retry_test (void)
1524 {
1525         SoupSession *session;
1526         SoupMessage *msg;
1527         char *uri;
1528         GCancellable *cancellable;
1529         GMainLoop *loop;
1530
1531         SOUP_TEST_SKIP_IF_NO_APACHE;
1532
1533         session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
1534         cancellable = g_cancellable_new ();
1535         g_signal_connect (session, "authenticate",
1536                           G_CALLBACK (cancel_after_retry_authenticate),
1537                           cancellable);
1538
1539         loop = g_main_loop_new (NULL, FALSE);
1540
1541         uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
1542         msg = soup_message_new ("GET", uri);
1543         soup_session_send_async (session, msg, cancellable, (GAsyncReadyCallback)request_send_cb, loop);
1544         g_main_loop_run (loop);
1545
1546         g_object_unref (msg);
1547         g_object_unref (cancellable);
1548         g_free (uri);
1549         soup_test_session_abort_unref (session);
1550 }
1551
1552 int
1553 main (int argc, char **argv)
1554 {
1555         int ret;
1556
1557         test_init (argc, argv, NULL);
1558         apache_init ();
1559
1560         base_uri = "http://127.0.0.1:47524/";
1561
1562         g_test_add_data_func ("/auth/main-tests", main_tests, do_batch_tests);
1563         g_test_add_data_func ("/auth/relogin-tests", relogin_tests, do_batch_tests);
1564         g_test_add_data_func ("/auth/basic-root-pspec-test", basic_root_pspace_test, do_batch_tests);
1565         g_test_add_func ("/auth/pipelined-auth", do_pipelined_auth_test);
1566         g_test_add_func ("/auth/digest-expiration", do_digest_expiration_test);
1567         g_test_add_func ("/auth/async-auth/good-password", do_async_auth_good_password_test);
1568         g_test_add_func ("/auth/async-auth/bad-password", do_async_auth_bad_password_test);
1569         g_test_add_func ("/auth/async-auth/no-password", do_async_auth_no_password_test);
1570         g_test_add_func ("/auth/select-auth", do_select_auth_test);
1571         g_test_add_func ("/auth/auth-close", do_auth_close_test);
1572         g_test_add_func ("/auth/infinite-auth", do_infinite_auth_test);
1573         g_test_add_func ("/auth/disappearing-auth", do_disappearing_auth_test);
1574         g_test_add_func ("/auth/clear-credentials", do_clear_credentials_test);
1575         g_test_add_func ("/auth/message-do-not-use-auth-cache", do_message_do_not_use_auth_cache_test);
1576         g_test_add_func ("/auth/async-message-do-not-use-auth-cache", do_async_message_do_not_use_auth_cache_test);
1577         g_test_add_func ("/auth/authorization-header-request", do_message_has_authorization_header_test);
1578         g_test_add_func ("/auth/cancel-after-retry", do_cancel_after_retry_test);
1579
1580         ret = g_test_run ();
1581
1582         test_cleanup ();
1583         return ret;
1584 }