1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2008 Red Hat, Inc.
6 #include "test-utils.h"
18 TestRequest requests[3];
20 guint request_api_final_status;
22 /* A redirecty response to a GET or HEAD should cause a redirect */
24 { { { "GET", "/301", 301 },
27 { { { "GET", "/302", 302 },
30 { { { "GET", "/303", 303 },
33 { { { "GET", "/307", 307 },
36 { { { "HEAD", "/301", 301 },
39 { { { "HEAD", "/302", 302 },
42 /* 303 is a nonsensical response to HEAD, but some sites do
45 { { { "HEAD", "/303", 303 },
48 { { { "HEAD", "/307", 307 },
52 /* A non-redirecty response to a GET or HEAD should not */
54 { { { "GET", "/300", 300 },
56 { { { "GET", "/304", 304 },
58 { { { "GET", "/305", 305 },
60 { { { "GET", "/306", 306 },
62 { { { "GET", "/308", 308 },
64 { { { "HEAD", "/300", 300 },
66 { { { "HEAD", "/304", 304 },
68 { { { "HEAD", "/305", 305 },
70 { { { "HEAD", "/306", 306 },
72 { { { "HEAD", "/308", 308 },
75 /* Test double-redirect */
77 { { { "GET", "/301/302", 301 },
78 { "GET", "/302", 302 },
79 { "GET", "/", 200 } }, 200 },
80 { { { "HEAD", "/301/302", 301 },
81 { "HEAD", "/302", 302 },
82 { "HEAD", "/", 200 } }, 200 },
84 /* POST should only automatically redirect on 301, 302 and 303 */
86 { { { "POST", "/301", 301 },
89 { { { "POST", "/302", 302 },
92 { { { "POST", "/303", 303 },
95 { { { "POST", "/307", 307 },
98 /* Test behavior with recoverably-bad Location header */
99 { { { "GET", "/bad", 302 },
100 { "GET", "/bad%20with%20spaces", 200 },
103 /* Test behavior with irrecoverably-bad Location header */
104 { { { "GET", "/bad-no-host", 302 },
105 { NULL } }, SOUP_STATUS_MALFORMED, 302 },
107 /* Test infinite redirection */
108 { { { "GET", "/bad-recursive", 302, TRUE },
109 { NULL } }, SOUP_STATUS_TOO_MANY_REDIRECTS },
111 /* Test redirection to a different server */
112 { { { "GET", "/server2", 302 },
113 { "GET", "/on-server2", 200 },
116 static const int n_tests = G_N_ELEMENTS (tests);
119 got_headers (SoupMessage *msg, gpointer user_data)
121 TestRequest **treq = user_data;
122 const char *location;
124 debug_printf (2, " -> %d %s\n", msg->status_code,
126 location = soup_message_headers_get_one (msg->response_headers,
129 debug_printf (2, " Location: %s\n", location);
131 if (!(*treq)->method)
134 if (msg->status_code != (*treq)->status_code) {
135 debug_printf (1, " - Expected %d !\n",
136 (*treq)->status_code);
142 restarted (SoupMessage *msg, gpointer user_data)
144 TestRequest **treq = user_data;
145 SoupURI *uri = soup_message_get_uri (msg);
147 debug_printf (2, " %s %s\n", msg->method, uri->path);
149 if ((*treq)->method && !(*treq)->repeat)
152 if (!(*treq)->method) {
153 debug_printf (1, " - Expected to be done!\n");
158 if (strcmp (msg->method, (*treq)->method) != 0) {
159 debug_printf (1, " - Expected %s !\n", (*treq)->method);
162 if (strcmp (uri->path, (*treq)->path) != 0) {
163 debug_printf (1, " - Expected %s !\n", (*treq)->path);
169 do_message_api_test (SoupSession *session, SoupURI *base_uri, int n)
175 debug_printf (1, "%2d. %s %s\n", n + 1,
176 tests[n].requests[0].method,
177 tests[n].requests[0].path);
179 uri = soup_uri_new_with_base (base_uri, tests[n].requests[0].path);
180 msg = soup_message_new_from_uri (tests[n].requests[0].method, uri);
183 if (msg->method == SOUP_METHOD_POST) {
184 soup_message_set_request (msg, "text/plain",
187 strlen ("post body"));
190 treq = &tests[n].requests[0];
191 g_signal_connect (msg, "got_headers",
192 G_CALLBACK (got_headers), &treq);
193 g_signal_connect (msg, "restarted",
194 G_CALLBACK (restarted), &treq);
196 soup_session_send_message (session, msg);
198 if (msg->status_code != tests[n].final_status) {
199 debug_printf (1, " - Expected final status of %d, got %d !\n",
200 tests[n].final_status, msg->status_code);
204 g_object_unref (msg);
205 debug_printf (2, "\n");
209 do_request_api_test (SoupSession *session, SoupURI *base_uri, int n)
211 SoupRequester *requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
216 GInputStream *stream;
217 GError *error = NULL;
220 debug_printf (1, "%2d. %s %s\n", n + 1,
221 tests[n].requests[0].method,
222 tests[n].requests[0].path);
224 final_status = tests[n].request_api_final_status;
226 final_status = tests[n].final_status;
228 uri = soup_uri_new_with_base (base_uri, tests[n].requests[0].path);
229 req = soup_requester_request_uri (requester, uri, &error);
232 debug_printf (1, " could not create request: %s\n",
234 g_error_free (error);
236 debug_printf (2, "\n");
240 msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
241 g_object_set (G_OBJECT (msg),
242 SOUP_MESSAGE_METHOD, tests[n].requests[0].method,
245 if (msg->method == SOUP_METHOD_POST) {
246 soup_message_set_request (msg, "text/plain",
249 strlen ("post body"));
252 treq = &tests[n].requests[0];
253 g_signal_connect (msg, "got_headers",
254 G_CALLBACK (got_headers), &treq);
255 g_signal_connect (msg, "restarted",
256 G_CALLBACK (restarted), &treq);
258 if (SOUP_IS_SESSION_SYNC (session))
259 stream = soup_request_send (req, NULL, &error);
261 stream = soup_test_request_send_async_as_sync (req, NULL, &error);
263 if (SOUP_STATUS_IS_TRANSPORT_ERROR (final_status)) {
265 debug_printf (1, " expected failure (%s) but succeeded",
266 soup_status_get_phrase (final_status));
268 g_object_unref (stream);
270 if (error->domain != SOUP_HTTP_ERROR ||
271 error->code != final_status) {
272 debug_printf (1, " expected '%s' but got '%s'",
273 soup_status_get_phrase (final_status),
278 g_error_free (error);
279 g_object_unref (req);
280 debug_printf (2, "\n");
282 } else if (!stream) {
283 debug_printf (1, " could not send request: %s\n",
285 g_error_free (error);
286 g_object_unref (req);
288 debug_printf (2, "\n");
292 if (SOUP_IS_SESSION_SYNC (session))
293 g_input_stream_close (stream, NULL, &error);
295 soup_test_stream_close_async_as_sync (stream, NULL, &error);
297 debug_printf (1, " could not close stream: %s\n",
299 g_error_free (error);
303 if (msg->status_code != final_status) {
304 debug_printf (1, " - Expected final status of %d, got %d !\n",
305 final_status, msg->status_code);
309 g_object_unref (req);
310 debug_printf (2, "\n");
314 do_redirect_tests (SoupURI *base_uri)
316 SoupSession *session;
319 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
320 SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
321 SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
323 debug_printf (1, "Async session, SoupMessage\n");
324 for (n = 0; n < n_tests; n++)
325 do_message_api_test (session, base_uri, n);
326 debug_printf (1, "\nAsync session, SoupRequest\n");
327 for (n = 0; n < n_tests; n++)
328 do_request_api_test (session, base_uri, n);
329 soup_test_session_abort_unref (session);
331 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC,
332 SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
334 debug_printf (1, "\nSync session, SoupMessage\n");
335 for (n = 0; n < n_tests; n++)
336 do_message_api_test (session, base_uri, n);
337 debug_printf (1, "\nSync session, SoupRequest\n");
338 for (n = 0; n < n_tests; n++)
339 do_request_api_test (session, base_uri, n);
340 soup_test_session_abort_unref (session);
344 SoupSession *session;
345 SoupMessage *msg1, *msg2;
346 SoupURI *uri1, *uri2;
347 SoupSocket *sock1, *sock2;
348 } ConnectionTestData;
351 msg2_finished (SoupSession *session, SoupMessage *msg2, gpointer user_data)
353 if (!SOUP_STATUS_IS_SUCCESSFUL (msg2->status_code)) {
354 debug_printf (1, " msg2 failed: %d %s\n",
355 msg2->status_code, msg2->reason_phrase);
361 unpause_msg1 (SoupMessage *msg2, gpointer user_data)
363 ConnectionTestData *data = user_data;
366 debug_printf (1, " msg1 has no connection?\n");
368 } else if (!data->sock2) {
369 debug_printf (1, " msg2 has no connection?\n");
371 } else if (data->sock1 == data->sock2) {
372 debug_printf (1, " Both messages sharing the same connection\n");
376 soup_session_unpause_message (data->session, data->msg1);
380 msg1_just_restarted (gpointer user_data)
382 ConnectionTestData *data = user_data;
384 soup_session_pause_message (data->session, data->msg1);
386 data->msg2 = soup_message_new_from_uri ("GET", data->uri2);
388 g_signal_connect (data->msg2, "got_body",
389 G_CALLBACK (unpause_msg1), data);
391 soup_session_queue_message (data->session, data->msg2, msg2_finished, data);
396 msg1_about_to_restart (SoupMessage *msg1, gpointer user_data)
398 ConnectionTestData *data = user_data;
400 /* Do nothing when loading the redirected-to resource */
401 if (!SOUP_STATUS_IS_REDIRECTION (data->msg1->status_code))
404 /* We have to pause msg1 after the I/O finishes, but before
405 * the queue runs again.
407 g_idle_add_full (G_PRIORITY_HIGH, msg1_just_restarted, data, NULL);
411 request_started (SoupSession *session, SoupMessage *msg,
412 SoupSocket *socket, gpointer user_data)
414 ConnectionTestData *data = user_data;
416 if (msg == data->msg1)
417 data->sock1 = socket;
418 else if (msg == data->msg2)
419 data->sock2 = socket;
421 g_warn_if_reached ();
425 do_connection_test (SoupURI *base_uri)
427 ConnectionTestData data;
429 debug_printf (1, "\nConnection reuse\n");
430 memset (&data, 0, sizeof (data));
432 data.session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
433 g_signal_connect (data.session, "request-started",
434 G_CALLBACK (request_started), &data);
436 data.uri1 = soup_uri_new_with_base (base_uri, "/301");
437 data.uri2 = soup_uri_new_with_base (base_uri, "/");
438 data.msg1 = soup_message_new_from_uri ("GET", data.uri1);
440 g_signal_connect (data.msg1, "got-body",
441 G_CALLBACK (msg1_about_to_restart), &data);
442 soup_session_send_message (data.session, data.msg1);
444 if (!SOUP_STATUS_IS_SUCCESSFUL (data.msg1->status_code)) {
445 debug_printf (1, " msg1 failed: %d %s\n",
446 data.msg1->status_code, data.msg1->reason_phrase);
449 g_object_unref (data.msg1);
450 soup_uri_free (data.uri1);
451 soup_uri_free (data.uri2);
453 soup_test_session_abort_unref (data.session);
457 server_callback (SoupServer *server, SoupMessage *msg,
458 const char *path, GHashTable *query,
459 SoupClientContext *context, gpointer data)
464 /* Make sure that a HTTP/1.0 redirect doesn't cause an
465 * HTTP/1.0 re-request. (#521848)
467 if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
468 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
472 if (g_str_has_prefix (path, "/bad")) {
473 if (!strcmp (path, "/bad")) {
474 soup_message_set_status (msg, SOUP_STATUS_FOUND);
475 soup_message_headers_replace (msg->response_headers,
478 } else if (!strcmp (path, "/bad-recursive")) {
479 soup_message_set_status (msg, SOUP_STATUS_FOUND);
480 soup_message_headers_replace (msg->response_headers,
483 } else if (!strcmp (path, "/bad-no-host")) {
484 soup_message_set_status (msg, SOUP_STATUS_FOUND);
485 soup_message_headers_replace (msg->response_headers,
488 } else if (!strcmp (path, "/bad with spaces"))
489 soup_message_set_status (msg, SOUP_STATUS_OK);
491 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
493 } else if (!strcmp (path, "/server2")) {
494 soup_message_set_status (msg, SOUP_STATUS_FOUND);
495 soup_message_headers_replace (msg->response_headers,
499 } else if (!strcmp (path, "/")) {
500 if (msg->method != SOUP_METHOD_GET &&
501 msg->method != SOUP_METHOD_HEAD) {
502 soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
506 /* Make sure that redirecting a POST clears the body */
507 if (msg->request_body->length) {
508 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
512 soup_message_set_status (msg, SOUP_STATUS_OK);
514 /* FIXME: this is wrong, though it doesn't matter for
515 * the purposes of this test, and to do the right
516 * thing currently we'd have to set Content-Length by
519 if (msg->method != SOUP_METHOD_HEAD) {
520 soup_message_set_response (msg, "text/plain",
527 status_code = strtoul (path + 1, &remainder, 10);
528 if (!SOUP_STATUS_IS_REDIRECTION (status_code) ||
529 (*remainder && *remainder != '/')) {
530 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
534 /* See above comment re bug 521848. We only test this on the
535 * double-redirects so that we get connection-reuse testing
536 * the rest of the time.
538 if (*remainder == '/')
539 soup_message_set_http_version (msg, SOUP_HTTP_1_0);
541 soup_message_set_redirect (msg, status_code,
542 *remainder ? remainder : "/");
546 server2_callback (SoupServer *server, SoupMessage *msg,
547 const char *path, GHashTable *query,
548 SoupClientContext *context, gpointer data)
550 soup_message_set_status (msg, SOUP_STATUS_OK);
553 static gboolean run_tests = TRUE;
555 static GOptionEntry no_test_entry[] = {
556 { "no-tests", 'n', G_OPTION_FLAG_REVERSE,
557 G_OPTION_ARG_NONE, &run_tests,
558 "Don't run tests, just run the test server", NULL },
563 main (int argc, char **argv)
566 SoupServer *server, *server2;
570 test_init (argc, argv, no_test_entry);
572 server = soup_test_server_new (TRUE);
573 soup_server_add_handler (server, NULL,
574 server_callback, NULL, NULL);
575 port = soup_server_get_port (server);
577 server2 = soup_test_server_new (TRUE);
578 soup_server_add_handler (server2, NULL,
579 server2_callback, NULL, NULL);
580 server2_uri = g_strdup_printf ("http://127.0.0.1:%d/on-server2",
581 soup_server_get_port (server2));
583 loop = g_main_loop_new (NULL, TRUE);
586 base_uri = soup_uri_new ("http://127.0.0.1");
587 soup_uri_set_port (base_uri, port);
588 do_redirect_tests (base_uri);
589 do_connection_test (base_uri);
590 soup_uri_free (base_uri);
592 g_print ("Listening on port %d\n", port);
593 g_main_loop_run (loop);
596 g_main_loop_unref (loop);
597 g_free (server2_uri);
598 soup_test_server_quit_unref (server);
599 soup_test_server_quit_unref (server2);