• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
curl_exited(GPid pid,int status,gpointer data)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
do_test(SoupURI * base_uri,const char * path,gboolean good_user,gboolean good_password,gboolean offer_basic,gboolean offer_digest,gboolean client_sends_basic,gboolean client_sends_digest,gboolean server_requests_basic,gboolean server_requests_digest,gboolean success)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
do_server_auth_test(gconstpointer data)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
basic_auth_callback(SoupAuthDomain * auth_domain,SoupMessage * msg,const char * username,const char * password,gpointer data)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 *
digest_auth_callback(SoupAuthDomain * auth_domain,SoupMessage * msg,const char * username,gpointer data)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
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)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
got_headers_callback(SoupMessage * msg,gpointer data)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
wrote_headers_callback(SoupMessage * msg,gpointer data)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
request_started_callback(SoupServer * server,SoupMessage * msg,SoupClientContext * client,gpointer data)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
main(int argc,char ** argv)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 }
402