1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007, 2008 Red Hat, Inc.
4  */
5 
6 #include "test-utils.h"
7 
8 static struct {
9 	const char *title, *name;
10 	const char *result;
11 } tests[] = {
12 	/* Both fields must be filled in */
13 	{ NULL, "Name", "" },
14 	{ "Mr.", NULL, "" },
15 
16 	/* Filled-in but empty is OK */
17 	{ "", "", "Hello,  " },
18 	{ "", "Name", "Hello,  Name" },
19 	{ "Mr.", "", "Hello, MR. " },
20 
21 	/* Simple */
22 	{ "Mr.", "Name", "Hello, MR. Name" },
23 
24 	/* Encoding of spaces */
25 	{ "Mr.", "Full Name", "Hello, MR. Full Name" },
26 	{ "Mr. and Mrs.", "Full Name", "Hello, MR. AND MRS. Full Name" },
27 
28 	/* Encoding of "+" */
29 	{ "Mr.+Mrs.", "Full Name", "Hello, MR.+MRS. Full Name" },
30 
31 	/* Encoding of non-ASCII. */
32 	{ "Se\xC3\xB1or", "Nombre", "Hello, SE\xC3\xB1OR Nombre" },
33 
34 	/* Encoding of '%' */
35 	{ "Mr.", "Foo %2f Bar", "Hello, MR. Foo %2f Bar" },
36 };
37 
38 static void
do_hello_test_curl(int n,gboolean extra,const char * uri)39 do_hello_test_curl (int n, gboolean extra, const char *uri)
40 {
41 	GPtrArray *args;
42 	char *title_arg = NULL, *name_arg = NULL;
43 	char *str_stdout = NULL;
44 	GError *error = NULL;
45 
46 	debug_printf (1, "%2d. '%s' '%s'%s: \n", n * 2 + (extra ? 2 : 1),
47 		      tests[n].title ? tests[n].title : "(null)",
48 		      tests[n].name  ? tests[n].name  : "(null)",
49 		      extra ? " + extra" : "");
50 
51 	args = g_ptr_array_new ();
52 	g_ptr_array_add (args, "curl");
53 	g_ptr_array_add (args, "--noproxy");
54 	g_ptr_array_add (args, "*");
55 	g_ptr_array_add (args, "-G");
56 	if (tests[n].title) {
57 		title_arg = soup_form_encode ("title", tests[n].title, NULL);
58 		g_ptr_array_add (args, "-d");
59 		g_ptr_array_add (args, title_arg);
60 	}
61 	if (tests[n].name) {
62 		name_arg = soup_form_encode ("n@me", tests[n].name, NULL);
63 		g_ptr_array_add (args, "-d");
64 		g_ptr_array_add (args, name_arg);
65 	}
66 	if (extra) {
67 		g_ptr_array_add (args, "-d");
68 		g_ptr_array_add (args, "extra=something");
69 	}
70 	g_ptr_array_add (args, (char *)uri);
71 	g_ptr_array_add (args, NULL);
72 
73 	if (g_spawn_sync (NULL, (char **)args->pdata, NULL,
74 			  G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
75 			  NULL, NULL,
76 			  &str_stdout, NULL, NULL, &error)) {
77 		g_assert_cmpstr (str_stdout, ==, tests[n].result);
78 		g_free (str_stdout);
79 	} else {
80 		g_assert_no_error (error);
81 		g_error_free (error);
82 	}
83 	g_ptr_array_free (args, TRUE);
84 	g_free (title_arg);
85 	g_free (name_arg);
86 }
87 
88 static void
do_hello_tests_curl(gconstpointer uri)89 do_hello_tests_curl (gconstpointer uri)
90 {
91 	int n;
92 
93 	if (!have_curl()) {
94 		g_test_skip ("curl is not available");
95 		return;
96 	}
97 
98 	for (n = 0; n < G_N_ELEMENTS (tests); n++) {
99 		do_hello_test_curl (n, FALSE, uri);
100 		do_hello_test_curl (n, TRUE, uri);
101 	}
102 }
103 
104 static void
do_hello_test_libsoup(int n,gboolean extra,const char * uri)105 do_hello_test_libsoup (int n, gboolean extra, const char *uri)
106 {
107 	SoupSession *session;
108 	SoupMessage *msg;
109 	GData *data;
110 	GBytes *body;
111         char *encoded;
112 
113 	debug_printf (1, "%2d. '%s' '%s'%s: \n", n * 2 + (extra ? 2 : 1),
114 		      tests[n].title ? tests[n].title : "(null)",
115 		      tests[n].name  ? tests[n].name  : "(null)",
116 		      extra ? " + extra" : "");
117 
118 	g_datalist_init (&data);
119 	if (tests[n].title)
120 		g_datalist_set_data (&data, "title", (gpointer)tests[n].title);
121 	if (tests[n].name)
122 		g_datalist_set_data (&data, "n@me", (gpointer)tests[n].name);
123 	if (extra)
124 		g_datalist_set_data (&data, "extra", (gpointer)"something");
125 
126 	session = soup_test_session_new (NULL);
127 
128         encoded = soup_form_encode_datalist (&data);
129 	msg = soup_message_new_from_encoded_form ("GET",
130 						  uri,
131 						  encoded);
132         g_free (encoded);
133 	g_datalist_clear (&data);
134 
135 	body = soup_session_send_and_read (session, msg, NULL, NULL);
136 	soup_test_assert_message_status (msg, SOUP_STATUS_OK);
137 	g_assert_cmpmem (tests[n].result, strlen (tests[n].result), g_bytes_get_data (body, NULL), g_bytes_get_size (body));
138 
139 	g_bytes_unref (body);
140 	g_object_unref (msg);
141 	soup_test_session_abort_unref (session);
142 }
143 
144 static void
do_hello_tests_libsoup(gconstpointer uri)145 do_hello_tests_libsoup (gconstpointer uri)
146 {
147 	int n;
148 
149 	for (n = 0; n < G_N_ELEMENTS (tests); n++) {
150 		do_hello_test_libsoup (n, FALSE, uri);
151 		do_hello_test_libsoup (n, TRUE, uri);
152 	}
153 }
154 
155 #define MD5_TEST_FILE (g_test_get_filename (G_TEST_DIST, "index.txt", NULL))
156 #define MD5_TEST_FILE_BASENAME "index.txt"
157 #define MD5_TEST_FILE_MIME_TYPE "text/plain"
158 
159 static char *
get_md5_data(char ** contents,gsize * length)160 get_md5_data (char **contents, gsize *length)
161 {
162 	char *my_contents, *md5;
163 	gsize my_length;
164 	GError *error = NULL;
165 
166 	if (!g_file_get_contents (MD5_TEST_FILE, &my_contents, &my_length, &error)) {
167 		g_assert_no_error (error);
168 		g_error_free (error);
169 		return NULL;
170 	}
171 
172 	md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, my_contents, my_length);
173 
174 	if (contents)
175 		*contents = my_contents;
176 	else
177 		g_free (my_contents);
178 	if (length)
179 		*length = my_length;
180 
181 	return md5;
182 }
183 
184 static void
do_md5_test_curl(gconstpointer data)185 do_md5_test_curl (gconstpointer data)
186 {
187 	const char *uri = data;
188 	char *md5;
189 	GPtrArray *args;
190 	char *file_arg, *str_stdout;
191 	GError *error = NULL;
192 
193 	if (!have_curl()) {
194 		g_test_skip ("curl is not available");
195 		return;
196 	}
197 
198 	md5 = get_md5_data (NULL, NULL);
199 	if (!md5)
200 		return;
201 
202 	args = g_ptr_array_new ();
203 	g_ptr_array_add (args, "curl");
204 	g_ptr_array_add (args, "--noproxy");
205 	g_ptr_array_add (args, "*");
206 	g_ptr_array_add (args, "-L");
207 	g_ptr_array_add (args, "-F");
208 	file_arg = g_strdup_printf ("file=@%s", MD5_TEST_FILE);
209 	g_ptr_array_add (args, file_arg);
210 	g_ptr_array_add (args, "-F");
211 	g_ptr_array_add (args, "fmt=txt");
212 	g_ptr_array_add (args, (char *)uri);
213 	g_ptr_array_add (args, NULL);
214 
215 	if (g_spawn_sync (NULL, (char **)args->pdata, NULL,
216 			  G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
217 			  NULL, NULL,
218 			  &str_stdout, NULL, NULL, NULL)) {
219 		g_assert_cmpstr (str_stdout, ==, md5);
220 		g_free (str_stdout);
221 	} else {
222 		g_assert_no_error (error);
223 		g_error_free (error);
224 	}
225 	g_ptr_array_free (args, TRUE);
226 	g_free (file_arg);
227 
228 	g_free (md5);
229 }
230 
231 static void
do_md5_test_libsoup(gconstpointer data)232 do_md5_test_libsoup (gconstpointer data)
233 {
234 	const char *uri = data;
235 	char *contents, *md5;
236 	gsize length;
237 	SoupMultipart *multipart;
238 	GBytes *buffer;
239 	SoupMessage *msg;
240 	SoupSession *session;
241 	GBytes *body;
242 
243 	g_test_bug ("601640");
244 
245 	md5 = get_md5_data (&contents, &length);
246 	if (!md5)
247 		return;
248 
249 	multipart = soup_multipart_new (SOUP_FORM_MIME_TYPE_MULTIPART);
250 	buffer = g_bytes_new (contents, length);
251 	soup_multipart_append_form_file (multipart, "file",
252 					 MD5_TEST_FILE_BASENAME,
253 					 MD5_TEST_FILE_MIME_TYPE,
254 					 buffer);
255 	g_bytes_unref (buffer);
256 	soup_multipart_append_form_string (multipart, "fmt", "text");
257 
258 	msg = soup_message_new_from_multipart (uri, multipart);
259 	soup_multipart_free (multipart);
260 
261 	session = soup_test_session_new (NULL);
262 	body = soup_session_send_and_read (session, msg, NULL, NULL);
263 
264 	soup_test_assert_message_status (msg, SOUP_STATUS_OK);
265 	g_assert_cmpmem (md5, strlen (md5), g_bytes_get_data (body, NULL), g_bytes_get_size (body));
266 
267 	g_bytes_unref (body);
268 	g_object_unref (msg);
269 	soup_test_session_abort_unref (session);
270 
271 	g_free (contents);
272 	g_free (md5);
273 }
274 
275 static void
do_form_decode_test(void)276 do_form_decode_test (void)
277 {
278 	GHashTable *table;
279 	const gchar *value;
280 	gchar *tmp;
281 
282 	if (!have_curl()) {
283 		g_test_skip ("curl is not available");
284 		return;
285 	}
286 
287 	/*  Test that the code handles multiple values with the same key.  */
288 	table = soup_form_decode ("foo=first&foo=second&foo=third");
289 
290 	/*  Allocate some memory. We do this to test for a bug in
291 	 *  soup_form_decode() that resulted in values from the hash
292 	 *  table pointing to memory that is already released.
293 	 */
294 	tmp = g_strdup ("other");
295 
296 	value = g_hash_table_lookup (table, "foo");
297 	g_assert_cmpstr (value, ==, "third");
298 
299 	g_free (tmp);
300 	g_hash_table_destroy (table);
301 }
302 
303 static void
hello_callback(SoupServer * server,SoupServerMessage * msg,const char * path,GHashTable * query,gpointer data)304 hello_callback (SoupServer        *server,
305 		SoupServerMessage *msg,
306 		const char        *path,
307 		GHashTable        *query,
308 		gpointer           data)
309 {
310 	char *title, *name, *fmt;
311 	const char *content_type;
312 	GString *buf;
313 	const char *method;
314 
315 	method = soup_server_message_get_method (msg);
316 	if (method != SOUP_METHOD_GET && method != SOUP_METHOD_HEAD) {
317 		soup_server_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED, NULL);
318 		return;
319 	}
320 
321 	if (query) {
322 		title = g_hash_table_lookup (query, "title");
323 		name = g_hash_table_lookup (query, "n@me");
324 		fmt = g_hash_table_lookup (query, "fmt");
325 	} else
326 		title = name = fmt = NULL;
327 
328 	buf = g_string_new (NULL);
329 	if (!query || (fmt && !strcmp (fmt, "html"))) {
330 		content_type = "text/html";
331 		g_string_append (buf, "<html><head><title>forms-test: hello</title></head><body>\r\n");
332 		if (title && name) {
333 			/* mumble mumble html-escape... */
334 			g_string_append_printf (buf, "<p>Hello, <b><em>%s</em> %s</b></p>\r\n",
335 						title, name);
336 		}
337 		g_string_append (buf, "<form action='/hello' method='get'>"
338 				 "<p>Title: <input name='title'></p>"
339 				 "<p>Name: <input name='n@me'></p>"
340 				 "<p><input type=hidden name='fmt' value='html'></p>"
341 				 "<p><input type=submit></p>"
342 				 "</form>\r\n");
343 		g_string_append (buf, "</body></html>\r\n");
344 	} else {
345 		content_type = "text/plain";
346 		if (title && name) {
347 			char *uptitle = g_ascii_strup (title, -1);
348 			g_string_append_printf (buf, "Hello, %s %s",
349 						uptitle, name);
350 			g_free (uptitle);
351 		}
352 	}
353 
354 	soup_server_message_set_response (msg, content_type,
355 					  SOUP_MEMORY_TAKE,
356 					  buf->str, buf->len);
357 	g_string_free (buf, FALSE);
358 	soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
359 }
360 
361 static void
md5_get_callback(SoupServer * server,SoupServerMessage * msg,const char * path,GHashTable * query,gpointer data)362 md5_get_callback (SoupServer        *server,
363 		  SoupServerMessage *msg,
364 		  const char        *path,
365 		  GHashTable        *query,
366 		  gpointer           data)
367 {
368 	const char *file = NULL, *md5sum = NULL, *fmt;
369 	const char *content_type;
370 	GString *buf;
371 
372 	if (query) {
373 		file = g_hash_table_lookup (query, "file");
374 		md5sum = g_hash_table_lookup (query, "md5sum");
375 		fmt = g_hash_table_lookup (query, "fmt");
376 	} else
377 		fmt = "html";
378 
379 	buf = g_string_new (NULL);
380 	if (!strcmp (fmt, "html")) {
381 		content_type = "text/html";
382 		g_string_append (buf, "<html><head><title>forms-test: md5</title></head><body>\r\n");
383 		if (file && md5sum) {
384 			/* mumble mumble html-escape... */
385 			g_string_append_printf (buf, "<p>File: %s<br>MD5: <b>%s</b></p>\r\n",
386 						file, md5sum);
387 		}
388 		g_string_append (buf, "<form action='/md5' method='post' enctype='multipart/form-data'>"
389 				 "<p>File: <input type='file' name='file'></p>"
390 				 "<p><input type=hidden name='fmt' value='html'></p>"
391 				 "<p><input type=submit></p>"
392 				 "</form>\r\n");
393 		g_string_append (buf, "</body></html>\r\n");
394 	} else {
395 		content_type = "text/plain";
396 		if (md5sum)
397 			g_string_append_printf (buf, "%s", md5sum);
398 	}
399 
400 	soup_server_message_set_response (msg, content_type,
401 					  SOUP_MEMORY_TAKE,
402 					  buf->str, buf->len);
403 	g_string_free (buf, FALSE);
404 	soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
405 }
406 
407 static void
md5_post_callback(SoupServer * server,SoupServerMessage * msg,const char * path,GHashTable * query,gpointer data)408 md5_post_callback (SoupServer        *server,
409 		   SoupServerMessage *msg,
410 		   const char        *path,
411 		   GHashTable        *query,
412 		   gpointer           data)
413 {
414 	const char *content_type;
415 	GHashTable *params;
416 	const char *fmt;
417 	char *filename, *md5sum, *redirect_uri;
418 	GBytes *file;
419 	GUri *uri;
420 	char *encoded_form;
421 	SoupMultipart *multipart;
422 	GBytes *body;
423 	SoupMessageHeaders *request_headers;
424 
425 	request_headers = soup_server_message_get_request_headers (msg);
426 	content_type = soup_message_headers_get_content_type (request_headers, NULL);
427 	if (!content_type || strcmp (content_type, "multipart/form-data") != 0) {
428 		soup_server_message_set_status (msg, SOUP_STATUS_BAD_REQUEST, NULL);
429 		return;
430 	}
431 
432 	body = soup_message_body_flatten (soup_server_message_get_request_body (msg));
433 	multipart = soup_multipart_new_from_message (request_headers, body);
434 	g_bytes_unref (body);
435 	params = multipart ? soup_form_decode_multipart (multipart, "file", &filename, NULL, &file) : NULL;
436 	if (!params) {
437 		soup_server_message_set_status (msg, SOUP_STATUS_BAD_REQUEST, NULL);
438 		return;
439 	}
440 	fmt = g_hash_table_lookup (params, "fmt");
441 
442 	md5sum = g_compute_checksum_for_bytes (G_CHECKSUM_MD5, file);
443 	g_bytes_unref (file);
444 
445 	encoded_form = soup_form_encode ("file", filename ? filename : "",
446 					 "md5sum", md5sum,
447 					 "fmt", fmt ? fmt : "html",
448 					 NULL);
449 	uri = soup_uri_copy (soup_server_message_get_uri (msg),
450 			     SOUP_URI_QUERY, encoded_form,
451 			     SOUP_URI_NONE);
452 	g_free (encoded_form);
453 	redirect_uri = g_uri_to_string (uri);
454 
455 	soup_server_message_set_redirect (msg, SOUP_STATUS_SEE_OTHER, redirect_uri);
456 
457 	g_free (redirect_uri);
458 	g_uri_unref (uri);
459 	g_free (md5sum);
460 	g_free (filename);
461 	g_hash_table_destroy (params);
462 }
463 
464 static void
md5_callback(SoupServer * server,SoupServerMessage * msg,const char * path,GHashTable * query,gpointer data)465 md5_callback (SoupServer        *server,
466 	      SoupServerMessage *msg,
467 	      const char        *path,
468 	      GHashTable        *query,
469 	      gpointer           data)
470 {
471 	const char *method;
472 
473 	method = soup_server_message_get_method (msg);
474 
475 	if (method == SOUP_METHOD_GET || method == SOUP_METHOD_HEAD)
476 		md5_get_callback (server, msg, path, query, data);
477 	else if (method == SOUP_METHOD_POST)
478 		md5_post_callback (server, msg, path, query, data);
479 	else
480 		soup_server_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED, NULL);
481 }
482 
483 static gboolean run_tests = TRUE;
484 
485 static GOptionEntry no_test_entry[] = {
486         { "no-tests", 'n', G_OPTION_FLAG_REVERSE,
487           G_OPTION_ARG_NONE, &run_tests,
488           "Don't run tests, just run the test server", NULL },
489         { NULL }
490 };
491 
492 int
main(int argc,char ** argv)493 main (int argc, char **argv)
494 {
495 	GMainLoop *loop;
496 	SoupServer *server;
497 	GUri *base_uri, *uri;
498 	int ret = 0;
499 
500 	test_init (argc, argv, no_test_entry);
501 
502 	server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
503 	soup_server_add_handler (server, "/hello",
504 				 hello_callback, NULL, NULL);
505 	soup_server_add_handler (server, "/md5",
506 				 md5_callback, NULL, NULL);
507 	base_uri = soup_test_server_get_uri (server, "http", NULL);
508 
509 	loop = g_main_loop_new (NULL, TRUE);
510 
511 	if (run_tests) {
512 		uri = g_uri_parse_relative (base_uri, "/hello", SOUP_HTTP_URI_FLAGS, NULL);
513 		g_test_add_data_func_full ("/forms/hello/curl", g_uri_to_string (uri), do_hello_tests_curl, g_free);
514 		g_test_add_data_func_full ("/forms/hello/libsoup", g_uri_to_string (uri), do_hello_tests_libsoup, g_free);
515 		g_uri_unref (uri);
516 
517 		uri = g_uri_parse_relative (base_uri, "/md5", SOUP_HTTP_URI_FLAGS, NULL);
518 		g_test_add_data_func_full ("/forms/md5/curl", g_uri_to_string (uri), do_md5_test_curl, g_free);
519 		g_test_add_data_func_full ("/forms/md5/libsoup", g_uri_to_string (uri), do_md5_test_libsoup, g_free);
520 		g_uri_unref (uri);
521 
522 		g_test_add_func ("/forms/decode", do_form_decode_test);
523 
524 		ret = g_test_run ();
525 	} else {
526 		g_print ("Listening on port %d\n", g_uri_get_port (base_uri));
527 		g_main_loop_run (loop);
528 	}
529 
530 	g_main_loop_unref (loop);
531 
532 	soup_test_server_quit_unref (server);
533 	g_uri_unref (base_uri);
534 
535 	if (run_tests)
536 		test_cleanup ();
537 	return ret;
538 }
539