Fix the retry-on-broken-connection codepath for SoupRequest
[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 GMainLoop *loop;
6
7 typedef struct {
8         /* Explanation of what you should see */
9         const char *explanation;
10
11         /* URL to test against */
12         const char *url;
13
14         /* Provided passwords, 1 character each. ('1', '2', and '3'
15          * mean the correct passwords for "realm1", "realm2", and
16          * "realm3" respectively. '4' means "use the wrong password".)
17          * The first password (if present) will be used by
18          * authenticate(), and the second (if present) will be used by
19          * reauthenticate().
20          */
21         const char *provided;
22
23         /* Whether to pass user and password in the URL or not.
24          */
25         gboolean url_auth;
26
27         /* Expected passwords, 1 character each. (As with the provided
28          * passwords, with the addition that '0' means "no
29          * Authorization header expected".) Used to verify that soup
30          * used the password it was supposed to at each step.
31          */
32         const char *expected;
33
34         /* What the final status code should be. */
35         guint final_status;
36 } SoupAuthTest;
37
38 /* Will either point to main_tests or relogin_tests
39  */
40 static SoupAuthTest *current_tests;
41
42 static SoupAuthTest main_tests[] = {
43         { "No auth available, should fail",
44           "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
45
46         { "Should fail with no auth, fail again with bad password, and give up",
47           "Basic/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
48
49         { "Auth provided this time, so should succeed",
50           "Basic/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
51
52         { "Now should automatically reuse previous auth",
53           "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
54
55         { "Subdir should also automatically reuse auth",
56           "Basic/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
57
58         { "Subdir should retry last auth, but will fail this time",
59           "Basic/realm1/realm2/", "", FALSE, "1", SOUP_STATUS_UNAUTHORIZED },
60
61         { "Now should use provided auth",
62           "Basic/realm1/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
63
64         { "Reusing last auth. Should succeed on first try",
65           "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
66
67         { "Reuse will fail, but 2nd try will succeed because it's a known realm",
68           "Basic/realm1/realm2/realm1/", "", FALSE, "21", SOUP_STATUS_OK },
69
70         { "Should succeed on first try. (Known realm with cached password)",
71           "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
72
73         { "Fail once, then use typoed password, then use right password",
74           "Basic/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
75
76
77         { "No auth available, should fail",
78           "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
79
80         { "Should fail with no auth, fail again with bad password, and give up",
81           "Digest/realm2/", "4", FALSE, "04", SOUP_STATUS_UNAUTHORIZED },
82
83         { "Known realm, auth provided, so should succeed",
84           "Digest/realm1/", "1", FALSE, "01", SOUP_STATUS_OK },
85
86         { "Now should automatically reuse previous auth",
87           "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
88
89         { "Subdir should also automatically reuse auth",
90           "Digest/realm1/subdir/", "", FALSE, "1", SOUP_STATUS_OK },
91
92         { "Password provided, should succeed",
93           "Digest/realm2/", "2", FALSE, "02", SOUP_STATUS_OK },
94
95         { "Should already know correct domain and use provided auth on first try",
96           "Digest/realm1/realm2/", "2", FALSE, "2", SOUP_STATUS_OK },
97
98         { "Reusing last auth. Should succeed on first try",
99           "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
100
101         { "Should succeed on first try because of earlier domain directive",
102           "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
103
104         { "Fail once, then use typoed password, then use right password",
105           "Digest/realm3/", "43", FALSE, "043", SOUP_STATUS_OK },
106
107
108         { "Make sure we haven't forgotten anything",
109           "Basic/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
110
111         { "Make sure we haven't forgotten anything",
112           "Basic/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
113
114         { "Make sure we haven't forgotten anything",
115           "Basic/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
116
117         { "Make sure we haven't forgotten anything",
118           "Basic/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
119
120         { "Make sure we haven't forgotten anything",
121           "Basic/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
122
123
124         { "Make sure we haven't forgotten anything",
125           "Digest/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
126
127         { "Make sure we haven't forgotten anything",
128           "Digest/realm1/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
129
130         { "Make sure we haven't forgotten anything",
131           "Digest/realm1/realm2/realm1/", "", FALSE, "1", SOUP_STATUS_OK },
132
133         { "Make sure we haven't forgotten anything",
134           "Digest/realm2/", "", FALSE, "2", SOUP_STATUS_OK },
135
136         { "Make sure we haven't forgotten anything",
137           "Digest/realm3/", "", FALSE, "3", SOUP_STATUS_OK },
138
139         { "Now the server will reject the formerly-good password",
140           "Basic/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
141
142         { "Make sure we've forgotten it",
143           "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
144
145         { "Likewise, reject the formerly-good Digest password",
146           "Digest/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED },
147
148         { "Make sure we've forgotten it",
149           "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED }
150 };
151
152 static const char *auths[] = {
153         "no password", "password 1",
154         "password 2", "password 3",
155         "intentionally wrong password",
156 };
157
158 static int
159 identify_auth (SoupMessage *msg)
160 {
161         const char *header;
162         int num;
163
164         header = soup_message_headers_get_one (msg->request_headers,
165                                                "Authorization");
166         if (!header)
167                 return 0;
168
169         if (!g_ascii_strncasecmp (header, "Basic ", 6)) {
170                 char *token;
171                 gsize len;
172
173                 token = (char *)g_base64_decode (header + 6, &len);
174                 num = token[len - 1] - '0';
175                 g_free (token);
176         } else {
177                 const char *user;
178
179                 user = strstr (header, "username=\"user");
180                 if (user)
181                         num = user[14] - '0';
182                 else
183                         num = 0;
184         }
185
186         g_assert (num >= 0 && num <= 4);
187
188         return num;
189 }
190
191 static void
192 handler (SoupMessage *msg, gpointer data)
193 {
194         char *expected = data;
195         int auth, exp;
196
197         auth = identify_auth (msg);
198
199         debug_printf (1, "  %d %s (using %s)\n",
200                       msg->status_code, msg->reason_phrase,
201                       auths[auth]);
202
203         if (*expected) {
204                 exp = *expected - '0';
205                 if (auth != exp) {
206                         debug_printf (1, "    expected %s!\n", auths[exp]);
207                         errors++;
208                 }
209                 memmove (expected, expected + 1, strlen (expected));
210         } else {
211                 debug_printf (1, "    expected to be finished\n");
212                 errors++;
213         }
214 }
215
216 static void
217 authenticate (SoupSession *session, SoupMessage *msg,
218               SoupAuth *auth, gboolean retrying, gpointer data)
219 {
220         int *i = data;
221         char *username, *password;
222         char num;
223
224         if (!current_tests[*i].provided[0])
225                 return;
226         if (retrying) {
227                 if (!current_tests[*i].provided[1])
228                         return;
229                 num = current_tests[*i].provided[1];
230         } else
231                 num = current_tests[*i].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         if (!*authenticated && auth) {
248                 debug_printf (1, "    using auth on message %d before authenticating!!??\n", n);
249                 errors++;
250         } else if (*authenticated && !auth) {
251                 debug_printf (1, "    sent unauthenticated message %d after authenticating!\n", n);
252                 errors++;
253         }
254 }
255
256 static void
257 bug271540_authenticate (SoupSession *session, SoupMessage *msg,
258                         SoupAuth *auth, gboolean retrying, gpointer data)
259 {
260         int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
261         gboolean *authenticated = data;
262
263         if (strcmp (soup_auth_get_scheme_name (auth), "Basic") != 0 ||
264             strcmp (soup_auth_get_realm (auth), "realm1") != 0)
265                 return;
266
267         if (!*authenticated) {
268                 debug_printf (1, "    authenticating message %d\n", n);
269                 soup_auth_authenticate (auth, "user1", "realm1");
270                 *authenticated = TRUE;
271         } else {
272                 debug_printf (1, "    asked to authenticate message %d after authenticating!\n", n);
273                 errors++;
274         }
275 }
276
277 static void
278 bug271540_finished (SoupSession *session, SoupMessage *msg, gpointer data)
279 {
280         int *left = data;
281         int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#"));
282
283         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
284                 debug_printf (1, "      got status '%d %s' on message %d!\n",
285                               msg->status_code, msg->reason_phrase, n);
286                 errors++;
287         }
288
289         (*left)--;
290         if (!*left)
291                 g_main_loop_quit (loop);
292 }
293
294 static void
295 do_pipelined_auth_test (const char *base_uri)
296 {
297         SoupSession *session;
298         SoupMessage *msg;
299         gboolean authenticated;
300         char *uri;
301         int i;
302
303         debug_printf (1, "Testing pipelined auth (bug 271540):\n");
304         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
305
306         authenticated = FALSE;
307         g_signal_connect (session, "authenticate",
308                           G_CALLBACK (bug271540_authenticate), &authenticated);
309
310         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
311         for (i = 0; i < 10; i++) {
312                 msg = soup_message_new (SOUP_METHOD_GET, uri);
313                 g_object_set_data (G_OBJECT (msg), "#", GINT_TO_POINTER (i + 1));
314                 g_signal_connect (msg, "wrote_headers",
315                                   G_CALLBACK (bug271540_sent), &authenticated);
316
317                 soup_session_queue_message (session, msg,
318                                             bug271540_finished, &i);
319         }
320         g_free (uri);
321
322         loop = g_main_loop_new (NULL, TRUE);
323         g_main_loop_run (loop);
324         g_main_loop_unref (loop);
325
326         soup_test_session_abort_unref (session);
327 }
328
329 /* We test two different things here:
330  *
331  *   1. If we get a 401 response with "WWW-Authenticate: Digest
332  *      stale=true...", we should retry and succeed *without* the
333  *      session asking for a password again.
334  *
335  *   2. If we get a successful response with "Authentication-Info:
336  *      nextnonce=...", we should update the nonce automatically so as
337  *      to avoid getting a stale nonce error on the next request.
338  *
339  * In our Apache config, /Digest/realm1 and /Digest/realm1/expire are
340  * set up to use the same auth info, but only the latter has an
341  * AuthDigestNonceLifetime (of 2 seconds). The way nonces work in
342  * Apache, a nonce received from /Digest/realm1 will still expire in
343  * /Digest/realm1/expire, but it won't issue a nextnonce for a request
344  * in /Digest/realm1. This lets us test both behaviors.
345  *
346  * The expected conversation is:
347  *
348  * First message
349  *   GET /Digest/realm1
350  *
351  *   401 Unauthorized
352  *   WWW-Authenticate: Digest nonce=A
353  *
354  *   [emit 'authenticate']
355  *
356  *   GET /Digest/realm1
357  *   Authorization: Digest nonce=A
358  *
359  *   200 OK
360  *   [No Authentication-Info]
361  *
362  * [sleep 2 seconds: nonce A is no longer valid, but we have no
363  * way of knowing that]
364  *
365  * Second message
366  *   GET /Digest/realm1/expire/
367  *   Authorization: Digest nonce=A
368  *
369  *   401 Unauthorized
370  *   WWW-Authenticate: Digest stale=true nonce=B
371  *
372  *   GET /Digest/realm1/expire/
373  *   Authorization: Digest nonce=B
374  *
375  *   200 OK
376  *   Authentication-Info: nextnonce=C
377  *
378  * [sleep 1 second]
379  *
380  * Third message
381  *   GET /Digest/realm1/expire/
382  *   Authorization: Digest nonce=C
383  *   [nonce=B would work here too]
384  *
385  *   200 OK
386  *   Authentication-Info: nextnonce=D
387  *
388  * [sleep 1 second; nonces B and C are no longer valid, but D is]
389  *
390  * Fourth message
391  *   GET /Digest/realm1/expire/
392  *   Authorization: Digest nonce=D
393  *
394  *   200 OK
395  *   Authentication-Info: nextnonce=D
396  *
397  */
398
399 static void
400 digest_nonce_authenticate (SoupSession *session, SoupMessage *msg,
401                            SoupAuth *auth, gboolean retrying, gpointer data)
402 {
403         if (retrying)
404                 return;
405
406         if (strcmp (soup_auth_get_scheme_name (auth), "Digest") != 0 ||
407             strcmp (soup_auth_get_realm (auth), "realm1") != 0)
408                 return;
409
410         soup_auth_authenticate (auth, "user1", "realm1");
411 }
412
413 static void
414 digest_nonce_unauthorized (SoupMessage *msg, gpointer data)
415 {
416         gboolean *got_401 = data;
417         *got_401 = TRUE;
418 }
419
420 static void
421 do_digest_nonce_test (SoupSession *session,
422                       const char *nth, const char *uri,
423                       gboolean expect_401, gboolean expect_signal)
424 {
425         SoupMessage *msg;
426         gboolean got_401;
427
428         msg = soup_message_new (SOUP_METHOD_GET, uri);
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         if (got_401 != expect_401) {
441                 debug_printf (1, "  %s request %s a 401 Unauthorized!\n", nth,
442                               got_401 ? "got" : "did not get");
443                 errors++;
444         }
445         if (msg->status_code != SOUP_STATUS_OK) {
446                 debug_printf (1, "  %s request got status %d %s!\n", nth,
447                               msg->status_code, msg->reason_phrase);
448                 errors++;
449         }
450         if (errors == 0)
451                 debug_printf (1, "  %s request succeeded\n", nth);
452         g_object_unref (msg);
453 }
454
455 static void
456 do_digest_expiration_test (const char *base_uri)
457 {
458         SoupSession *session;
459         char *uri;
460
461         debug_printf (1, "\nTesting digest nonce expiration:\n");
462
463         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
464
465         uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
466         do_digest_nonce_test (session, "First", uri, TRUE, TRUE);
467         g_free (uri);
468         sleep (2);
469         uri = g_strconcat (base_uri, "Digest/realm1/expire/", NULL);
470         do_digest_nonce_test (session, "Second", uri, TRUE, FALSE);
471         sleep (1);
472         do_digest_nonce_test (session, "Third", uri, FALSE, FALSE);
473         sleep (1);
474         do_digest_nonce_test (session, "Fourth", uri, FALSE, FALSE);
475         g_free (uri);
476
477         soup_test_session_abort_unref (session);
478 }
479
480 /* Async auth test. We queue three requests to /Basic/realm1, ensuring
481  * that they are sent in order. The first and third ones will be
482  * paused from the authentication callback. The second will be allowed
483  * to fail. Shortly after the third one requests auth, we'll provide
484  * the auth and unpause the two remaining messages, allowing them to
485  * succeed.
486  */
487
488 static void
489 async_authenticate (SoupSession *session, SoupMessage *msg,
490                     SoupAuth *auth, gboolean retrying, gpointer data)
491 {
492         SoupAuth **saved_auth = data;
493         int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
494
495         debug_printf (2, "  async_authenticate msg%d\n", id);
496
497         /* The session will try to authenticate msg3 *before* sending
498          * it, because it already knows it's going to need the auth.
499          * Ignore that.
500          */
501         if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
502                 debug_printf (2, "    (ignoring)\n");
503                 return;
504         }
505
506         soup_session_pause_message (session, msg);
507         if (saved_auth)
508                 *saved_auth = g_object_ref (auth);
509         g_main_loop_quit (loop);
510 }
511
512 static void
513 async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
514 {
515         int *remaining = user_data;
516         int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
517
518         debug_printf (2, "  async_finished msg%d\n", id);
519
520         (*remaining)--;
521         if (!*remaining)
522                 g_main_loop_quit (loop);
523 }
524
525 static void
526 async_authenticate_assert_once (SoupSession *session, SoupMessage *msg,
527                                 SoupAuth *auth, gboolean retrying, gpointer data)
528 {
529         gboolean *been_here = data;
530
531         debug_printf (2, "  async_authenticate_assert_once\n");
532
533         if (*been_here) {
534                 debug_printf (1, "  ERROR: async_authenticate_assert_once called twice\n");
535                 errors++;
536         }
537         *been_here = TRUE;
538 }
539
540 static void
541 async_authenticate_assert_once_and_stop (SoupSession *session, SoupMessage *msg,
542                                          SoupAuth *auth, gboolean retrying, gpointer data)
543 {
544         gboolean *been_here = data;
545
546         debug_printf (2, "  async_authenticate_assert_once_and_stop\n");
547
548         if (*been_here) {
549                 debug_printf (1, "  ERROR: async_authenticate_assert_once called twice\n");
550                 errors++;
551         }
552         *been_here = TRUE;
553
554         soup_session_pause_message (session, msg);
555         g_main_loop_quit (loop);
556 }
557
558 static void
559 do_async_auth_test (const char *base_uri)
560 {
561         SoupSession *session;
562         SoupMessage *msg1, *msg2, *msg3, msg2_bak;
563         guint auth_id;
564         char *uri;
565         SoupAuth *auth = NULL;
566         int remaining;
567         gboolean been_there;
568
569         debug_printf (1, "\nTesting async auth:\n");
570
571         loop = g_main_loop_new (NULL, TRUE);
572         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
573         remaining = 0;
574
575         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
576
577         msg1 = soup_message_new ("GET", uri);
578         g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
579         auth_id = g_signal_connect (session, "authenticate",
580                                     G_CALLBACK (async_authenticate), &auth);
581         g_object_ref (msg1);
582         remaining++;
583         soup_session_queue_message (session, msg1, async_finished, &remaining);
584         g_main_loop_run (loop);
585         g_signal_handler_disconnect (session, auth_id);
586
587         /* async_authenticate will pause msg1 and quit loop */
588
589         msg2 = soup_message_new ("GET", uri);
590         g_object_set_data (G_OBJECT (msg2), "id", GINT_TO_POINTER (2));
591         soup_session_send_message (session, msg2);
592
593         if (msg2->status_code == SOUP_STATUS_UNAUTHORIZED)
594                 debug_printf (1, "  msg2 failed as expected\n");
595         else {
596                 debug_printf (1, "  msg2 got wrong status! (%u)\n",
597                               msg2->status_code);
598                 errors++;
599         }
600
601         /* msg2 should be done at this point; assuming everything is
602          * working correctly, the session won't look at it again; we
603          * ensure that if it does, it will crash the test program.
604          */
605         memcpy (&msg2_bak, msg2, sizeof (SoupMessage));
606         memset (msg2, 0, sizeof (SoupMessage));
607
608         msg3 = soup_message_new ("GET", uri);
609         g_object_set_data (G_OBJECT (msg3), "id", GINT_TO_POINTER (3));
610         auth_id = g_signal_connect (session, "authenticate",
611                                     G_CALLBACK (async_authenticate), NULL);
612         g_object_ref (msg3);
613         remaining++;
614         soup_session_queue_message (session, msg3, async_finished, &remaining);
615         g_main_loop_run (loop);
616         g_signal_handler_disconnect (session, auth_id);
617
618         /* async_authenticate will pause msg3 and quit loop */
619
620         /* Now do the auth, and restart */
621         if (auth) {
622                 soup_auth_authenticate (auth, "user1", "realm1");
623                 g_object_unref (auth);
624                 soup_session_unpause_message (session, msg1);
625                 soup_session_unpause_message (session, msg3);
626
627                 g_main_loop_run (loop);
628
629                 /* async_finished will quit the loop */
630         } else {
631                 debug_printf (1, "  msg1 didn't get authenticate signal!\n");
632                 errors++;
633         }
634
635         if (msg1->status_code == SOUP_STATUS_OK)
636                 debug_printf (1, "  msg1 succeeded\n");
637         else {
638                 debug_printf (1, "  msg1 FAILED! (%u %s)\n",
639                               msg1->status_code, msg1->reason_phrase);
640                 errors++;
641         }
642         if (msg3->status_code == SOUP_STATUS_OK)
643                 debug_printf (1, "  msg3 succeeded\n");
644         else {
645                 debug_printf (1, "  msg3 FAILED! (%u %s)\n",
646                               msg3->status_code, msg3->reason_phrase);
647                 errors++;
648         }
649
650         soup_test_session_abort_unref (session);
651
652         g_object_unref (msg1);
653         g_object_unref (msg3);
654         memcpy (msg2, &msg2_bak, sizeof (SoupMessage));
655         g_object_unref (msg2);
656
657         /* Test that giving the wrong password doesn't cause multiple
658          * authenticate signals the second time.
659          */
660         debug_printf (1, "\nTesting async auth with wrong password (#522601):\n");
661
662         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
663         remaining = 0;
664         auth = NULL;
665
666         msg1 = soup_message_new ("GET", uri);
667         g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
668         auth_id = g_signal_connect (session, "authenticate",
669                                     G_CALLBACK (async_authenticate), &auth);
670         g_object_ref (msg1);
671         remaining++;
672         soup_session_queue_message (session, msg1, async_finished, &remaining);
673         g_main_loop_run (loop);
674         g_signal_handler_disconnect (session, auth_id);
675         soup_auth_authenticate (auth, "user1", "wrong");
676         g_object_unref (auth);
677         soup_session_unpause_message (session, msg1);
678
679         been_there = FALSE;
680         auth_id = g_signal_connect (session, "authenticate",
681                                     G_CALLBACK (async_authenticate_assert_once),
682                                     &been_there);
683         g_main_loop_run (loop);
684         g_signal_handler_disconnect (session, auth_id);
685
686         if (!been_there) {
687                 debug_printf (1, "  authenticate not emitted?\n");
688                 errors++;
689         }
690
691         soup_test_session_abort_unref (session);
692         g_object_unref (msg1);
693
694         /* Test that giving no password doesn't cause multiple
695          * authenticate signals the second time.
696          */
697         debug_printf (1, "\nTesting async auth with no password (#583462):\n");
698
699         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
700         remaining = 0;
701
702         /* Send a message that doesn't actually authenticate
703          */
704         msg1 = soup_message_new ("GET", uri);
705         g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
706         auth_id = g_signal_connect (session, "authenticate",
707                                     G_CALLBACK (async_authenticate), NULL);
708         g_object_ref (msg1);
709         remaining++;
710         soup_session_queue_message (session, msg1, async_finished, &remaining);
711         g_main_loop_run (loop);
712         g_signal_handler_disconnect (session, auth_id);
713         soup_session_unpause_message (session, msg1);
714         g_main_loop_run (loop);
715         g_object_unref(msg1);
716
717         /* Now send a second message */
718         msg1 = soup_message_new ("GET", uri);
719         g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (2));
720         g_object_ref (msg1);
721         been_there = FALSE;
722         auth_id = g_signal_connect (session, "authenticate",
723                                     G_CALLBACK (async_authenticate_assert_once_and_stop),
724                                     &been_there);
725         remaining++;
726         soup_session_queue_message (session, msg1, async_finished, &remaining);
727         g_main_loop_run (loop);
728         soup_session_unpause_message (session, msg1);
729
730         g_main_loop_run (loop);
731         g_signal_handler_disconnect (session, auth_id);
732
733         soup_test_session_abort_unref (session);
734         g_object_unref (msg1);
735
736         g_free (uri);
737         g_main_loop_unref (loop);
738 }
739
740 typedef struct {
741         const char *password;
742         struct {
743                 const char *headers;
744                 const char *response;
745         } round[2];
746 } SelectAuthData;
747
748 static void
749 select_auth_authenticate (SoupSession *session, SoupMessage *msg,
750                           SoupAuth *auth, gboolean retrying, gpointer data)
751 {
752         SelectAuthData *sad = data;
753         const char *header, *basic, *digest;
754         int round = retrying ? 1 : 0;
755
756         header = soup_message_headers_get_list (msg->response_headers,
757                                                 "WWW-Authenticate");
758         basic = strstr (header, "Basic");
759         digest = strstr (header, "Digest");
760         if (basic && digest) {
761                 if (basic < digest)
762                         sad->round[round].headers = "Basic, Digest";
763                 else
764                         sad->round[round].headers = "Digest, Basic";
765         } else if (basic)
766                 sad->round[round].headers = "Basic";
767         else if (digest)
768                 sad->round[round].headers = "Digest";
769
770         sad->round[round].response = soup_auth_get_scheme_name (auth);
771         if (sad->password && !retrying)
772                 soup_auth_authenticate (auth, "user", sad->password);
773 }
774
775 static void
776 select_auth_test_one (SoupURI *uri,
777                       gboolean disable_digest, const char *password,
778                       const char *first_headers, const char *first_response,
779                       const char *second_headers, const char *second_response,
780                       guint final_status)
781 {
782         SelectAuthData sad;
783         SoupMessage *msg;
784         SoupSession *session;
785
786         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
787         if (disable_digest)
788                 soup_session_remove_feature_by_type (session, SOUP_TYPE_AUTH_DIGEST);
789
790         g_signal_connect (session, "authenticate",
791                           G_CALLBACK (select_auth_authenticate), &sad);
792         memset (&sad, 0, sizeof (sad));
793         sad.password = password;
794
795         msg = soup_message_new_from_uri ("GET", uri);
796         soup_session_send_message (session, msg);
797
798         if (strcmp (sad.round[0].headers, first_headers) != 0) {
799                 debug_printf (1, "    Header order wrong: expected %s, got %s\n",
800                               first_headers, sad.round[0].headers);
801                 errors++;
802         }
803         if (strcmp (sad.round[0].response, first_response) != 0) {
804                 debug_printf (1, "    Selected auth type wrong: expected %s, got %s\n",
805                               first_response, sad.round[0].response);
806                 errors++;
807         }
808
809         if (second_headers && !sad.round[1].headers) {
810                 debug_printf (1, "    Expected a second round!\n");
811                 errors++;
812         } else if (!second_headers && sad.round[1].headers) {
813                 debug_printf (1, "    Didn't expect a second round!\n");
814                 errors++;
815         } else if (second_headers) {
816                 if (strcmp (sad.round[1].headers, second_headers) != 0) {
817                         debug_printf (1, "    Second round header order wrong: expected %s, got %s\n",
818                                       second_headers, sad.round[1].headers);
819                         errors++;
820                 }
821                 if (strcmp (sad.round[1].response, second_response) != 0) {
822                         debug_printf (1, "    Second round selected auth type wrong: expected %s, got %s\n",
823                                       second_response, sad.round[1].response);
824                         errors++;
825                 }
826         }
827
828         if (msg->status_code != final_status) {
829                 debug_printf (1, "    Final status wrong: expected %u, got %u\n",
830                               final_status, msg->status_code);
831                 errors++;
832         }
833
834         g_object_unref (msg);
835         soup_test_session_abort_unref (session);
836 }
837
838 static void
839 server_callback (SoupServer *server, SoupMessage *msg,
840                  const char *path, GHashTable *query,
841                  SoupClientContext *context, gpointer data)
842 {
843         soup_message_set_response (msg, "text/plain",
844                                    SOUP_MEMORY_STATIC,
845                                    "OK\r\n", 4);
846         soup_message_set_status (msg, SOUP_STATUS_OK);
847 }
848
849 static gboolean
850 server_basic_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
851                             const char *username, const char *password, gpointer data)
852 {
853         if (strcmp (username, "user") != 0)
854                 return FALSE;
855         return strcmp (password, "good-basic") == 0;
856 }
857
858 static char *
859 server_digest_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
860                              const char *username, gpointer data)
861 {
862         if (strcmp (username, "user") != 0)
863                 return NULL;
864         return soup_auth_domain_digest_encode_password ("user",
865                                                         "auth-test",
866                                                         "good");
867 }
868
869 static void
870 do_select_auth_test (void)
871 {
872         SoupServer *server;
873         SoupAuthDomain *basic_auth_domain, *digest_auth_domain;
874         SoupURI *uri;
875
876         debug_printf (1, "\nTesting selection among multiple auths:\n");
877
878         /* It doesn't seem to be possible to configure Apache to serve
879          * multiple auth types for a single URL. So we have to use
880          * SoupServer here. We know that SoupServer handles the server
881          * side of this scenario correctly, because we test it against
882          * curl in server-auth-test.
883          */
884         server = soup_test_server_new (FALSE);
885         soup_server_add_handler (server, NULL,
886                                  server_callback, NULL, NULL);
887
888         uri = soup_uri_new ("http://127.0.0.1/");
889         soup_uri_set_port (uri, soup_server_get_port (server));
890
891         basic_auth_domain = soup_auth_domain_basic_new (
892                 SOUP_AUTH_DOMAIN_REALM, "auth-test",
893                 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
894                 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
895                 NULL);
896         soup_server_add_auth_domain (server, basic_auth_domain);
897
898         digest_auth_domain = soup_auth_domain_digest_new (
899                 SOUP_AUTH_DOMAIN_REALM, "auth-test",
900                 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
901                 SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, server_digest_auth_callback,
902                 NULL);
903         soup_server_add_auth_domain (server, digest_auth_domain);
904
905         debug_printf (1, "  Testing with no auth\n");
906         select_auth_test_one (uri, FALSE, NULL,
907                               "Basic, Digest", "Digest",
908                               NULL, NULL,
909                               SOUP_STATUS_UNAUTHORIZED);
910
911         debug_printf (1, "  Testing with bad password\n");
912         select_auth_test_one (uri, FALSE, "bad",
913                               "Basic, Digest", "Digest",
914                               "Basic, Digest", "Digest",
915                               SOUP_STATUS_UNAUTHORIZED);
916
917         debug_printf (1, "  Testing with good password\n");
918         select_auth_test_one (uri, FALSE, "good",
919                               "Basic, Digest", "Digest",
920                               NULL, NULL,
921                               SOUP_STATUS_OK);
922
923         /* Test with Digest disabled in the client. */
924         debug_printf (1, "  Testing without Digest with no auth\n");
925         select_auth_test_one (uri, TRUE, NULL,
926                               "Basic, Digest", "Basic",
927                               NULL, NULL,
928                               SOUP_STATUS_UNAUTHORIZED);
929
930         debug_printf (1, "  Testing without Digest with bad password\n");
931         select_auth_test_one (uri, TRUE, "bad",
932                               "Basic, Digest", "Basic",
933                               "Basic, Digest", "Basic",
934                               SOUP_STATUS_UNAUTHORIZED);
935
936         debug_printf (1, "  Testing without Digest with good password\n");
937         select_auth_test_one (uri, TRUE, "good-basic",
938                               "Basic, Digest", "Basic",
939                               NULL, NULL,
940                               SOUP_STATUS_OK);
941
942         /* Now flip the order of the domains, verify that this flips
943          * the order of the headers, and make sure that digest auth
944          * *still* gets used.
945          */
946
947         soup_server_remove_auth_domain (server, basic_auth_domain);
948         soup_server_remove_auth_domain (server, digest_auth_domain);
949         soup_server_add_auth_domain (server, digest_auth_domain);
950         soup_server_add_auth_domain (server, basic_auth_domain);
951
952         debug_printf (1, "  Testing flipped with no auth\n");
953         select_auth_test_one (uri, FALSE, NULL,
954                               "Digest, Basic", "Digest",
955                               NULL, NULL,
956                               SOUP_STATUS_UNAUTHORIZED);
957
958         debug_printf (1, "  Testing flipped with bad password\n");
959         select_auth_test_one (uri, FALSE, "bad",
960                               "Digest, Basic", "Digest",
961                               "Digest, Basic", "Digest",
962                               SOUP_STATUS_UNAUTHORIZED);
963
964         debug_printf (1, "  Testing flipped with good password\n");
965         select_auth_test_one (uri, FALSE, "good",
966                               "Digest, Basic", "Digest",
967                               NULL, NULL,
968                               SOUP_STATUS_OK);
969
970         g_object_unref (basic_auth_domain);
971         g_object_unref (digest_auth_domain);
972         soup_uri_free (uri);
973         soup_test_server_quit_unref (server);
974 }
975
976 static void
977 sneakily_close_connection (SoupMessage *msg, gpointer user_data)
978 {
979         /* Sneakily close the connection after the response, by
980          * tricking soup-message-io into thinking that had been
981          * the plan all along.
982          */
983         soup_message_headers_append (msg->response_headers,
984                                      "Connection", "close");
985 }
986
987 static void
988 auth_close_request_started (SoupServer *server, SoupMessage *msg,
989                             SoupClientContext *client, gpointer user_data)
990 {
991         g_signal_connect (msg, "wrote-headers",
992                           G_CALLBACK (sneakily_close_connection), NULL);
993 }
994
995 typedef struct {
996         SoupSession *session;
997         SoupMessage *msg;
998         SoupAuth *auth;
999 } AuthCloseData;
1000
1001 static gboolean
1002 auth_close_idle_authenticate (gpointer user_data)
1003 {
1004         AuthCloseData *acd = user_data;
1005
1006         soup_auth_authenticate (acd->auth, "user", "good-basic");
1007         soup_session_unpause_message (acd->session, acd->msg);
1008
1009         g_object_unref (acd->auth);
1010         return FALSE;
1011 }
1012
1013 static void
1014 auth_close_authenticate (SoupSession *session, SoupMessage *msg,
1015                          SoupAuth *auth, gboolean retrying, gpointer data)
1016 {
1017         AuthCloseData *acd = data;
1018
1019         soup_session_pause_message (session, msg);
1020         acd->auth = g_object_ref (auth);
1021         g_idle_add (auth_close_idle_authenticate, acd);
1022 }
1023
1024 static void
1025 do_auth_close_test (void)
1026 {
1027         SoupServer *server;
1028         SoupAuthDomain *basic_auth_domain;
1029         SoupURI *uri;
1030         AuthCloseData acd;
1031
1032         debug_printf (1, "\nTesting auth when server times out connection:\n");
1033
1034         server = soup_test_server_new (FALSE);
1035         soup_server_add_handler (server, NULL,
1036                                  server_callback, NULL, NULL);
1037
1038         uri = soup_uri_new ("http://127.0.0.1/close");
1039         soup_uri_set_port (uri, soup_server_get_port (server));
1040
1041         basic_auth_domain = soup_auth_domain_basic_new (
1042                 SOUP_AUTH_DOMAIN_REALM, "auth-test",
1043                 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
1044                 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
1045                 NULL);
1046         soup_server_add_auth_domain (server, basic_auth_domain);
1047         g_object_unref (basic_auth_domain);
1048
1049         g_signal_connect (server, "request-started",
1050                           G_CALLBACK (auth_close_request_started), NULL);
1051
1052         acd.session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1053         g_signal_connect (acd.session, "authenticate",
1054                           G_CALLBACK (auth_close_authenticate), &acd);
1055
1056         acd.msg = soup_message_new_from_uri ("GET", uri);
1057         soup_uri_free (uri);
1058         soup_session_send_message (acd.session, acd.msg);
1059
1060         if (acd.msg->status_code != SOUP_STATUS_OK) {
1061                 debug_printf (1, "    Final status wrong: expected %u, got %u %s\n",
1062                               SOUP_STATUS_OK, acd.msg->status_code,
1063                               acd.msg->reason_phrase);
1064                 errors++;
1065         }
1066
1067         g_object_unref (acd.msg);
1068         soup_test_session_abort_unref (acd.session);
1069         soup_test_server_quit_unref (server);
1070 }
1071
1072 static SoupAuthTest relogin_tests[] = {
1073         { "Auth provided via URL, should succeed",
1074           "Basic/realm12/", "1", TRUE, "01", SOUP_STATUS_OK },
1075
1076         { "Now should automatically reuse previous auth",
1077           "Basic/realm12/", "", FALSE, "1", SOUP_STATUS_OK },
1078
1079         { "Different auth provided via URL for the same realm, should succeed",
1080           "Basic/realm12/", "2", TRUE, "2", SOUP_STATUS_OK },
1081
1082         { "Subdir should also automatically reuse auth",
1083           "Basic/realm12/subdir/", "", FALSE, "2", SOUP_STATUS_OK },
1084
1085         { "Should fail with no auth",
1086           "Basic/realm12/", "4", TRUE, "4", SOUP_STATUS_UNAUTHORIZED },
1087
1088         { "Make sure we've forgotten it",
1089           "Basic/realm12/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
1090
1091         { "Should fail with no auth, fail again with bad password, and give up",
1092           "Basic/realm12/", "3", FALSE, "03", SOUP_STATUS_UNAUTHORIZED },
1093 };
1094
1095 static void
1096 do_batch_tests (const gchar *base_uri_str, gint ntests)
1097 {
1098         SoupSession *session;
1099         SoupMessage *msg;
1100         char *expected, *uristr;
1101         SoupURI *base_uri;
1102         int i;
1103
1104         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1105         g_signal_connect (session, "authenticate",
1106                           G_CALLBACK (authenticate), &i);
1107
1108         base_uri = soup_uri_new (base_uri_str);
1109
1110         for (i = 0; i < ntests; i++) {
1111                 SoupURI *soup_uri = soup_uri_new_with_base (base_uri, current_tests[i].url);
1112
1113                 debug_printf (1, "Test %d: %s\n", i + 1, current_tests[i].explanation);
1114
1115                 if (current_tests[i].url_auth) {
1116                         gchar *username = g_strdup_printf ("user%c", current_tests[i].provided[0]);
1117                         gchar *password = g_strdup_printf ("realm%c", current_tests[i].provided[0]);
1118                         soup_uri_set_user (soup_uri, username);
1119                         soup_uri_set_password (soup_uri, password);
1120                         g_free (username);
1121                         g_free (password);
1122                 }
1123
1124                 msg = soup_message_new_from_uri (SOUP_METHOD_GET, soup_uri);
1125                 soup_uri_free (soup_uri);
1126                 if (!msg) {
1127                         g_printerr ("auth-test: Could not parse URI\n");
1128                         exit (1);
1129                 }
1130
1131                 uristr = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1132                 debug_printf (1, "  GET %s\n", uristr);
1133                 g_free (uristr);
1134
1135                 expected = g_strdup (current_tests[i].expected);
1136                 soup_message_add_status_code_handler (
1137                         msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
1138                         G_CALLBACK (handler), expected);
1139                 soup_message_add_status_code_handler (
1140                         msg, "got_headers", SOUP_STATUS_OK,
1141                         G_CALLBACK (handler), expected);
1142                 soup_session_send_message (session, msg);
1143                 if (msg->status_code != SOUP_STATUS_UNAUTHORIZED &&
1144                     msg->status_code != SOUP_STATUS_OK) {
1145                         debug_printf (1, "  %d %s !\n", msg->status_code,
1146                                       msg->reason_phrase);
1147                         errors++;
1148                 }
1149                 if (*expected) {
1150                         debug_printf (1, "  expected %d more round(s)\n",
1151                                       (int)strlen (expected));
1152                         errors++;
1153                 }
1154                 g_free (expected);
1155
1156                 if (msg->status_code != current_tests[i].final_status) {
1157                         debug_printf (1, "  expected %d\n",
1158                                       current_tests[i].final_status);
1159                 }
1160
1161                 debug_printf (1, "\n");
1162
1163                 g_object_unref (msg);
1164         }
1165         soup_uri_free (base_uri);
1166
1167         soup_test_session_abort_unref (session);
1168 }
1169
1170 int
1171 main (int argc, char **argv)
1172 {
1173         const char *base_uri;
1174         int ntests;
1175
1176         test_init (argc, argv, NULL);
1177         apache_init ();
1178
1179         base_uri = "http://127.0.0.1:47524/";
1180
1181         /* Main tests */
1182         current_tests = main_tests;
1183         ntests = G_N_ELEMENTS (main_tests);
1184         do_batch_tests (base_uri, ntests);
1185
1186         /* Re-login tests */
1187         current_tests = relogin_tests;
1188         ntests = G_N_ELEMENTS (relogin_tests);
1189         do_batch_tests (base_uri, ntests);
1190
1191         /* Other regression tests */
1192         do_pipelined_auth_test (base_uri);
1193         do_digest_expiration_test (base_uri);
1194         do_async_auth_test (base_uri);
1195         do_select_auth_test ();
1196         do_auth_close_test ();
1197
1198         test_cleanup ();
1199         return errors != 0;
1200 }