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