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