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