1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2008 Red Hat, Inc.
13 #include <libsoup/soup.h>
15 #include "test-utils.h"
27 TestRequest requests[3];
30 /* A redirecty response to a GET or HEAD should cause a redirect */
32 { { { "GET", "/301", 301 },
35 { { { "GET", "/302", 302 },
38 { { { "GET", "/303", 303 },
41 { { { "GET", "/307", 307 },
44 { { { "HEAD", "/301", 301 },
47 { { { "HEAD", "/302", 302 },
50 /* 303 is a nonsensical response to HEAD, but some sites do
53 { { { "HEAD", "/303", 303 },
56 { { { "HEAD", "/307", 307 },
60 /* A non-redirecty response to a GET or HEAD should not */
62 { { { "GET", "/300", 300 },
64 { { { "GET", "/304", 304 },
66 { { { "GET", "/305", 305 },
68 { { { "GET", "/306", 306 },
70 { { { "GET", "/308", 308 },
72 { { { "HEAD", "/300", 300 },
74 { { { "HEAD", "/304", 304 },
76 { { { "HEAD", "/305", 305 },
78 { { { "HEAD", "/306", 306 },
80 { { { "HEAD", "/308", 308 },
83 /* Test double-redirect */
85 { { { "GET", "/301/302", 301 },
86 { "GET", "/302", 302 },
87 { "GET", "/", 200 } }, 200 },
88 { { { "HEAD", "/301/302", 301 },
89 { "HEAD", "/302", 302 },
90 { "HEAD", "/", 200 } }, 200 },
92 /* POST should only automatically redirect on 301, 302 and 303 */
94 { { { "POST", "/301", 301 },
97 { { { "POST", "/302", 302 },
100 { { { "POST", "/303", 303 },
103 { { { "POST", "/307", 307 },
106 /* Test behavior with recoverably-bad Location header */
107 { { { "GET", "/bad", 302 },
108 { "GET", "/bad%20with%20spaces", 200 },
111 /* Test behavior with irrecoverably-bad Location header */
112 { { { "GET", "/bad-no-host", 302 },
113 { NULL } }, SOUP_STATUS_MALFORMED },
115 /* Test infinite redirection */
116 { { { "GET", "/bad-recursive", 302, TRUE },
117 { NULL } }, SOUP_STATUS_TOO_MANY_REDIRECTS },
119 /* Test redirection to a different server */
120 { { { "GET", "/server2", 302 },
121 { "GET", "/on-server2", 200 },
124 static const int n_tests = G_N_ELEMENTS (tests);
127 got_headers (SoupMessage *msg, gpointer user_data)
129 TestRequest **req = user_data;
130 const char *location;
132 debug_printf (2, " -> %d %s\n", msg->status_code,
134 location = soup_message_headers_get_one (msg->response_headers,
137 debug_printf (2, " Location: %s\n", location);
142 if (msg->status_code != (*req)->status_code) {
143 debug_printf (1, " - Expected %d !\n",
144 (*req)->status_code);
150 restarted (SoupMessage *msg, gpointer user_data)
152 TestRequest **req = user_data;
153 SoupURI *uri = soup_message_get_uri (msg);
155 debug_printf (2, " %s %s\n", msg->method, uri->path);
157 if ((*req)->method && !(*req)->repeat)
160 if (!(*req)->method) {
161 debug_printf (1, " - Expected to be done!\n");
166 if (strcmp (msg->method, (*req)->method) != 0) {
167 debug_printf (1, " - Expected %s !\n", (*req)->method);
170 if (strcmp (uri->path, (*req)->path) != 0) {
171 debug_printf (1, " - Expected %s !\n", (*req)->path);
177 do_test (SoupSession *session, SoupURI *base_uri, int n)
183 debug_printf (1, "%2d. %s %s\n", n + 1,
184 tests[n].requests[0].method,
185 tests[n].requests[0].path);
187 uri = soup_uri_new_with_base (base_uri, tests[n].requests[0].path);
188 msg = soup_message_new_from_uri (tests[n].requests[0].method, uri);
191 if (msg->method == SOUP_METHOD_POST) {
192 soup_message_set_request (msg, "text/plain",
195 strlen ("post body"));
198 req = &tests[n].requests[0];
199 g_signal_connect (msg, "got_headers",
200 G_CALLBACK (got_headers), &req);
201 g_signal_connect (msg, "restarted",
202 G_CALLBACK (restarted), &req);
204 soup_session_send_message (session, msg);
206 if (msg->status_code != tests[n].final_status) {
207 debug_printf (1, " - Expected final status of %d, got %d !\n",
208 tests[n].final_status, msg->status_code);
212 g_object_unref (msg);
213 debug_printf (2, "\n");
217 do_redirect_tests (SoupURI *base_uri)
219 SoupSession *session;
222 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
223 debug_printf (1, "Async session\n");
224 for (n = 0; n < n_tests; n++)
225 do_test (session, base_uri, n);
226 soup_test_session_abort_unref (session);
228 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
229 debug_printf (1, "\nSync session\n");
230 for (n = 0; n < n_tests; n++)
231 do_test (session, base_uri, n);
232 soup_test_session_abort_unref (session);
236 SoupSession *session;
237 SoupMessage *msg1, *msg2;
238 SoupURI *uri1, *uri2;
239 SoupSocket *sock1, *sock2;
240 } ConnectionTestData;
243 msg2_finished (SoupSession *session, SoupMessage *msg2, gpointer user_data)
245 if (!SOUP_STATUS_IS_SUCCESSFUL (msg2->status_code)) {
246 debug_printf (1, " msg2 failed: %d %s\n",
247 msg2->status_code, msg2->reason_phrase);
253 unpause_msg1 (SoupMessage *msg2, gpointer user_data)
255 ConnectionTestData *data = user_data;
258 debug_printf (1, " msg1 has no connection?\n");
260 } else if (!data->sock2) {
261 debug_printf (1, " msg2 has no connection?\n");
263 } else if (data->sock1 == data->sock2) {
264 debug_printf (1, " Both messages sharing the same connection\n");
268 soup_session_unpause_message (data->session, data->msg1);
272 msg1_just_restarted (gpointer user_data)
274 ConnectionTestData *data = user_data;
276 soup_session_pause_message (data->session, data->msg1);
278 data->msg2 = soup_message_new_from_uri ("GET", data->uri2);
280 g_signal_connect (data->msg2, "got_body",
281 G_CALLBACK (unpause_msg1), data);
283 soup_session_queue_message (data->session, data->msg2, msg2_finished, data);
288 msg1_about_to_restart (SoupMessage *msg1, gpointer user_data)
290 ConnectionTestData *data = user_data;
292 /* Do nothing when loading the redirected-to resource */
293 if (!SOUP_STATUS_IS_REDIRECTION (data->msg1->status_code))
296 /* We have to pause msg1 after the I/O finishes, but before
297 * the queue runs again.
299 g_idle_add_full (G_PRIORITY_HIGH, msg1_just_restarted, data, NULL);
303 request_started (SoupSession *session, SoupMessage *msg,
304 SoupSocket *socket, gpointer user_data)
306 ConnectionTestData *data = user_data;
308 if (msg == data->msg1)
309 data->sock1 = socket;
310 else if (msg == data->msg2)
311 data->sock2 = socket;
313 g_warn_if_reached ();
317 do_connection_test (SoupURI *base_uri)
319 ConnectionTestData data;
321 debug_printf (1, "\nConnection reuse\n");
322 memset (&data, 0, sizeof (data));
324 data.session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
325 g_signal_connect (data.session, "request-started",
326 G_CALLBACK (request_started), &data);
328 data.uri1 = soup_uri_new_with_base (base_uri, "/301");
329 data.uri2 = soup_uri_new_with_base (base_uri, "/");
330 data.msg1 = soup_message_new_from_uri ("GET", data.uri1);
332 g_signal_connect (data.msg1, "got-body",
333 G_CALLBACK (msg1_about_to_restart), &data);
334 soup_session_send_message (data.session, data.msg1);
336 if (!SOUP_STATUS_IS_SUCCESSFUL (data.msg1->status_code)) {
337 debug_printf (1, " msg1 failed: %d %s\n",
338 data.msg1->status_code, data.msg1->reason_phrase);
341 g_object_unref (data.msg1);
342 soup_uri_free (data.uri1);
343 soup_uri_free (data.uri2);
345 soup_test_session_abort_unref (data.session);
349 server_callback (SoupServer *server, SoupMessage *msg,
350 const char *path, GHashTable *query,
351 SoupClientContext *context, gpointer data)
356 /* Make sure that a HTTP/1.0 redirect doesn't cause an
357 * HTTP/1.0 re-request. (#521848)
359 if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
360 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
364 if (g_str_has_prefix (path, "/bad")) {
365 if (!strcmp (path, "/bad")) {
366 soup_message_set_status (msg, SOUP_STATUS_FOUND);
367 soup_message_headers_replace (msg->response_headers,
370 } else if (!strcmp (path, "/bad-recursive")) {
371 soup_message_set_status (msg, SOUP_STATUS_FOUND);
372 soup_message_headers_replace (msg->response_headers,
375 } else if (!strcmp (path, "/bad-no-host")) {
376 soup_message_set_status (msg, SOUP_STATUS_FOUND);
377 soup_message_headers_replace (msg->response_headers,
380 } else if (!strcmp (path, "/bad with spaces"))
381 soup_message_set_status (msg, SOUP_STATUS_OK);
383 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
385 } else if (!strcmp (path, "/server2")) {
386 soup_message_set_status (msg, SOUP_STATUS_FOUND);
387 soup_message_headers_replace (msg->response_headers,
391 } else if (!strcmp (path, "/")) {
392 if (msg->method != SOUP_METHOD_GET &&
393 msg->method != SOUP_METHOD_HEAD) {
394 soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
398 /* Make sure that redirecting a POST clears the body */
399 if (msg->request_body->length) {
400 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
404 soup_message_set_status (msg, SOUP_STATUS_OK);
406 /* FIXME: this is wrong, though it doesn't matter for
407 * the purposes of this test, and to do the right
408 * thing currently we'd have to set Content-Length by
411 if (msg->method != SOUP_METHOD_HEAD) {
412 soup_message_set_response (msg, "text/plain",
419 status_code = strtoul (path + 1, &remainder, 10);
420 if (!SOUP_STATUS_IS_REDIRECTION (status_code) ||
421 (*remainder && *remainder != '/')) {
422 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
426 /* See above comment re bug 521848. We only test this on the
427 * double-redirects so that we get connection-reuse testing
428 * the rest of the time.
430 if (*remainder == '/')
431 soup_message_set_http_version (msg, SOUP_HTTP_1_0);
433 soup_message_set_status (msg, status_code);
435 soup_message_headers_replace (msg->response_headers,
436 "Location", remainder);
438 soup_message_headers_replace (msg->response_headers,
444 server2_callback (SoupServer *server, SoupMessage *msg,
445 const char *path, GHashTable *query,
446 SoupClientContext *context, gpointer data)
448 soup_message_set_status (msg, SOUP_STATUS_OK);
451 static gboolean run_tests = TRUE;
453 static GOptionEntry no_test_entry[] = {
454 { "no-tests", 'n', G_OPTION_FLAG_REVERSE,
455 G_OPTION_ARG_NONE, &run_tests,
456 "Don't run tests, just run the test server", NULL },
461 main (int argc, char **argv)
464 SoupServer *server, *server2;
468 test_init (argc, argv, no_test_entry);
470 server = soup_test_server_new (TRUE);
471 soup_server_add_handler (server, NULL,
472 server_callback, NULL, NULL);
473 port = soup_server_get_port (server);
475 server2 = soup_test_server_new (TRUE);
476 soup_server_add_handler (server2, NULL,
477 server2_callback, NULL, NULL);
478 server2_uri = g_strdup_printf ("http://127.0.0.1:%d/on-server2",
479 soup_server_get_port (server2));
481 loop = g_main_loop_new (NULL, TRUE);
484 base_uri = soup_uri_new ("http://127.0.0.1");
485 soup_uri_set_port (base_uri, port);
486 do_redirect_tests (base_uri);
487 do_connection_test (base_uri);
488 soup_uri_free (base_uri);
490 printf ("Listening on port %d\n", port);
491 g_main_loop_run (loop);
494 g_main_loop_unref (loop);
495 g_free (server2_uri);
496 soup_test_server_quit_unref (server);
497 soup_test_server_quit_unref (server2);