• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2008 Red Hat, Inc.
4  */
5 
6 #include "test-utils.h"
7 
8 SoupURI *base_uri;
9 char *server2_uri;
10 SoupSession *async_session, *sync_session;
11 
12 typedef struct {
13 	const char *method;
14 	const char *path;
15 	guint status_code;
16 	gboolean repeat;
17 } TestRequest;
18 
19 typedef struct {
20 	TestRequest requests[3];
21 	guint final_status;
22 	const char *bugref;
23 } TestCase;
24 
25 static TestCase tests[] = {
26 	/* A redirecty response to a GET or HEAD should cause a redirect */
27 
28 	{ { { "GET", "/301", 301 },
29 	    { "GET", "/", 200 },
30 	    { NULL } }, 200, NULL },
31 	{ { { "GET", "/302", 302 },
32 	    { "GET", "/", 200 },
33 	    { NULL } }, 200, NULL },
34 	{ { { "GET", "/303", 303 },
35 	    { "GET", "/", 200 },
36 	    { NULL } }, 200, NULL },
37 	{ { { "GET", "/307", 307 },
38 	    { "GET", "/", 200 },
39 	    { NULL } }, 200, NULL },
40 	{ { { "HEAD", "/301", 301 },
41 	    { "HEAD", "/", 200 },
42 	    { NULL } }, 200, "551190" },
43 	{ { { "HEAD", "/302", 302 },
44 	    { "HEAD", "/", 200 },
45 	    { NULL } }, 200, "551190" },
46 	/* 303 is a nonsensical response to HEAD, but some sites do
47 	 * it anyway. :-/
48 	 */
49 	{ { { "HEAD", "/303", 303 },
50 	    { "HEAD", "/", 200 },
51 	    { NULL } }, 200, "600830" },
52 	{ { { "HEAD", "/307", 307 },
53 	    { "HEAD", "/", 200 },
54 	    { NULL } }, 200, "551190" },
55 
56 	/* A non-redirecty response to a GET or HEAD should not */
57 
58 	{ { { "GET", "/300", 300 },
59 	    { NULL } }, 300, NULL },
60 	{ { { "GET", "/304", 304 },
61 	    { NULL } }, 304, NULL },
62 	{ { { "GET", "/305", 305 },
63 	    { NULL } }, 305, NULL },
64 	{ { { "GET", "/306", 306 },
65 	    { NULL } }, 306, NULL },
66 	{ { { "GET", "/308", 308 },
67 	    { NULL } }, 308, NULL },
68 	{ { { "HEAD", "/300", 300 },
69 	    { NULL } }, 300, "551190" },
70 	{ { { "HEAD", "/304", 304 },
71 	    { NULL } }, 304, "551190" },
72 	{ { { "HEAD", "/305", 305 },
73 	    { NULL } }, 305, "551190" },
74 	{ { { "HEAD", "/306", 306 },
75 	    { NULL } }, 306, "551190" },
76 	{ { { "HEAD", "/308", 308 },
77 	    { NULL } }, 308, "551190" },
78 
79 	/* Test double-redirect */
80 
81 	{ { { "GET", "/301/302", 301 },
82 	    { "GET", "/302", 302 },
83 	    { "GET", "/", 200 } }, 200, NULL },
84 	{ { { "HEAD", "/301/302", 301 },
85 	    { "HEAD", "/302", 302 },
86 	    { "HEAD", "/", 200 } }, 200, "551190" },
87 
88 	/* POST should only automatically redirect on 301, 302 and 303 */
89 
90 	{ { { "POST", "/301", 301 },
91 	    { "GET", "/", 200 },
92 	    { NULL } }, 200, "586692" },
93 	{ { { "POST", "/302", 302 },
94 	    { "GET", "/", 200 },
95 	    { NULL } }, 200, NULL },
96 	{ { { "POST", "/303", 303 },
97 	    { "GET", "/", 200 },
98 	    { NULL } }, 200, NULL },
99 	{ { { "POST", "/307", 307 },
100 	    { NULL } }, 307, NULL },
101 
102 	/* Test behavior with recoverably-bad Location header */
103 	{ { { "GET", "/bad", 302 },
104 	    { "GET", "/bad%20with%20spaces", 200 },
105 	    { NULL } }, 200, "566530" },
106 
107 	/* Test behavior with irrecoverably-bad Location header */
108 	{ { { "GET", "/bad-no-host", 302 },
109 	    { NULL } }, SOUP_STATUS_MALFORMED, "528882" },
110 
111 	/* Test infinite redirection */
112 	{ { { "GET", "/bad-recursive", 302, TRUE },
113 	    { NULL } }, SOUP_STATUS_TOO_MANY_REDIRECTS, "604383" },
114 
115 	/* Test redirection to a different server */
116 	{ { { "GET", "/server2", 302 },
117 	    { "GET", "/on-server2", 200 },
118 	    { NULL } }, 200, NULL },
119 };
120 static const int n_tests = G_N_ELEMENTS (tests);
121 
122 static void
got_headers(SoupMessage * msg,gpointer user_data)123 got_headers (SoupMessage *msg, gpointer user_data)
124 {
125 	TestRequest **treq = user_data;
126 	const char *location;
127 
128 	debug_printf (2, "    -> %d %s\n", msg->status_code,
129 		      msg->reason_phrase);
130 	location = soup_message_headers_get_one (msg->response_headers,
131 						 "Location");
132 	if (location)
133 		debug_printf (2, "       Location: %s\n", location);
134 
135 	if (!(*treq)->method)
136 		return;
137 
138 	soup_test_assert_message_status (msg, (*treq)->status_code);
139 }
140 
141 static void
restarted(SoupMessage * msg,gpointer user_data)142 restarted (SoupMessage *msg, gpointer user_data)
143 {
144 	TestRequest **treq = user_data;
145 	SoupURI *uri = soup_message_get_uri (msg);
146 
147 	debug_printf (2, "    %s %s\n", msg->method, uri->path);
148 
149 	if ((*treq)->method && !(*treq)->repeat)
150 		(*treq)++;
151 
152 	soup_test_assert ((*treq)->method,
153 			  "Expected to be done");
154 
155 	g_assert_cmpstr (msg->method, ==, (*treq)->method);
156 	g_assert_cmpstr (uri->path, ==, (*treq)->path);
157 }
158 
159 static void
do_message_api_test(SoupSession * session,TestCase * test)160 do_message_api_test (SoupSession *session, TestCase *test)
161 {
162 	SoupURI *uri;
163 	SoupMessage *msg;
164 	TestRequest *treq;
165 
166 	if (test->bugref)
167 		g_test_bug (test->bugref);
168 
169 	uri = soup_uri_new_with_base (base_uri, test->requests[0].path);
170 	msg = soup_message_new_from_uri (test->requests[0].method, uri);
171 	soup_uri_free (uri);
172 
173 	if (msg->method == SOUP_METHOD_POST) {
174 		soup_message_set_request (msg, "text/plain",
175 					  SOUP_MEMORY_STATIC,
176 					  "post body",
177 					  strlen ("post body"));
178 	}
179 
180 	treq = &test->requests[0];
181 	g_signal_connect (msg, "got_headers",
182 			  G_CALLBACK (got_headers), &treq);
183 	g_signal_connect (msg, "restarted",
184 			  G_CALLBACK (restarted), &treq);
185 
186 	soup_session_send_message (session, msg);
187 
188 	soup_test_assert_message_status (msg, test->final_status);
189 
190 	g_object_unref (msg);
191 }
192 
193 static void
do_request_api_test(SoupSession * session,TestCase * test)194 do_request_api_test (SoupSession *session, TestCase *test)
195 {
196 	SoupURI *uri;
197 	SoupRequestHTTP *reqh;
198 	SoupMessage *msg;
199 	TestRequest *treq;
200 	GInputStream *stream;
201 	GError *error = NULL;
202 
203 	if (test->bugref)
204 		g_test_bug (test->bugref);
205 
206 	uri = soup_uri_new_with_base (base_uri, test->requests[0].path);
207 	reqh = soup_session_request_http_uri (session,
208 					      test->requests[0].method,
209 					      uri, &error);
210 	soup_uri_free (uri);
211 	g_assert_no_error (error);
212 	if (error) {
213 		g_error_free (error);
214 		return;
215 	}
216 
217 	msg = soup_request_http_get_message (reqh);
218 	if (msg->method == SOUP_METHOD_POST) {
219 		soup_message_set_request (msg, "text/plain",
220 					  SOUP_MEMORY_STATIC,
221 					  "post body",
222 					  strlen ("post body"));
223 	}
224 
225 	treq = &test->requests[0];
226 	g_signal_connect (msg, "got_headers",
227 			  G_CALLBACK (got_headers), &treq);
228 	g_signal_connect (msg, "restarted",
229 			  G_CALLBACK (restarted), &treq);
230 
231 	stream = soup_test_request_send (SOUP_REQUEST (reqh), NULL, 0, &error);
232 
233 	if (SOUP_STATUS_IS_TRANSPORT_ERROR (test->final_status) &&
234 	    test->final_status != SOUP_STATUS_MALFORMED) {
235 		g_assert_error (error, SOUP_HTTP_ERROR, test->final_status);
236 		g_clear_error (&error);
237 
238 		g_assert_null (stream);
239 		g_clear_object (&stream);
240 
241 		g_object_unref (msg);
242 		g_object_unref (reqh);
243 		return;
244 	}
245 
246 	g_assert_no_error (error);
247 	if (error) {
248 		g_error_free (error);
249 		g_object_unref (msg);
250 		g_object_unref (reqh);
251 		return;
252 	}
253 
254 	soup_test_request_read_all (SOUP_REQUEST (reqh), stream, NULL, &error);
255 	g_assert_no_error (error);
256 	g_clear_error (&error);
257 
258 	soup_test_request_close_stream (SOUP_REQUEST (reqh), stream, NULL, &error);
259 	g_assert_no_error (error);
260 	g_clear_error (&error);
261 	g_object_unref (stream);
262 
263 	if (test->final_status == SOUP_STATUS_MALFORMED)
264 		g_assert_cmpint (msg->status_code, ==, test->requests[0].status_code);
265 	else
266 		g_assert_cmpint (msg->status_code, ==, test->final_status);
267 
268 	g_object_unref (msg);
269 	g_object_unref (reqh);
270 }
271 
272 static void
do_async_msg_api_test(gconstpointer test)273 do_async_msg_api_test (gconstpointer test)
274 {
275 	do_message_api_test (async_session, (TestCase *)test);
276 }
277 
278 static void
do_async_req_api_test(gconstpointer test)279 do_async_req_api_test (gconstpointer test)
280 {
281 	do_request_api_test (async_session, (TestCase *)test);
282 }
283 
284 static void
do_sync_msg_api_test(gconstpointer test)285 do_sync_msg_api_test (gconstpointer test)
286 {
287 	do_message_api_test (sync_session, (TestCase *)test);
288 }
289 
290 static void
do_sync_req_api_test(gconstpointer test)291 do_sync_req_api_test (gconstpointer test)
292 {
293 	do_request_api_test (sync_session, (TestCase *)test);
294 }
295 
296 static void
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)297 server_callback (SoupServer *server, SoupMessage *msg,
298 		 const char *path, GHashTable *query,
299 		 SoupClientContext *context, gpointer data)
300 {
301 	char *remainder;
302 	guint status_code;
303 
304 	/* Make sure that a HTTP/1.0 redirect doesn't cause an
305 	 * HTTP/1.0 re-request. (#521848)
306 	 */
307 	if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
308 		soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
309 		return;
310 	}
311 
312 	if (g_str_has_prefix (path, "/bad")) {
313 		if (!strcmp (path, "/bad")) {
314 			soup_message_set_status (msg, SOUP_STATUS_FOUND);
315 			soup_message_headers_replace (msg->response_headers,
316 						      "Location",
317 						      "/bad with spaces");
318 		} else if (!strcmp (path, "/bad-recursive")) {
319 			soup_message_set_status (msg, SOUP_STATUS_FOUND);
320 			soup_message_headers_replace (msg->response_headers,
321 						      "Location",
322 						      "/bad-recursive");
323 		} else if (!strcmp (path, "/bad-no-host")) {
324 			soup_message_set_status (msg, SOUP_STATUS_FOUND);
325 			soup_message_headers_replace (msg->response_headers,
326 						      "Location",
327 						      "about:blank");
328 		} else if (!strcmp (path, "/bad with spaces"))
329 			soup_message_set_status (msg, SOUP_STATUS_OK);
330 		else
331 			soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
332 		return;
333 	} else if (!strcmp (path, "/server2")) {
334 		soup_message_set_status (msg, SOUP_STATUS_FOUND);
335 		soup_message_headers_replace (msg->response_headers,
336 					      "Location",
337 					      server2_uri);
338 		return;
339 	} else if (!strcmp (path, "/")) {
340 		if (msg->method != SOUP_METHOD_GET &&
341 		    msg->method != SOUP_METHOD_HEAD) {
342 			soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
343 			return;
344 		}
345 
346 		/* Make sure that redirecting a POST clears the body */
347 		if (msg->request_body->length) {
348 			soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
349 			return;
350 		}
351 
352 		soup_message_set_status (msg, SOUP_STATUS_OK);
353 
354 		/* FIXME: this is wrong, though it doesn't matter for
355 		 * the purposes of this test, and to do the right
356 		 * thing currently we'd have to set Content-Length by
357 		 * hand.
358 		 */
359 		if (msg->method != SOUP_METHOD_HEAD) {
360 			soup_message_set_response (msg, "text/plain",
361 						   SOUP_MEMORY_STATIC,
362 						   "OK\r\n", 4);
363 		}
364 		return;
365 	}
366 
367 	status_code = strtoul (path + 1, &remainder, 10);
368 	if (!SOUP_STATUS_IS_REDIRECTION (status_code) ||
369 	    (*remainder && *remainder != '/')) {
370 		soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
371 		return;
372 	}
373 
374 	/* See above comment re bug 521848. We only test this on the
375 	 * double-redirects so that we get connection-reuse testing
376 	 * the rest of the time.
377 	 */
378 	if (*remainder == '/')
379 		soup_message_set_http_version (msg, SOUP_HTTP_1_0);
380 
381 	soup_message_set_redirect (msg, status_code,
382 				   *remainder ? remainder : "/");
383 }
384 
385 static void
server2_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)386 server2_callback (SoupServer *server, SoupMessage *msg,
387 		  const char *path, GHashTable *query,
388 		  SoupClientContext *context, gpointer data)
389 {
390 	soup_message_set_status (msg, SOUP_STATUS_OK);
391 }
392 
393 int
main(int argc,char ** argv)394 main (int argc, char **argv)
395 {
396 	GMainLoop *loop;
397 	SoupServer *server, *server2;
398 	SoupURI *uri2;
399 	char *path;
400 	int n, ret;
401 
402 	test_init (argc, argv, NULL);
403 
404 	server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
405 	soup_server_add_handler (server, NULL,
406 				 server_callback, NULL, NULL);
407 	base_uri = soup_test_server_get_uri (server, "http", NULL);
408 
409 	server2 = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
410 	soup_server_add_handler (server2, NULL,
411 				 server2_callback, NULL, NULL);
412 	uri2 = soup_test_server_get_uri (server2, "http", NULL);
413 	soup_uri_set_path (uri2, "/on-server2");
414 	server2_uri = soup_uri_to_string (uri2, FALSE);
415 	soup_uri_free (uri2);
416 
417 	loop = g_main_loop_new (NULL, TRUE);
418 
419 	async_session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
420 					       SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
421 					       NULL);
422 	sync_session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
423 
424 	for (n = 0; n < n_tests; n++) {
425 		path = g_strdup_printf ("/redirect/async/msg/%d-%s-%d", n,
426 					tests[n].requests[0].method,
427 					tests[n].requests[0].status_code);
428 		g_test_add_data_func (path, &tests[n], do_async_msg_api_test);
429 		g_free (path);
430 
431 		path = g_strdup_printf ("/redirect/async/req/%d-%s-%d", n,
432 					tests[n].requests[0].method,
433 					tests[n].requests[0].status_code);
434 		g_test_add_data_func (path, &tests[n], do_async_req_api_test);
435 		g_free (path);
436 
437 		path = g_strdup_printf ("/redirect/sync/msg/%d-%s-%d", n,
438 					tests[n].requests[0].method,
439 					tests[n].requests[0].status_code);
440 		g_test_add_data_func (path, &tests[n], do_sync_msg_api_test);
441 		g_free (path);
442 
443 		path = g_strdup_printf ("/redirect/sync/req/%d-%s-%d", n,
444 					tests[n].requests[0].method,
445 					tests[n].requests[0].status_code);
446 		g_test_add_data_func (path, &tests[n], do_sync_req_api_test);
447 		g_free (path);
448 	}
449 
450 	ret = g_test_run ();
451 
452 	g_main_loop_unref (loop);
453 	soup_uri_free (base_uri);
454 	soup_test_server_quit_unref (server);
455 	g_free (server2_uri);
456 	soup_test_server_quit_unref (server2);
457 
458 	soup_test_session_abort_unref (async_session);
459 	soup_test_session_abort_unref (sync_session);
460 
461 	test_cleanup ();
462 	return ret;
463 }
464