Fix the retry-on-broken-connection codepath for SoupRequest
[platform/upstream/libsoup.git] / tests / server-auth-test.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2001-2003, Ximian, Inc.
4  */
5
6 #include "test-utils.h"
7
8 static struct {
9         gboolean client_sent_basic, client_sent_digest;
10         gboolean server_requested_basic, server_requested_digest;
11         gboolean succeeded;
12 } test_data;
13
14 static void
15 curl_exited (GPid pid, int status, gpointer data)
16 {
17         gboolean *done = data;
18
19         *done = TRUE;
20         test_data.succeeded = (status == 0);
21 }
22
23 static void
24 do_test (int n, SoupURI *base_uri, const char *path,
25          gboolean good_user, gboolean good_password,
26          gboolean offer_basic, gboolean offer_digest,
27          gboolean client_sends_basic, gboolean client_sends_digest,
28          gboolean server_requests_basic, gboolean server_requests_digest,
29          gboolean success)
30 {
31         SoupURI *uri;
32         char *uri_str;
33         GPtrArray *args;
34         GPid pid;
35         gboolean done;
36
37         debug_printf (1, "%2d. %s, %soffer Basic, %soffer Digest, %s user, %s password\n",
38                       n, path, offer_basic ? "" : "don't ",
39                       offer_digest ? "" : "don't ",
40                       good_user ? "good" : "bad",
41                       good_password ? "good" : "bad");
42
43         uri = soup_uri_new_with_base (base_uri, path);
44         uri_str = soup_uri_to_string (uri, FALSE);
45         soup_uri_free (uri);
46
47         args = g_ptr_array_new ();
48         g_ptr_array_add (args, "curl");
49         g_ptr_array_add (args, "-f");
50         g_ptr_array_add (args, "-s");
51         if (offer_basic || offer_digest) {
52                 g_ptr_array_add (args, "-u");
53                 if (good_user) {
54                         if (good_password)
55                                 g_ptr_array_add (args, "user:password");
56                         else
57                                 g_ptr_array_add (args, "user:badpassword");
58                 } else {
59                         if (good_password)
60                                 g_ptr_array_add (args, "baduser:password");
61                         else
62                                 g_ptr_array_add (args, "baduser:badpassword");
63                 }
64
65                 if (offer_basic && offer_digest)
66                         g_ptr_array_add (args, "--anyauth");
67                 else if (offer_basic)
68                         g_ptr_array_add (args, "--basic");
69                 else
70                         g_ptr_array_add (args, "--digest");
71         }
72         g_ptr_array_add (args, uri_str);
73         g_ptr_array_add (args, NULL);
74
75         memset (&test_data, 0, sizeof (test_data));
76         if (g_spawn_async (NULL, (char **)args->pdata, NULL,
77                            G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_DO_NOT_REAP_CHILD,
78                            NULL, NULL, &pid, NULL)) {
79                 done = FALSE;
80                 g_child_watch_add (pid, curl_exited, &done);
81
82                 while (!done)
83                         g_main_context_iteration (NULL, TRUE);
84         } else
85                 test_data.succeeded = FALSE;
86         g_ptr_array_free (args, TRUE);
87         g_free (uri_str);
88
89         if (server_requests_basic != test_data.server_requested_basic) {
90                 errors++;
91                 if (test_data.server_requested_basic)
92                         debug_printf (1, "  Server sent WWW-Authenticate: Basic, but shouldn't have!\n");
93                 else
94                         debug_printf (1, "  Server didn't send WWW-Authenticate: Basic, but should have!\n");
95         }
96         if (server_requests_digest != test_data.server_requested_digest) {
97                 errors++;
98                 if (test_data.server_requested_digest)
99                         debug_printf (1, "  Server sent WWW-Authenticate: Digest, but shouldn't have!\n");
100                 else
101                         debug_printf (1, "  Server didn't send WWW-Authenticate: Digest, but should have!\n");
102         }
103         if (client_sends_basic != test_data.client_sent_basic) {
104                 errors++;
105                 if (test_data.client_sent_basic)
106                         debug_printf (1, "  Client sent Authorization: Basic, but shouldn't have!\n");
107                 else
108                         debug_printf (1, "  Client didn't send Authorization: Basic, but should have!\n");
109         }
110         if (client_sends_digest != test_data.client_sent_digest) {
111                 errors++;
112                 if (test_data.client_sent_digest)
113                         debug_printf (1, "  Client sent Authorization: Digest, but shouldn't have!\n");
114                 else
115                         debug_printf (1, "  Client didn't send Authorization: Digest, but should have!\n");
116         }
117         if (success && !test_data.succeeded) {
118                 errors++;
119                 debug_printf (1, "  Should have succeeded, but didn't!\n");
120         } else if (!success && test_data.succeeded) {
121                 errors++;
122                 debug_printf (1, "  Should not have succeeded, but did!\n");
123         }
124 }
125
126 static void
127 do_auth_tests (SoupURI *base_uri)
128 {
129         int i, n = 1;
130         gboolean use_basic, use_digest, good_user, good_password;
131         gboolean preemptive_basic, good_auth;
132
133         for (i = 0; i < 16; i++) {
134                 use_basic     = (i & 1) == 1;
135                 use_digest    = (i & 2) == 2;
136                 good_user     = (i & 4) == 4;
137                 good_password = (i & 8) == 8;
138
139                 good_auth = good_user && good_password;
140
141                 /* Curl will preemptively send Basic if it's told to
142                  * use Basic but not Digest.
143                  */
144                 preemptive_basic = use_basic && !use_digest;
145
146                 /* 1. No auth required. The server will ignore the
147                  * Authorization headers completely, and the request
148                  * will always succeed.
149                  */
150                 do_test (n++, base_uri, "/foo",
151                          good_user, good_password,
152                          /* request */
153                          use_basic, use_digest,
154                          /* expected from client */
155                          preemptive_basic, FALSE,
156                          /* expected from server */
157                          FALSE, FALSE,
158                          /* success? */
159                          TRUE);
160
161                 /* 2. Basic auth required. The server will send
162                  * "WWW-Authenticate: Basic" if the client fails to
163                  * send an Authorization: Basic on the first request,
164                  * or if it sends a bad password.
165                  */
166                 do_test (n++, base_uri, "/Basic/foo",
167                          good_user, good_password,
168                          /* request */
169                          use_basic, use_digest,
170                          /* expected from client */
171                          use_basic, FALSE,
172                          /* expected from server */
173                          !preemptive_basic || !good_auth, FALSE,
174                          /* success? */
175                          use_basic && good_auth);
176
177                 /* 3. Digest auth required. Simpler than the basic
178                  * case because the client can't send Digest auth
179                  * premptively.
180                  */
181                 do_test (n++, base_uri, "/Digest/foo",
182                          good_user, good_password,
183                          /* request */
184                          use_basic, use_digest,
185                          /* expected from client */
186                          preemptive_basic, use_digest,
187                          /* expected from server */
188                          FALSE, TRUE,
189                          /* success? */
190                          use_digest && good_auth);
191
192                 /* 4. Any auth required. */
193                 do_test (n++, base_uri, "/Any/foo",
194                          good_user, good_password,
195                          /* request */
196                          use_basic, use_digest,
197                          /* expected from client */
198                          preemptive_basic, use_digest,
199                          /* expected from server */
200                          !preemptive_basic || !good_auth, !preemptive_basic || !good_auth,
201                          /* success? */
202                          (use_basic || use_digest) && good_auth);
203
204                 /* 5. No auth required again. (Makes sure that
205                  * SOUP_AUTH_DOMAIN_REMOVE_PATH works.)
206                  */
207                 do_test (n++, base_uri, "/Any/Not/foo",
208                          good_user, good_password,
209                          /* request */
210                          use_basic, use_digest,
211                          /* expected from client */
212                          preemptive_basic, FALSE,
213                          /* expected from server */
214                          FALSE, FALSE,
215                          /* success? */
216                          TRUE);
217         }
218 }
219
220 static gboolean
221 basic_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
222                      const char *username, const char *password, gpointer data)
223 {
224         return !strcmp (username, "user") && !strcmp (password, "password");
225 }
226
227 static char *
228 digest_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
229                       const char *username, gpointer data)
230 {
231         if (strcmp (username, "user") != 0)
232                 return NULL;
233
234         /* Note: this is exactly how you *shouldn't* do it in the real
235          * world; you should have the pre-encoded password stored in a
236          * database of some sort rather than using the cleartext
237          * password in the callback.
238          */
239         return soup_auth_domain_digest_encode_password ("user",
240                                                         "server-auth-test",
241                                                         "password");
242 }
243
244 static void
245 server_callback (SoupServer *server, SoupMessage *msg,
246                  const char *path, GHashTable *query,
247                  SoupClientContext *context, gpointer data)
248 {
249         if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_HEAD) {
250                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
251                 return;
252         }
253
254         soup_message_set_response (msg, "text/plain",
255                                    SOUP_MEMORY_STATIC,
256                                    "OK\r\n", 4);
257         soup_message_set_status (msg, SOUP_STATUS_OK);
258 }
259
260 static void
261 got_headers_callback (SoupMessage *msg, gpointer data)
262 {
263         const char *header;
264
265         header = soup_message_headers_get_one (msg->request_headers,
266                                                "Authorization");
267         if (header) {
268                 if (strstr (header, "Basic "))
269                         test_data.client_sent_basic = TRUE;
270                 if (strstr (header, "Digest "))
271                         test_data.client_sent_digest = TRUE;
272         }
273 }
274
275 static void
276 wrote_headers_callback (SoupMessage *msg, gpointer data)
277 {
278         const char *header;
279
280         header = soup_message_headers_get_list (msg->response_headers,
281                                                 "WWW-Authenticate");
282         if (header) {
283                 if (strstr (header, "Basic "))
284                         test_data.server_requested_basic = TRUE;
285                 if (strstr (header, "Digest "))
286                         test_data.server_requested_digest = TRUE;
287         }
288 }
289
290 static void
291 request_started_callback (SoupServer *server, SoupMessage *msg,
292                           SoupClientContext *client, gpointer data)
293 {
294         g_signal_connect (msg, "got_headers",
295                           G_CALLBACK (got_headers_callback), NULL);
296         g_signal_connect (msg, "wrote_headers",
297                           G_CALLBACK (wrote_headers_callback), NULL);
298 }
299
300 static gboolean run_tests = TRUE;
301
302 static GOptionEntry no_test_entry[] = {
303         { "no-tests", 'n', G_OPTION_FLAG_REVERSE,
304           G_OPTION_ARG_NONE, &run_tests,
305           "Don't run tests, just run the test server", NULL },
306         { NULL }
307 };
308
309 int
310 main (int argc, char **argv)
311 {
312         GMainLoop *loop;
313         SoupServer *server;
314         SoupURI *uri;
315         SoupAuthDomain *auth_domain;
316
317         test_init (argc, argv, no_test_entry);
318
319         server = soup_test_server_new (FALSE);
320         g_signal_connect (server, "request_started",
321                           G_CALLBACK (request_started_callback), NULL);
322         soup_server_add_handler (server, NULL,
323                                  server_callback, NULL, NULL);
324
325         auth_domain = soup_auth_domain_basic_new (
326                 SOUP_AUTH_DOMAIN_REALM, "server-auth-test",
327                 SOUP_AUTH_DOMAIN_ADD_PATH, "/Basic",
328                 SOUP_AUTH_DOMAIN_ADD_PATH, "/Any",
329                 SOUP_AUTH_DOMAIN_REMOVE_PATH, "/Any/Not",
330                 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, basic_auth_callback,
331                 NULL);
332         soup_server_add_auth_domain (server, auth_domain);
333         g_object_unref (auth_domain);
334
335         auth_domain = soup_auth_domain_digest_new (
336                 SOUP_AUTH_DOMAIN_REALM, "server-auth-test",
337                 SOUP_AUTH_DOMAIN_ADD_PATH, "/Digest",
338                 SOUP_AUTH_DOMAIN_ADD_PATH, "/Any",
339                 SOUP_AUTH_DOMAIN_REMOVE_PATH, "/Any/Not",
340                 SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, digest_auth_callback,
341                 NULL);
342         soup_server_add_auth_domain (server, auth_domain);
343         g_object_unref (auth_domain);
344
345         loop = g_main_loop_new (NULL, TRUE);
346
347         if (run_tests) {
348                 uri = soup_uri_new ("http://127.0.0.1");
349                 soup_uri_set_port (uri, soup_server_get_port (server));
350                 do_auth_tests (uri);
351                 soup_uri_free (uri);
352         } else {
353                 g_print ("Listening on port %d\n", soup_server_get_port (server));
354                 g_main_loop_run (loop);
355         }
356
357         g_main_loop_unref (loop);
358         soup_test_server_quit_unref (server);
359
360         if (run_tests)
361                 test_cleanup ();
362         return errors != 0;
363 }