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