Define two new signals, request_queued and request_unqueued, to provided a
[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 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         /* Expected passwords, 1 character each. (As with the provided
36          * passwords, with the addition that '0' means "no
37          * Authorization header expected".) Used to verify that soup
38          * used the password it was supposed to at each step.
39          */
40         const char *expected;
41
42         /* What the final status code should be. */
43         guint final_status;
44 } SoupAuthTest;
45
46 SoupAuthTest tests[] = {
47         { "No auth available, should fail",
48           "Basic/realm1/", "", "0", SOUP_STATUS_UNAUTHORIZED },
49
50         { "Should fail with no auth, fail again with bad password, and give up",
51           "Basic/realm2/", "4", "04", SOUP_STATUS_UNAUTHORIZED },
52
53         { "Known realm, auth provided, so should succeed immediately",
54           "Basic/realm1/", "1", "1", SOUP_STATUS_OK },
55
56         { "Now should automatically reuse previous auth",
57           "Basic/realm1/", "", "1", SOUP_STATUS_OK },
58
59         { "Subdir should also automatically reuse auth",
60           "Basic/realm1/subdir/", "", "1", SOUP_STATUS_OK },
61
62         { "Subdir should retry last auth, but will fail this time",
63           "Basic/realm1/realm2/", "", "1", SOUP_STATUS_UNAUTHORIZED },
64
65         { "Now should use provided auth on first try",
66           "Basic/realm1/realm2/", "2", "2", SOUP_STATUS_OK },
67
68         { "Reusing last auth. Should succeed on first try",
69           "Basic/realm1/realm2/", "", "2", SOUP_STATUS_OK },
70
71         { "Reuse will fail, but 2nd try will succeed because it's a known realm",
72           "Basic/realm1/realm2/realm1/", "", "21", SOUP_STATUS_OK },
73
74         { "Should succeed on first try. (Known realm with cached password)",
75           "Basic/realm2/", "", "2", SOUP_STATUS_OK },
76
77         { "Fail once, then use typoed password, then use right password",
78           "Basic/realm3/", "43", "043", SOUP_STATUS_OK },
79
80
81         { "No auth available, should fail",
82           "Digest/realm1/", "", "0", SOUP_STATUS_UNAUTHORIZED },
83
84         { "Should fail with no auth, fail again with bad password, and give up",
85           "Digest/realm2/", "4", "04", SOUP_STATUS_UNAUTHORIZED },
86
87         { "Known realm, auth provided, so should succeed immediately",
88           "Digest/realm1/", "1", "1", SOUP_STATUS_OK },
89
90         { "Now should automatically reuse previous auth",
91           "Digest/realm1/", "", "1", SOUP_STATUS_OK },
92
93         { "Subdir should also automatically reuse auth",
94           "Digest/realm1/subdir/", "", "1", SOUP_STATUS_OK },
95
96         { "Should already know correct domain and use provided auth on first try",
97           "Digest/realm1/realm2/", "2", "2", SOUP_STATUS_OK },
98
99         { "Reusing last auth. Should succeed on first try",
100           "Digest/realm1/realm2/", "", "2", SOUP_STATUS_OK },
101
102         { "Should succeed on first try because of earlier domain directive",
103           "Digest/realm1/realm2/realm1/", "", "1", SOUP_STATUS_OK },
104
105         { "Should succeed on first try. (Known realm with cached password)",
106           "Digest/realm2/", "", "2", SOUP_STATUS_OK },
107
108         { "Fail once, then use typoed password, then use right password",
109           "Digest/realm3/", "43", "043", SOUP_STATUS_OK },
110
111
112         { "Make sure we haven't forgotten anything",
113           "Basic/realm1/", "", "1", SOUP_STATUS_OK },
114
115         { "Make sure we haven't forgotten anything",
116           "Basic/realm1/realm2/", "", "2", SOUP_STATUS_OK },
117
118         { "Make sure we haven't forgotten anything",
119           "Basic/realm1/realm2/realm1/", "", "1", SOUP_STATUS_OK },
120
121         { "Make sure we haven't forgotten anything",
122           "Basic/realm2/", "", "2", SOUP_STATUS_OK },
123
124         { "Make sure we haven't forgotten anything",
125           "Basic/realm3/", "", "3", SOUP_STATUS_OK },
126
127
128         { "Make sure we haven't forgotten anything",
129           "Digest/realm1/", "", "1", SOUP_STATUS_OK },
130
131         { "Make sure we haven't forgotten anything",
132           "Digest/realm1/realm2/", "", "2", SOUP_STATUS_OK },
133
134         { "Make sure we haven't forgotten anything",
135           "Digest/realm1/realm2/realm1/", "", "1", SOUP_STATUS_OK },
136
137         { "Make sure we haven't forgotten anything",
138           "Digest/realm2/", "", "2", SOUP_STATUS_OK },
139
140         { "Make sure we haven't forgotten anything",
141           "Digest/realm3/", "", "3", SOUP_STATUS_OK },
142
143         { "Now the server will reject the formerly-good password",
144           "Basic/realm1/not/", "1" /* should not be used */, "1", SOUP_STATUS_UNAUTHORIZED },
145
146         { "Make sure we've forgotten it",
147           "Basic/realm1/", "", "0", SOUP_STATUS_UNAUTHORIZED },
148
149         { "Likewise, reject the formerly-good Digest password",
150           "Digest/realm1/not/", "1" /* should not be used */, "1", SOUP_STATUS_UNAUTHORIZED },
151
152         { "Make sure we've forgotten it",
153           "Digest/realm1/", "", "0", SOUP_STATUS_UNAUTHORIZED }
154 };
155 int ntests = sizeof (tests) / sizeof (tests[0]);
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 (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 (!tests[*i].provided[0])
230                 return;
231         if (retrying) {
232                 if (!tests[*i].provided[1])
233                         return;
234                 num = tests[*i].provided[1];
235         } else
236                 num = 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 digest_nonce_authenticate (SoupSession *session, SoupMessage *msg,
301                            SoupAuth *auth, gboolean retrying, gpointer data)
302 {
303         if (retrying)
304                 return;
305
306         if (strcmp (soup_auth_get_scheme_name (auth), "Digest") != 0 ||
307             strcmp (soup_auth_get_realm (auth), "realm1") != 0)
308                 return;
309
310         soup_auth_authenticate (auth, "user1", "realm1");
311 }
312
313 static void
314 digest_nonce_unauthorized (SoupMessage *msg, gpointer data)
315 {
316         gboolean *got_401 = data;
317         *got_401 = TRUE;
318 }
319
320 static void
321 do_digest_nonce_test (SoupSession *session,
322                       const char *nth, const char *uri,
323                       gboolean expect_401, gboolean expect_signal)
324 {
325         SoupMessage *msg;
326         gboolean got_401;
327
328         msg = soup_message_new (SOUP_METHOD_GET, uri);
329         if (expect_signal) {
330                 g_signal_connect (session, "authenticate",
331                                   G_CALLBACK (digest_nonce_authenticate),
332                                   NULL);
333         }
334         soup_message_add_status_code_handler (msg, "got_headers",
335                                               SOUP_STATUS_UNAUTHORIZED,
336                                               G_CALLBACK (digest_nonce_unauthorized),
337                                               &got_401);
338         got_401 = FALSE;
339         soup_session_send_message (session, msg);
340         if (got_401 != expect_401) {
341                 debug_printf (1, "  %s request %s a 401 Unauthorized!\n", nth,
342                               got_401 ? "got" : "did not get");
343                 errors++;
344         }
345         if (msg->status_code != SOUP_STATUS_OK) {
346                 debug_printf (1, "  %s request got status %d %s!\n", nth,
347                               msg->status_code, msg->reason_phrase);
348                 errors++;
349         }
350         if (errors == 0)
351                 debug_printf (1, "  %s request succeeded\n", nth);
352         g_object_unref (msg);
353 }
354
355 /* Async auth test. We queue three requests to /Basic/realm1, ensuring
356  * that they are sent in order. The first and third ones will be
357  * paused from the authentication callback. The second will be allowed
358  * to fail. Shortly after the third one requests auth, we'll provide
359  * the auth and unpause the two remaining messages, allowing them to
360  * succeed.
361  */
362
363 static void
364 async_authenticate (SoupSession *session, SoupMessage *msg,
365                     SoupAuth *auth, gboolean retrying, gpointer data)
366 {
367         SoupAuth **saved_auth = data;
368         int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
369
370         debug_printf (2, "  async_authenticate msg%d\n", id);
371
372         /* The session will try to authenticate msg3 *before* sending
373          * it, because it already knows it's going to need the auth.
374          * Ignore that.
375          */
376         if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
377                 debug_printf (2, "    (ignoring)\n");
378                 return;
379         }
380
381         soup_session_pause_message (session, msg);
382         if (saved_auth)
383                 *saved_auth = g_object_ref (auth);
384         g_main_loop_quit (loop);
385 }
386
387 static void
388 async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
389 {
390         int *finished = user_data;
391         int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
392
393         debug_printf (2, "  async_finished msg%d\n", id);
394
395         (*finished)++;
396         if (*finished == 2)
397                 g_main_loop_quit (loop);
398 }
399
400 static void
401 async_authenticate_522601 (SoupSession *session, SoupMessage *msg,
402                            SoupAuth *auth, gboolean retrying, gpointer data)
403 {
404         gboolean *been_here = data;
405
406         debug_printf (2, "  async_authenticate_522601\n");
407
408         if (*been_here) {
409                 debug_printf (1, "  ERROR: async_authenticate_522601 called twice\n");
410                 errors++;
411         }
412         *been_here = TRUE;
413
414         soup_session_pause_message (session, msg);
415         g_main_loop_quit (loop);
416 }
417
418 static void
419 do_async_auth_test (const char *base_uri)
420 {
421         SoupSession *session;
422         SoupMessage *msg1, *msg2, *msg3, msg2_bak;
423         guint auth_id;
424         char *uri;
425         SoupAuth *auth = NULL;
426         int finished = 0;
427         gboolean been_there = FALSE;
428
429         debug_printf (1, "\nTesting async auth:\n");
430
431         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
432
433         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
434
435         msg1 = soup_message_new ("GET", uri);
436         g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
437         auth_id = g_signal_connect (session, "authenticate",
438                                     G_CALLBACK (async_authenticate), &auth);
439         g_object_ref (msg1);
440         soup_session_queue_message (session, msg1, async_finished, &finished);
441         g_main_loop_run (loop);
442         g_signal_handler_disconnect (session, auth_id);
443
444         /* async_authenticate will pause msg1 and quit loop */
445
446         msg2 = soup_message_new ("GET", uri);
447         g_object_set_data (G_OBJECT (msg2), "id", GINT_TO_POINTER (2));
448         soup_session_send_message (session, msg2);
449
450         if (msg2->status_code == SOUP_STATUS_UNAUTHORIZED)
451                 debug_printf (1, "  msg2 failed as expected\n");
452         else {
453                 debug_printf (1, "  msg2 got wrong status! (%u)\n",
454                               msg2->status_code);
455                 errors++;
456         }
457
458         /* msg2 should be done at this point; assuming everything is
459          * working correctly, the session won't look at it again; we
460          * ensure that if it does, it will crash the test program.
461          */
462         memcpy (&msg2_bak, msg2, sizeof (SoupMessage));
463         memset (msg2, 0, sizeof (SoupMessage));
464
465         msg3 = soup_message_new ("GET", uri);
466         g_object_set_data (G_OBJECT (msg3), "id", GINT_TO_POINTER (3));
467         auth_id = g_signal_connect (session, "authenticate",
468                                     G_CALLBACK (async_authenticate), NULL);
469         g_object_ref (msg3);
470         soup_session_queue_message (session, msg3, async_finished, &finished);
471         g_main_loop_run (loop);
472         g_signal_handler_disconnect (session, auth_id);
473
474         /* async_authenticate will pause msg3 and quit loop */
475
476         /* Now do the auth, and restart */
477         if (auth) {
478                 soup_auth_authenticate (auth, "user1", "realm1");
479                 g_object_unref (auth);
480                 soup_session_unpause_message (session, msg1);
481                 soup_session_unpause_message (session, msg3);
482
483                 g_main_loop_run (loop);
484
485                 /* async_finished will quit the loop */
486         } else {
487                 debug_printf (1, "  msg1 didn't get authenticate signal!\n");
488                 errors++;
489         }
490
491         if (msg1->status_code == SOUP_STATUS_OK)
492                 debug_printf (1, "  msg1 succeeded\n");
493         else {
494                 debug_printf (1, "  msg1 FAILED! (%u %s)\n",
495                               msg1->status_code, msg1->reason_phrase);
496                 errors++;
497         }
498         if (msg3->status_code == SOUP_STATUS_OK)
499                 debug_printf (1, "  msg3 succeeded\n");
500         else {
501                 debug_printf (1, "  msg3 FAILED! (%u %s)\n",
502                               msg3->status_code, msg3->reason_phrase);
503                 errors++;
504         }
505
506         soup_session_abort (session);
507         g_object_unref (session);
508
509         g_object_unref (msg1);
510         g_object_unref (msg3);
511         memcpy (msg2, &msg2_bak, sizeof (SoupMessage));
512         g_object_unref (msg2);
513
514         /* Test that giving the wrong password doesn't cause multiple
515          * authenticate signals the second time.
516          */
517         debug_printf (1, "\nTesting async auth with wrong password (#522601):\n");
518
519         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
520         auth = NULL;
521
522         msg1 = soup_message_new ("GET", uri);
523         g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
524         auth_id = g_signal_connect (session, "authenticate",
525                                     G_CALLBACK (async_authenticate), &auth);
526         g_object_ref (msg1);
527         soup_session_queue_message (session, msg1, async_finished, &finished);
528         g_main_loop_run (loop);
529         g_signal_handler_disconnect (session, auth_id);
530         soup_auth_authenticate (auth, "user1", "wrong");
531         g_object_unref (auth);
532         soup_session_unpause_message (session, msg1);
533
534         auth_id = g_signal_connect (session, "authenticate",
535                                     G_CALLBACK (async_authenticate_522601),
536                                     &been_there);
537         g_main_loop_run (loop);
538         g_signal_handler_disconnect (session, auth_id);
539
540         soup_session_abort (session);
541         g_object_unref (session);
542
543         g_object_unref (msg1);
544
545         g_free (uri);
546 }
547
548 int
549 main (int argc, char **argv)
550 {
551         SoupSession *session;
552         SoupMessage *msg;
553         char *base_uri, *uri, *expected;
554         gboolean authenticated;
555         int i;
556
557         test_init (argc, argv, NULL);
558         apache_init ();
559
560         base_uri = "http://localhost:47524/";
561
562         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
563         g_signal_connect (session, "authenticate",
564                           G_CALLBACK (authenticate), &i);
565
566         for (i = 0; i < ntests; i++) {
567                 debug_printf (1, "Test %d: %s\n", i + 1, tests[i].explanation);
568
569                 uri = g_strconcat (base_uri, tests[i].url, NULL);
570                 debug_printf (1, "  GET %s\n", uri);
571
572                 msg = soup_message_new (SOUP_METHOD_GET, uri);
573                 g_free (uri);
574                 if (!msg) {
575                         fprintf (stderr, "auth-test: Could not parse URI\n");
576                         exit (1);
577                 }
578
579                 expected = g_strdup (tests[i].expected);
580                 soup_message_add_status_code_handler (
581                         msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
582                         G_CALLBACK (handler), expected);
583                 soup_message_add_status_code_handler (
584                         msg, "got_headers", SOUP_STATUS_OK,
585                         G_CALLBACK (handler), expected);
586                 soup_session_send_message (session, msg);
587                 if (msg->status_code != SOUP_STATUS_UNAUTHORIZED &&
588                     msg->status_code != SOUP_STATUS_OK) {
589                         debug_printf (1, "  %d %s !\n", msg->status_code,
590                                       msg->reason_phrase);
591                         errors++;
592                 }
593                 if (*expected) {
594                         debug_printf (1, "  expected %d more round(s)\n",
595                                       (int)strlen (expected));
596                         errors++;
597                 }
598                 g_free (expected);
599
600                 if (msg->status_code != tests[i].final_status) {
601                         debug_printf (1, "  expected %d\n",
602                                       tests[i].final_status);
603                 }
604
605                 debug_printf (1, "\n");
606
607                 g_object_unref (msg);
608         }
609         soup_session_abort (session);
610         g_object_unref (session);
611
612         /* And now for some regression tests */
613         loop = g_main_loop_new (NULL, TRUE);
614
615         debug_printf (1, "Testing pipelined auth (bug 271540):\n");
616         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
617
618         authenticated = FALSE;
619         g_signal_connect (session, "authenticate",
620                           G_CALLBACK (bug271540_authenticate), &authenticated);
621
622         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
623         for (i = 0; i < 10; i++) {
624                 msg = soup_message_new (SOUP_METHOD_GET, uri);
625                 g_object_set_data (G_OBJECT (msg), "#", GINT_TO_POINTER (i + 1));
626                 g_signal_connect (msg, "wrote_headers",
627                                   G_CALLBACK (bug271540_sent), &authenticated);
628
629                 soup_session_queue_message (session, msg,
630                                             bug271540_finished, &i);
631         }
632         g_free (uri);
633
634         g_main_loop_run (loop);
635         soup_session_abort (session);
636         g_object_unref (session);
637
638         debug_printf (1, "\nTesting digest nonce expiration:\n");
639
640         /* We test two different things here:
641          *
642          *   1. If we get a 401 response with
643          *      "WWW-Authenticate: Digest stale=true...", we should
644          *      retry and succeed *without* the session asking for a
645          *      password again.
646          *
647          *   2. If we get a successful response with
648          *      "Authentication-Info: nextnonce=...", we should update
649          *      the nonce automatically so as to avoid getting a
650          *      stale nonce error on the next request.
651          *
652          * In our Apache config, /Digest/realm1 and
653          * /Digest/realm1/expire are set up to use the same auth info,
654          * but only the latter has an AuthDigestNonceLifetime (of 2
655          * seconds). The way nonces work in Apache, a nonce received
656          * from /Digest/realm1 will still expire in
657          * /Digest/realm1/expire, but it won't issue a nextnonce for a
658          * request in /Digest/realm1. This lets us test both
659          * behaviors.
660          *
661          * The expected conversation is:
662          *
663          * First message
664          *   GET /Digest/realm1
665          *
666          *   401 Unauthorized
667          *   WWW-Authenticate: Digest nonce=A
668          *
669          *   [emit 'authenticate']
670          *
671          *   GET /Digest/realm1
672          *   Authorization: Digest nonce=A
673          *
674          *   200 OK
675          *   [No Authentication-Info]
676          *
677          * [sleep 2 seconds: nonce A is no longer valid, but we have no
678          * way of knowing that]
679          *
680          * Second message
681          *   GET /Digest/realm1/expire/
682          *   Authorization: Digest nonce=A
683          *
684          *   401 Unauthorized
685          *   WWW-Authenticate: Digest stale=true nonce=B
686          *
687          *   GET /Digest/realm1/expire/
688          *   Authorization: Digest nonce=B
689          *
690          *   200 OK
691          *   Authentication-Info: nextnonce=C
692          *
693          * [sleep 1 second]
694          *
695          * Third message
696          *   GET /Digest/realm1/expire/
697          *   Authorization: Digest nonce=C
698          *   [nonce=B would work here too]
699          *
700          *   200 OK
701          *   Authentication-Info: nextnonce=D
702          *
703          * [sleep 1 second; nonces B and C are no longer valid, but D is]
704          *
705          * Fourth message
706          *   GET /Digest/realm1/expire/
707          *   Authorization: Digest nonce=D
708          *
709          *   200 OK
710          *   Authentication-Info: nextnonce=D
711          *
712          */
713
714         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
715
716         uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
717         do_digest_nonce_test (session, "First", uri, TRUE, TRUE);
718         g_free (uri);
719         sleep (2);
720         uri = g_strconcat (base_uri, "Digest/realm1/expire/", NULL);
721         do_digest_nonce_test (session, "Second", uri, TRUE, FALSE);
722         sleep (1);
723         do_digest_nonce_test (session, "Third", uri, FALSE, FALSE);
724         sleep (1);
725         do_digest_nonce_test (session, "Fourth", uri, FALSE, FALSE);
726         g_free (uri);
727
728         soup_session_abort (session);
729         g_object_unref (session);
730
731         /* Async auth */
732         do_async_auth_test (base_uri);
733
734         g_main_loop_unref (loop);
735
736         test_cleanup ();
737         return errors != 0;
738 }