Git init
[profile/ivi/libsoup2.4.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 digest_nonce_authenticate (SoupSession *session, SoupMessage *msg,
308                            SoupAuth *auth, gboolean retrying, gpointer data)
309 {
310         if (retrying)
311                 return;
312
313         if (strcmp (soup_auth_get_scheme_name (auth), "Digest") != 0 ||
314             strcmp (soup_auth_get_realm (auth), "realm1") != 0)
315                 return;
316
317         soup_auth_authenticate (auth, "user1", "realm1");
318 }
319
320 static void
321 digest_nonce_unauthorized (SoupMessage *msg, gpointer data)
322 {
323         gboolean *got_401 = data;
324         *got_401 = TRUE;
325 }
326
327 static void
328 do_digest_nonce_test (SoupSession *session,
329                       const char *nth, const char *uri,
330                       gboolean expect_401, gboolean expect_signal)
331 {
332         SoupMessage *msg;
333         gboolean got_401;
334
335         msg = soup_message_new (SOUP_METHOD_GET, uri);
336         if (expect_signal) {
337                 g_signal_connect (session, "authenticate",
338                                   G_CALLBACK (digest_nonce_authenticate),
339                                   NULL);
340         }
341         soup_message_add_status_code_handler (msg, "got_headers",
342                                               SOUP_STATUS_UNAUTHORIZED,
343                                               G_CALLBACK (digest_nonce_unauthorized),
344                                               &got_401);
345         got_401 = FALSE;
346         soup_session_send_message (session, msg);
347         if (got_401 != expect_401) {
348                 debug_printf (1, "  %s request %s a 401 Unauthorized!\n", nth,
349                               got_401 ? "got" : "did not get");
350                 errors++;
351         }
352         if (msg->status_code != SOUP_STATUS_OK) {
353                 debug_printf (1, "  %s request got status %d %s!\n", nth,
354                               msg->status_code, msg->reason_phrase);
355                 errors++;
356         }
357         if (errors == 0)
358                 debug_printf (1, "  %s request succeeded\n", nth);
359         g_object_unref (msg);
360 }
361
362 /* Async auth test. We queue three requests to /Basic/realm1, ensuring
363  * that they are sent in order. The first and third ones will be
364  * paused from the authentication callback. The second will be allowed
365  * to fail. Shortly after the third one requests auth, we'll provide
366  * the auth and unpause the two remaining messages, allowing them to
367  * succeed.
368  */
369
370 static void
371 async_authenticate (SoupSession *session, SoupMessage *msg,
372                     SoupAuth *auth, gboolean retrying, gpointer data)
373 {
374         SoupAuth **saved_auth = data;
375         int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
376
377         debug_printf (2, "  async_authenticate msg%d\n", id);
378
379         /* The session will try to authenticate msg3 *before* sending
380          * it, because it already knows it's going to need the auth.
381          * Ignore that.
382          */
383         if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
384                 debug_printf (2, "    (ignoring)\n");
385                 return;
386         }
387
388         soup_session_pause_message (session, msg);
389         if (saved_auth)
390                 *saved_auth = g_object_ref (auth);
391         g_main_loop_quit (loop);
392 }
393
394 static void
395 async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
396 {
397         int *remaining = user_data;
398         int id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "id"));
399
400         debug_printf (2, "  async_finished msg%d\n", id);
401
402         (*remaining)--;
403         if (!*remaining)
404                 g_main_loop_quit (loop);
405 }
406
407 static void
408 async_authenticate_assert_once (SoupSession *session, SoupMessage *msg,
409                                 SoupAuth *auth, gboolean retrying, gpointer data)
410 {
411         gboolean *been_here = data;
412
413         debug_printf (2, "  async_authenticate_assert_once\n");
414
415         if (*been_here) {
416                 debug_printf (1, "  ERROR: async_authenticate_assert_once called twice\n");
417                 errors++;
418         }
419         *been_here = TRUE;
420 }
421
422 static void
423 async_authenticate_assert_once_and_stop (SoupSession *session, SoupMessage *msg,
424                                          SoupAuth *auth, gboolean retrying, gpointer data)
425 {
426         gboolean *been_here = data;
427
428         debug_printf (2, "  async_authenticate_assert_once_and_stop\n");
429
430         if (*been_here) {
431                 debug_printf (1, "  ERROR: async_authenticate_assert_once called twice\n");
432                 errors++;
433         }
434         *been_here = TRUE;
435
436         soup_session_pause_message (session, msg);
437         g_main_loop_quit (loop);
438 }
439
440 static void
441 do_async_auth_test (const char *base_uri)
442 {
443         SoupSession *session;
444         SoupMessage *msg1, *msg2, *msg3, msg2_bak;
445         guint auth_id;
446         char *uri;
447         SoupAuth *auth = NULL;
448         int remaining;
449         gboolean been_there;
450
451         debug_printf (1, "\nTesting async auth:\n");
452
453         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
454         remaining = 0;
455
456         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
457
458         msg1 = soup_message_new ("GET", uri);
459         g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
460         auth_id = g_signal_connect (session, "authenticate",
461                                     G_CALLBACK (async_authenticate), &auth);
462         g_object_ref (msg1);
463         remaining++;
464         soup_session_queue_message (session, msg1, async_finished, &remaining);
465         g_main_loop_run (loop);
466         g_signal_handler_disconnect (session, auth_id);
467
468         /* async_authenticate will pause msg1 and quit loop */
469
470         msg2 = soup_message_new ("GET", uri);
471         g_object_set_data (G_OBJECT (msg2), "id", GINT_TO_POINTER (2));
472         soup_session_send_message (session, msg2);
473
474         if (msg2->status_code == SOUP_STATUS_UNAUTHORIZED)
475                 debug_printf (1, "  msg2 failed as expected\n");
476         else {
477                 debug_printf (1, "  msg2 got wrong status! (%u)\n",
478                               msg2->status_code);
479                 errors++;
480         }
481
482         /* msg2 should be done at this point; assuming everything is
483          * working correctly, the session won't look at it again; we
484          * ensure that if it does, it will crash the test program.
485          */
486         memcpy (&msg2_bak, msg2, sizeof (SoupMessage));
487         memset (msg2, 0, sizeof (SoupMessage));
488
489         msg3 = soup_message_new ("GET", uri);
490         g_object_set_data (G_OBJECT (msg3), "id", GINT_TO_POINTER (3));
491         auth_id = g_signal_connect (session, "authenticate",
492                                     G_CALLBACK (async_authenticate), NULL);
493         g_object_ref (msg3);
494         remaining++;
495         soup_session_queue_message (session, msg3, async_finished, &remaining);
496         g_main_loop_run (loop);
497         g_signal_handler_disconnect (session, auth_id);
498
499         /* async_authenticate will pause msg3 and quit loop */
500
501         /* Now do the auth, and restart */
502         if (auth) {
503                 soup_auth_authenticate (auth, "user1", "realm1");
504                 g_object_unref (auth);
505                 soup_session_unpause_message (session, msg1);
506                 soup_session_unpause_message (session, msg3);
507
508                 g_main_loop_run (loop);
509
510                 /* async_finished will quit the loop */
511         } else {
512                 debug_printf (1, "  msg1 didn't get authenticate signal!\n");
513                 errors++;
514         }
515
516         if (msg1->status_code == SOUP_STATUS_OK)
517                 debug_printf (1, "  msg1 succeeded\n");
518         else {
519                 debug_printf (1, "  msg1 FAILED! (%u %s)\n",
520                               msg1->status_code, msg1->reason_phrase);
521                 errors++;
522         }
523         if (msg3->status_code == SOUP_STATUS_OK)
524                 debug_printf (1, "  msg3 succeeded\n");
525         else {
526                 debug_printf (1, "  msg3 FAILED! (%u %s)\n",
527                               msg3->status_code, msg3->reason_phrase);
528                 errors++;
529         }
530
531         soup_test_session_abort_unref (session);
532
533         g_object_unref (msg1);
534         g_object_unref (msg3);
535         memcpy (msg2, &msg2_bak, sizeof (SoupMessage));
536         g_object_unref (msg2);
537
538         /* Test that giving the wrong password doesn't cause multiple
539          * authenticate signals the second time.
540          */
541         debug_printf (1, "\nTesting async auth with wrong password (#522601):\n");
542
543         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
544         remaining = 0;
545         auth = NULL;
546
547         msg1 = soup_message_new ("GET", uri);
548         g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
549         auth_id = g_signal_connect (session, "authenticate",
550                                     G_CALLBACK (async_authenticate), &auth);
551         g_object_ref (msg1);
552         remaining++;
553         soup_session_queue_message (session, msg1, async_finished, &remaining);
554         g_main_loop_run (loop);
555         g_signal_handler_disconnect (session, auth_id);
556         soup_auth_authenticate (auth, "user1", "wrong");
557         g_object_unref (auth);
558         soup_session_unpause_message (session, msg1);
559
560         been_there = FALSE;
561         auth_id = g_signal_connect (session, "authenticate",
562                                     G_CALLBACK (async_authenticate_assert_once),
563                                     &been_there);
564         g_main_loop_run (loop);
565         g_signal_handler_disconnect (session, auth_id);
566
567         if (!been_there) {
568                 debug_printf (1, "  authenticate not emitted?\n");
569                 errors++;
570         }
571
572         soup_test_session_abort_unref (session);
573         g_object_unref (msg1);
574
575         /* Test that giving no password doesn't cause multiple
576          * authenticate signals the second time.
577          */
578         debug_printf (1, "\nTesting async auth with no password (#583462):\n");
579
580         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
581         remaining = 0;
582
583         /* Send a message that doesn't actually authenticate
584          */
585         msg1 = soup_message_new ("GET", uri);
586         g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1));
587         auth_id = g_signal_connect (session, "authenticate",
588                                     G_CALLBACK (async_authenticate), NULL);
589         g_object_ref (msg1);
590         remaining++;
591         soup_session_queue_message (session, msg1, async_finished, &remaining);
592         g_main_loop_run (loop);
593         g_signal_handler_disconnect (session, auth_id);
594         soup_session_unpause_message (session, msg1);
595         g_main_loop_run (loop);
596         g_object_unref(msg1);
597
598         /* Now send a second message */
599         msg1 = soup_message_new ("GET", uri);
600         g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (2));
601         g_object_ref (msg1);
602         been_there = FALSE;
603         auth_id = g_signal_connect (session, "authenticate",
604                                     G_CALLBACK (async_authenticate_assert_once_and_stop),
605                                     &been_there);
606         remaining++;
607         soup_session_queue_message (session, msg1, async_finished, &remaining);
608         g_main_loop_run (loop);
609         soup_session_unpause_message (session, msg1);
610
611         g_main_loop_run (loop);
612         g_signal_handler_disconnect (session, auth_id);
613
614         soup_test_session_abort_unref (session);
615         g_object_unref (msg1);
616
617         g_free (uri);
618 }
619
620 typedef struct {
621         const char *password;
622         struct {
623                 const char *headers;
624                 const char *response;
625         } round[2];
626 } SelectAuthData;
627
628 static void
629 select_auth_authenticate (SoupSession *session, SoupMessage *msg,
630                           SoupAuth *auth, gboolean retrying, gpointer data)
631 {
632         SelectAuthData *sad = data;
633         const char *header, *basic, *digest;
634         int round = retrying ? 1 : 0;
635
636         header = soup_message_headers_get_list (msg->response_headers,
637                                                 "WWW-Authenticate");
638         basic = strstr (header, "Basic");
639         digest = strstr (header, "Digest");
640         if (basic && digest) {
641                 if (basic < digest)
642                         sad->round[round].headers = "Basic, Digest";
643                 else
644                         sad->round[round].headers = "Digest, Basic";
645         } else if (basic)
646                 sad->round[round].headers = "Basic";
647         else if (digest)
648                 sad->round[round].headers = "Digest";
649
650         sad->round[round].response = soup_auth_get_scheme_name (auth);
651         if (sad->password && !retrying)
652                 soup_auth_authenticate (auth, "user", sad->password);
653 }
654
655 static void
656 select_auth_test_one (SoupURI *uri,
657                       gboolean disable_digest, const char *password,
658                       const char *first_headers, const char *first_response,
659                       const char *second_headers, const char *second_response,
660                       guint final_status)
661 {
662         SelectAuthData sad;
663         SoupMessage *msg;
664         SoupSession *session;
665
666         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
667         if (disable_digest)
668                 soup_session_remove_feature_by_type (session, SOUP_TYPE_AUTH_DIGEST);
669
670         g_signal_connect (session, "authenticate",
671                           G_CALLBACK (select_auth_authenticate), &sad);
672         memset (&sad, 0, sizeof (sad));
673         sad.password = password;
674
675         msg = soup_message_new_from_uri ("GET", uri);
676         soup_session_send_message (session, msg);
677
678         if (strcmp (sad.round[0].headers, first_headers) != 0) {
679                 debug_printf (1, "    Header order wrong: expected %s, got %s\n",
680                               first_headers, sad.round[0].headers);
681                 errors++;
682         }
683         if (strcmp (sad.round[0].response, first_response) != 0) {
684                 debug_printf (1, "    Selected auth type wrong: expected %s, got %s\n",
685                               first_response, sad.round[0].response);
686                 errors++;
687         }
688
689         if (second_headers && !sad.round[1].headers) {
690                 debug_printf (1, "    Expected a second round!\n");
691                 errors++;
692         } else if (!second_headers && sad.round[1].headers) {
693                 debug_printf (1, "    Didn't expect a second round!\n");
694                 errors++;
695         } else if (second_headers) {
696                 if (strcmp (sad.round[1].headers, second_headers) != 0) {
697                         debug_printf (1, "    Second round header order wrong: expected %s, got %s\n",
698                                       second_headers, sad.round[1].headers);
699                         errors++;
700                 }
701                 if (strcmp (sad.round[1].response, second_response) != 0) {
702                         debug_printf (1, "    Second round selected auth type wrong: expected %s, got %s\n",
703                                       second_response, sad.round[1].response);
704                         errors++;
705                 }
706         }
707
708         if (msg->status_code != final_status) {
709                 debug_printf (1, "    Final status wrong: expected %u, got %u\n",
710                               final_status, msg->status_code);
711                 errors++;
712         }
713
714         g_object_unref (msg);
715         soup_test_session_abort_unref (session);
716 }
717
718 static void
719 server_callback (SoupServer *server, SoupMessage *msg,
720                  const char *path, GHashTable *query,
721                  SoupClientContext *context, gpointer data)
722 {
723         soup_message_set_response (msg, "text/plain",
724                                    SOUP_MEMORY_STATIC,
725                                    "OK\r\n", 4);
726         soup_message_set_status (msg, SOUP_STATUS_OK);
727 }
728
729 static gboolean
730 server_basic_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
731                             const char *username, const char *password, gpointer data)
732 {
733         if (strcmp (username, "user") != 0)
734                 return FALSE;
735         return strcmp (password, "good-basic") == 0;
736 }
737
738 static char *
739 server_digest_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
740                              const char *username, gpointer data)
741 {
742         if (strcmp (username, "user") != 0)
743                 return NULL;
744         return soup_auth_domain_digest_encode_password ("user",
745                                                         "auth-test",
746                                                         "good");
747 }
748
749 static void
750 do_select_auth_test (void)
751 {
752         SoupServer *server;
753         SoupAuthDomain *basic_auth_domain, *digest_auth_domain;
754         SoupURI *uri;
755
756         debug_printf (1, "\nTesting selection among multiple auths:\n");
757
758         /* It doesn't seem to be possible to configure Apache to serve
759          * multiple auth types for a single URL. So we have to use
760          * SoupServer here. We know that SoupServer handles the server
761          * side of this scenario correctly, because we test it against
762          * curl in server-auth-test.
763          */
764         server = soup_test_server_new (FALSE);
765         soup_server_add_handler (server, NULL,
766                                  server_callback, NULL, NULL);
767
768         uri = soup_uri_new ("http://127.0.0.1/");
769         soup_uri_set_port (uri, soup_server_get_port (server));
770
771         basic_auth_domain = soup_auth_domain_basic_new (
772                 SOUP_AUTH_DOMAIN_REALM, "auth-test",
773                 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
774                 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback,
775                 NULL);
776         soup_server_add_auth_domain (server, basic_auth_domain);
777
778         digest_auth_domain = soup_auth_domain_digest_new (
779                 SOUP_AUTH_DOMAIN_REALM, "auth-test",
780                 SOUP_AUTH_DOMAIN_ADD_PATH, "/",
781                 SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, server_digest_auth_callback,
782                 NULL);
783         soup_server_add_auth_domain (server, digest_auth_domain);
784
785         debug_printf (1, "  Testing with no auth\n");
786         select_auth_test_one (uri, FALSE, NULL,
787                               "Basic, Digest", "Digest",
788                               NULL, NULL,
789                               SOUP_STATUS_UNAUTHORIZED);
790
791         debug_printf (1, "  Testing with bad password\n");
792         select_auth_test_one (uri, FALSE, "bad",
793                               "Basic, Digest", "Digest",
794                               "Basic, Digest", "Digest",
795                               SOUP_STATUS_UNAUTHORIZED);
796
797         debug_printf (1, "  Testing with good password\n");
798         select_auth_test_one (uri, FALSE, "good",
799                               "Basic, Digest", "Digest",
800                               NULL, NULL,
801                               SOUP_STATUS_OK);
802
803         /* Test with Digest disabled in the client. */
804         debug_printf (1, "  Testing without Digest with no auth\n");
805         select_auth_test_one (uri, TRUE, NULL,
806                               "Basic, Digest", "Basic",
807                               NULL, NULL,
808                               SOUP_STATUS_UNAUTHORIZED);
809
810         debug_printf (1, "  Testing without Digest with bad password\n");
811         select_auth_test_one (uri, TRUE, "bad",
812                               "Basic, Digest", "Basic",
813                               "Basic, Digest", "Basic",
814                               SOUP_STATUS_UNAUTHORIZED);
815
816         debug_printf (1, "  Testing without Digest with good password\n");
817         select_auth_test_one (uri, TRUE, "good-basic",
818                               "Basic, Digest", "Basic",
819                               NULL, NULL,
820                               SOUP_STATUS_OK);
821
822         /* Now flip the order of the domains, verify that this flips
823          * the order of the headers, and make sure that digest auth
824          * *still* gets used.
825          */
826
827         soup_server_remove_auth_domain (server, basic_auth_domain);
828         soup_server_remove_auth_domain (server, digest_auth_domain);
829         soup_server_add_auth_domain (server, digest_auth_domain);
830         soup_server_add_auth_domain (server, basic_auth_domain);
831
832         debug_printf (1, "  Testing flipped with no auth\n");
833         select_auth_test_one (uri, FALSE, NULL,
834                               "Digest, Basic", "Digest",
835                               NULL, NULL,
836                               SOUP_STATUS_UNAUTHORIZED);
837
838         debug_printf (1, "  Testing flipped with bad password\n");
839         select_auth_test_one (uri, FALSE, "bad",
840                               "Digest, Basic", "Digest",
841                               "Digest, Basic", "Digest",
842                               SOUP_STATUS_UNAUTHORIZED);
843
844         debug_printf (1, "  Testing flipped with good password\n");
845         select_auth_test_one (uri, FALSE, "good",
846                               "Digest, Basic", "Digest",
847                               NULL, NULL,
848                               SOUP_STATUS_OK);
849
850         g_object_unref (basic_auth_domain);
851         g_object_unref (digest_auth_domain);
852         soup_uri_free (uri);
853         soup_test_server_quit_unref (server);
854 }
855
856 static SoupAuthTest relogin_tests[] = {
857         { "Auth provided via URL, should succeed",
858           "Basic/realm12/", "1", TRUE, "01", SOUP_STATUS_OK },
859
860         { "Now should automatically reuse previous auth",
861           "Basic/realm12/", "", FALSE, "1", SOUP_STATUS_OK },
862
863         { "Different auth provided via URL for the same realm, should succeed",
864           "Basic/realm12/", "2", TRUE, "2", SOUP_STATUS_OK },
865
866         { "Subdir should also automatically reuse auth",
867           "Basic/realm12/subdir/", "", FALSE, "2", SOUP_STATUS_OK },
868
869         { "Should fail with no auth",
870           "Basic/realm12/", "4", TRUE, "4", SOUP_STATUS_UNAUTHORIZED },
871
872         { "Make sure we've forgotten it",
873           "Basic/realm12/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED },
874
875         { "Should fail with no auth, fail again with bad password, and give up",
876           "Basic/realm12/", "3", FALSE, "03", SOUP_STATUS_UNAUTHORIZED },
877 };
878
879 static void
880 do_batch_tests (const gchar *base_uri_str, gint ntests)
881 {
882         SoupSession *session;
883         SoupMessage *msg;
884         char *expected, *uristr;
885         SoupURI *base_uri;
886         int i;
887
888         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
889         g_signal_connect (session, "authenticate",
890                           G_CALLBACK (authenticate), &i);
891
892         base_uri = soup_uri_new (base_uri_str);
893
894         for (i = 0; i < ntests; i++) {
895                 SoupURI *soup_uri = soup_uri_new_with_base (base_uri, current_tests[i].url);
896
897                 debug_printf (1, "Test %d: %s\n", i + 1, current_tests[i].explanation);
898
899                 if (current_tests[i].url_auth) {
900                         gchar *username = g_strdup_printf ("user%c", current_tests[i].provided[0]);
901                         gchar *password = g_strdup_printf ("realm%c", current_tests[i].provided[0]);
902                         soup_uri_set_user (soup_uri, username);
903                         soup_uri_set_password (soup_uri, password);
904                         g_free (username);
905                         g_free (password);
906                 }
907
908                 msg = soup_message_new_from_uri (SOUP_METHOD_GET, soup_uri);
909                 soup_uri_free (soup_uri);
910                 if (!msg) {
911                         fprintf (stderr, "auth-test: Could not parse URI\n");
912                         exit (1);
913                 }
914
915                 uristr = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
916                 debug_printf (1, "  GET %s\n", uristr);
917                 g_free (uristr);
918
919                 expected = g_strdup (current_tests[i].expected);
920                 soup_message_add_status_code_handler (
921                         msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
922                         G_CALLBACK (handler), expected);
923                 soup_message_add_status_code_handler (
924                         msg, "got_headers", SOUP_STATUS_OK,
925                         G_CALLBACK (handler), expected);
926                 soup_session_send_message (session, msg);
927                 if (msg->status_code != SOUP_STATUS_UNAUTHORIZED &&
928                     msg->status_code != SOUP_STATUS_OK) {
929                         debug_printf (1, "  %d %s !\n", msg->status_code,
930                                       msg->reason_phrase);
931                         errors++;
932                 }
933                 if (*expected) {
934                         debug_printf (1, "  expected %d more round(s)\n",
935                                       (int)strlen (expected));
936                         errors++;
937                 }
938                 g_free (expected);
939
940                 if (msg->status_code != current_tests[i].final_status) {
941                         debug_printf (1, "  expected %d\n",
942                                       current_tests[i].final_status);
943                 }
944
945                 debug_printf (1, "\n");
946
947                 g_object_unref (msg);
948         }
949         soup_uri_free (base_uri);
950
951         soup_test_session_abort_unref (session);
952 }
953
954 int
955 main (int argc, char **argv)
956 {
957         SoupSession *session;
958         SoupMessage *msg;
959         const char *base_uri;
960         char *uri;
961         gboolean authenticated;
962         int i, ntests;
963
964         test_init (argc, argv, NULL);
965         apache_init ();
966
967         base_uri = "http://127.0.0.1:47524/";
968
969         /* Main tests */
970         current_tests = main_tests;
971         ntests = G_N_ELEMENTS (main_tests);
972         do_batch_tests (base_uri, ntests);
973
974         /* Re-login tests */
975         current_tests = relogin_tests;
976         ntests = G_N_ELEMENTS (relogin_tests);
977         do_batch_tests (base_uri, ntests);
978
979         /* And now for some regression tests */
980         loop = g_main_loop_new (NULL, TRUE);
981
982         debug_printf (1, "Testing pipelined auth (bug 271540):\n");
983         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
984
985         authenticated = FALSE;
986         g_signal_connect (session, "authenticate",
987                           G_CALLBACK (bug271540_authenticate), &authenticated);
988
989         uri = g_strconcat (base_uri, "Basic/realm1/", NULL);
990         for (i = 0; i < 10; i++) {
991                 msg = soup_message_new (SOUP_METHOD_GET, uri);
992                 g_object_set_data (G_OBJECT (msg), "#", GINT_TO_POINTER (i + 1));
993                 g_signal_connect (msg, "wrote_headers",
994                                   G_CALLBACK (bug271540_sent), &authenticated);
995
996                 soup_session_queue_message (session, msg,
997                                             bug271540_finished, &i);
998         }
999         g_free (uri);
1000
1001         g_main_loop_run (loop);
1002         soup_test_session_abort_unref (session);
1003
1004         debug_printf (1, "\nTesting digest nonce expiration:\n");
1005
1006         /* We test two different things here:
1007          *
1008          *   1. If we get a 401 response with
1009          *      "WWW-Authenticate: Digest stale=true...", we should
1010          *      retry and succeed *without* the session asking for a
1011          *      password again.
1012          *
1013          *   2. If we get a successful response with
1014          *      "Authentication-Info: nextnonce=...", we should update
1015          *      the nonce automatically so as to avoid getting a
1016          *      stale nonce error on the next request.
1017          *
1018          * In our Apache config, /Digest/realm1 and
1019          * /Digest/realm1/expire are set up to use the same auth info,
1020          * but only the latter has an AuthDigestNonceLifetime (of 2
1021          * seconds). The way nonces work in Apache, a nonce received
1022          * from /Digest/realm1 will still expire in
1023          * /Digest/realm1/expire, but it won't issue a nextnonce for a
1024          * request in /Digest/realm1. This lets us test both
1025          * behaviors.
1026          *
1027          * The expected conversation is:
1028          *
1029          * First message
1030          *   GET /Digest/realm1
1031          *
1032          *   401 Unauthorized
1033          *   WWW-Authenticate: Digest nonce=A
1034          *
1035          *   [emit 'authenticate']
1036          *
1037          *   GET /Digest/realm1
1038          *   Authorization: Digest nonce=A
1039          *
1040          *   200 OK
1041          *   [No Authentication-Info]
1042          *
1043          * [sleep 2 seconds: nonce A is no longer valid, but we have no
1044          * way of knowing that]
1045          *
1046          * Second message
1047          *   GET /Digest/realm1/expire/
1048          *   Authorization: Digest nonce=A
1049          *
1050          *   401 Unauthorized
1051          *   WWW-Authenticate: Digest stale=true nonce=B
1052          *
1053          *   GET /Digest/realm1/expire/
1054          *   Authorization: Digest nonce=B
1055          *
1056          *   200 OK
1057          *   Authentication-Info: nextnonce=C
1058          *
1059          * [sleep 1 second]
1060          *
1061          * Third message
1062          *   GET /Digest/realm1/expire/
1063          *   Authorization: Digest nonce=C
1064          *   [nonce=B would work here too]
1065          *
1066          *   200 OK
1067          *   Authentication-Info: nextnonce=D
1068          *
1069          * [sleep 1 second; nonces B and C are no longer valid, but D is]
1070          *
1071          * Fourth message
1072          *   GET /Digest/realm1/expire/
1073          *   Authorization: Digest nonce=D
1074          *
1075          *   200 OK
1076          *   Authentication-Info: nextnonce=D
1077          *
1078          */
1079
1080         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1081
1082         uri = g_strconcat (base_uri, "Digest/realm1/", NULL);
1083         do_digest_nonce_test (session, "First", uri, TRUE, TRUE);
1084         g_free (uri);
1085         sleep (2);
1086         uri = g_strconcat (base_uri, "Digest/realm1/expire/", NULL);
1087         do_digest_nonce_test (session, "Second", uri, TRUE, FALSE);
1088         sleep (1);
1089         do_digest_nonce_test (session, "Third", uri, FALSE, FALSE);
1090         sleep (1);
1091         do_digest_nonce_test (session, "Fourth", uri, FALSE, FALSE);
1092         g_free (uri);
1093
1094         soup_test_session_abort_unref (session);
1095
1096         /* Async auth */
1097         do_async_auth_test (base_uri);
1098
1099         /* Selecting correct auth when multiple auth types are available */
1100         do_select_auth_test ();
1101
1102         g_main_loop_unref (loop);
1103
1104         test_cleanup ();
1105         return errors != 0;
1106 }