1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2008 Red Hat, Inc.
14 #define LIBSOUP_USE_UNSTABLE_REQUEST_API
15 #include <libsoup/soup.h>
16 #include <libsoup/soup-requester.h>
17 #include <libsoup/soup-request-http.h>
19 #include "test-utils.h"
31 TestRequest requests[3];
33 guint request_api_final_status;
35 /* A redirecty response to a GET or HEAD should cause a redirect */
37 { { { "GET", "/301", 301 },
40 { { { "GET", "/302", 302 },
43 { { { "GET", "/303", 303 },
46 { { { "GET", "/307", 307 },
49 { { { "HEAD", "/301", 301 },
52 { { { "HEAD", "/302", 302 },
55 /* 303 is a nonsensical response to HEAD, but some sites do
58 { { { "HEAD", "/303", 303 },
61 { { { "HEAD", "/307", 307 },
65 /* A non-redirecty response to a GET or HEAD should not */
67 { { { "GET", "/300", 300 },
69 { { { "GET", "/304", 304 },
71 { { { "GET", "/305", 305 },
73 { { { "GET", "/306", 306 },
75 { { { "GET", "/308", 308 },
77 { { { "HEAD", "/300", 300 },
79 { { { "HEAD", "/304", 304 },
81 { { { "HEAD", "/305", 305 },
83 { { { "HEAD", "/306", 306 },
85 { { { "HEAD", "/308", 308 },
88 /* Test double-redirect */
90 { { { "GET", "/301/302", 301 },
91 { "GET", "/302", 302 },
92 { "GET", "/", 200 } }, 200 },
93 { { { "HEAD", "/301/302", 301 },
94 { "HEAD", "/302", 302 },
95 { "HEAD", "/", 200 } }, 200 },
97 /* POST should only automatically redirect on 301, 302 and 303 */
99 { { { "POST", "/301", 301 },
102 { { { "POST", "/302", 302 },
105 { { { "POST", "/303", 303 },
108 { { { "POST", "/307", 307 },
111 /* Test behavior with recoverably-bad Location header */
112 { { { "GET", "/bad", 302 },
113 { "GET", "/bad%20with%20spaces", 200 },
116 /* Test behavior with irrecoverably-bad Location header */
117 { { { "GET", "/bad-no-host", 302 },
118 { NULL } }, SOUP_STATUS_MALFORMED, 302 },
120 /* Test infinite redirection */
121 { { { "GET", "/bad-recursive", 302, TRUE },
122 { NULL } }, SOUP_STATUS_TOO_MANY_REDIRECTS },
124 /* Test redirection to a different server */
125 { { { "GET", "/server2", 302 },
126 { "GET", "/on-server2", 200 },
129 static const int n_tests = G_N_ELEMENTS (tests);
132 got_headers (SoupMessage *msg, gpointer user_data)
134 TestRequest **treq = user_data;
135 const char *location;
137 debug_printf (2, " -> %d %s\n", msg->status_code,
139 location = soup_message_headers_get_one (msg->response_headers,
142 debug_printf (2, " Location: %s\n", location);
144 if (!(*treq)->method)
147 if (msg->status_code != (*treq)->status_code) {
148 debug_printf (1, " - Expected %d !\n",
149 (*treq)->status_code);
155 restarted (SoupMessage *msg, gpointer user_data)
157 TestRequest **treq = user_data;
158 SoupURI *uri = soup_message_get_uri (msg);
160 debug_printf (2, " %s %s\n", msg->method, uri->path);
162 if ((*treq)->method && !(*treq)->repeat)
165 if (!(*treq)->method) {
166 debug_printf (1, " - Expected to be done!\n");
171 if (strcmp (msg->method, (*treq)->method) != 0) {
172 debug_printf (1, " - Expected %s !\n", (*treq)->method);
175 if (strcmp (uri->path, (*treq)->path) != 0) {
176 debug_printf (1, " - Expected %s !\n", (*treq)->path);
182 do_message_api_test (SoupSession *session, SoupURI *base_uri, int n)
188 debug_printf (1, "%2d. %s %s\n", n + 1,
189 tests[n].requests[0].method,
190 tests[n].requests[0].path);
192 uri = soup_uri_new_with_base (base_uri, tests[n].requests[0].path);
193 msg = soup_message_new_from_uri (tests[n].requests[0].method, uri);
196 if (msg->method == SOUP_METHOD_POST) {
197 soup_message_set_request (msg, "text/plain",
200 strlen ("post body"));
203 treq = &tests[n].requests[0];
204 g_signal_connect (msg, "got_headers",
205 G_CALLBACK (got_headers), &treq);
206 g_signal_connect (msg, "restarted",
207 G_CALLBACK (restarted), &treq);
209 soup_session_send_message (session, msg);
211 if (msg->status_code != tests[n].final_status) {
212 debug_printf (1, " - Expected final status of %d, got %d !\n",
213 tests[n].final_status, msg->status_code);
217 g_object_unref (msg);
218 debug_printf (2, "\n");
222 async_callback (GObject *source, GAsyncResult *result, gpointer user_data)
224 GAsyncResult **result_p = user_data;
226 *result_p = g_object_ref (result);
230 do_request_api_test (SoupSession *session, SoupURI *base_uri, int n)
232 SoupRequester *requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
237 GInputStream *stream;
238 GAsyncResult *result;
239 GError *error = NULL;
242 debug_printf (1, "%2d. %s %s\n", n + 1,
243 tests[n].requests[0].method,
244 tests[n].requests[0].path);
246 final_status = tests[n].request_api_final_status;
248 final_status = tests[n].final_status;
250 uri = soup_uri_new_with_base (base_uri, tests[n].requests[0].path);
251 req = soup_requester_request_uri (requester, uri, &error);
254 debug_printf (1, " could not create request: %s\n",
256 g_error_free (error);
258 debug_printf (2, "\n");
262 msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
263 g_object_set (G_OBJECT (msg),
264 SOUP_MESSAGE_METHOD, tests[n].requests[0].method,
267 if (msg->method == SOUP_METHOD_POST) {
268 soup_message_set_request (msg, "text/plain",
271 strlen ("post body"));
274 treq = &tests[n].requests[0];
275 g_signal_connect (msg, "got_headers",
276 G_CALLBACK (got_headers), &treq);
277 g_signal_connect (msg, "restarted",
278 G_CALLBACK (restarted), &treq);
280 if (SOUP_IS_SESSION_SYNC (session)) {
281 stream = soup_request_send (req, NULL, &error);
284 soup_request_send_async (req, NULL, async_callback, &result);
286 g_main_context_iteration (NULL, TRUE);
287 stream = soup_request_send_finish (req, result, &error);
288 g_object_unref (result);
291 if (SOUP_STATUS_IS_TRANSPORT_ERROR (final_status)) {
293 debug_printf (1, " expected failure (%s) but succeeded",
294 soup_status_get_phrase (final_status));
296 g_object_unref (stream);
298 if (error->domain != SOUP_HTTP_ERROR ||
299 error->code != final_status) {
300 debug_printf (1, " expected '%s' but got '%s'",
301 soup_status_get_phrase (final_status),
306 g_error_free (error);
307 g_object_unref (req);
308 debug_printf (2, "\n");
310 } else if (!stream) {
311 debug_printf (1, " could not send request: %s\n",
313 g_error_free (error);
314 g_object_unref (req);
316 debug_printf (2, "\n");
320 if (SOUP_IS_SESSION_SYNC (session)) {
321 g_input_stream_close (stream, NULL, &error);
324 g_input_stream_close_async (stream, G_PRIORITY_DEFAULT,
325 NULL, async_callback, &result);
327 g_main_context_iteration (NULL, TRUE);
328 g_input_stream_close_finish (stream, result, &error);
329 g_object_unref (result);
332 debug_printf (1, " could not close stream: %s\n",
334 g_error_free (error);
338 if (msg->status_code != final_status) {
339 debug_printf (1, " - Expected final status of %d, got %d !\n",
340 final_status, msg->status_code);
344 g_object_unref (req);
345 debug_printf (2, "\n");
349 do_redirect_tests (SoupURI *base_uri)
351 SoupSession *session;
354 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
355 SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
356 SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
358 debug_printf (1, "Async session, SoupMessage\n");
359 for (n = 0; n < n_tests; n++)
360 do_message_api_test (session, base_uri, n);
361 debug_printf (1, "\nAsync session, SoupRequest\n");
362 for (n = 0; n < n_tests; n++)
363 do_request_api_test (session, base_uri, n);
364 soup_test_session_abort_unref (session);
366 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC,
367 SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
369 debug_printf (1, "\nSync session, SoupMessage\n");
370 for (n = 0; n < n_tests; n++)
371 do_message_api_test (session, base_uri, n);
372 debug_printf (1, "\nSync session, SoupRequest\n");
373 for (n = 0; n < n_tests; n++)
374 do_request_api_test (session, base_uri, n);
375 soup_test_session_abort_unref (session);
379 SoupSession *session;
380 SoupMessage *msg1, *msg2;
381 SoupURI *uri1, *uri2;
382 SoupSocket *sock1, *sock2;
383 } ConnectionTestData;
386 msg2_finished (SoupSession *session, SoupMessage *msg2, gpointer user_data)
388 if (!SOUP_STATUS_IS_SUCCESSFUL (msg2->status_code)) {
389 debug_printf (1, " msg2 failed: %d %s\n",
390 msg2->status_code, msg2->reason_phrase);
396 unpause_msg1 (SoupMessage *msg2, gpointer user_data)
398 ConnectionTestData *data = user_data;
401 debug_printf (1, " msg1 has no connection?\n");
403 } else if (!data->sock2) {
404 debug_printf (1, " msg2 has no connection?\n");
406 } else if (data->sock1 == data->sock2) {
407 debug_printf (1, " Both messages sharing the same connection\n");
411 soup_session_unpause_message (data->session, data->msg1);
415 msg1_just_restarted (gpointer user_data)
417 ConnectionTestData *data = user_data;
419 soup_session_pause_message (data->session, data->msg1);
421 data->msg2 = soup_message_new_from_uri ("GET", data->uri2);
423 g_signal_connect (data->msg2, "got_body",
424 G_CALLBACK (unpause_msg1), data);
426 soup_session_queue_message (data->session, data->msg2, msg2_finished, data);
431 msg1_about_to_restart (SoupMessage *msg1, gpointer user_data)
433 ConnectionTestData *data = user_data;
435 /* Do nothing when loading the redirected-to resource */
436 if (!SOUP_STATUS_IS_REDIRECTION (data->msg1->status_code))
439 /* We have to pause msg1 after the I/O finishes, but before
440 * the queue runs again.
442 g_idle_add_full (G_PRIORITY_HIGH, msg1_just_restarted, data, NULL);
446 request_started (SoupSession *session, SoupMessage *msg,
447 SoupSocket *socket, gpointer user_data)
449 ConnectionTestData *data = user_data;
451 if (msg == data->msg1)
452 data->sock1 = socket;
453 else if (msg == data->msg2)
454 data->sock2 = socket;
456 g_warn_if_reached ();
460 do_connection_test (SoupURI *base_uri)
462 ConnectionTestData data;
464 debug_printf (1, "\nConnection reuse\n");
465 memset (&data, 0, sizeof (data));
467 data.session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
468 g_signal_connect (data.session, "request-started",
469 G_CALLBACK (request_started), &data);
471 data.uri1 = soup_uri_new_with_base (base_uri, "/301");
472 data.uri2 = soup_uri_new_with_base (base_uri, "/");
473 data.msg1 = soup_message_new_from_uri ("GET", data.uri1);
475 g_signal_connect (data.msg1, "got-body",
476 G_CALLBACK (msg1_about_to_restart), &data);
477 soup_session_send_message (data.session, data.msg1);
479 if (!SOUP_STATUS_IS_SUCCESSFUL (data.msg1->status_code)) {
480 debug_printf (1, " msg1 failed: %d %s\n",
481 data.msg1->status_code, data.msg1->reason_phrase);
484 g_object_unref (data.msg1);
485 soup_uri_free (data.uri1);
486 soup_uri_free (data.uri2);
488 soup_test_session_abort_unref (data.session);
492 server_callback (SoupServer *server, SoupMessage *msg,
493 const char *path, GHashTable *query,
494 SoupClientContext *context, gpointer data)
499 /* Make sure that a HTTP/1.0 redirect doesn't cause an
500 * HTTP/1.0 re-request. (#521848)
502 if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
503 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
507 if (g_str_has_prefix (path, "/bad")) {
508 if (!strcmp (path, "/bad")) {
509 soup_message_set_status (msg, SOUP_STATUS_FOUND);
510 soup_message_headers_replace (msg->response_headers,
513 } else if (!strcmp (path, "/bad-recursive")) {
514 soup_message_set_status (msg, SOUP_STATUS_FOUND);
515 soup_message_headers_replace (msg->response_headers,
518 } else if (!strcmp (path, "/bad-no-host")) {
519 soup_message_set_status (msg, SOUP_STATUS_FOUND);
520 soup_message_headers_replace (msg->response_headers,
523 } else if (!strcmp (path, "/bad with spaces"))
524 soup_message_set_status (msg, SOUP_STATUS_OK);
526 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
528 } else if (!strcmp (path, "/server2")) {
529 soup_message_set_status (msg, SOUP_STATUS_FOUND);
530 soup_message_headers_replace (msg->response_headers,
534 } else if (!strcmp (path, "/")) {
535 if (msg->method != SOUP_METHOD_GET &&
536 msg->method != SOUP_METHOD_HEAD) {
537 soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
541 /* Make sure that redirecting a POST clears the body */
542 if (msg->request_body->length) {
543 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
547 soup_message_set_status (msg, SOUP_STATUS_OK);
549 /* FIXME: this is wrong, though it doesn't matter for
550 * the purposes of this test, and to do the right
551 * thing currently we'd have to set Content-Length by
554 if (msg->method != SOUP_METHOD_HEAD) {
555 soup_message_set_response (msg, "text/plain",
562 status_code = strtoul (path + 1, &remainder, 10);
563 if (!SOUP_STATUS_IS_REDIRECTION (status_code) ||
564 (*remainder && *remainder != '/')) {
565 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
569 /* See above comment re bug 521848. We only test this on the
570 * double-redirects so that we get connection-reuse testing
571 * the rest of the time.
573 if (*remainder == '/')
574 soup_message_set_http_version (msg, SOUP_HTTP_1_0);
576 soup_message_set_redirect (msg, status_code,
577 *remainder ? remainder : "/");
581 server2_callback (SoupServer *server, SoupMessage *msg,
582 const char *path, GHashTable *query,
583 SoupClientContext *context, gpointer data)
585 soup_message_set_status (msg, SOUP_STATUS_OK);
588 static gboolean run_tests = TRUE;
590 static GOptionEntry no_test_entry[] = {
591 { "no-tests", 'n', G_OPTION_FLAG_REVERSE,
592 G_OPTION_ARG_NONE, &run_tests,
593 "Don't run tests, just run the test server", NULL },
598 main (int argc, char **argv)
601 SoupServer *server, *server2;
605 test_init (argc, argv, no_test_entry);
607 server = soup_test_server_new (TRUE);
608 soup_server_add_handler (server, NULL,
609 server_callback, NULL, NULL);
610 port = soup_server_get_port (server);
612 server2 = soup_test_server_new (TRUE);
613 soup_server_add_handler (server2, NULL,
614 server2_callback, NULL, NULL);
615 server2_uri = g_strdup_printf ("http://127.0.0.1:%d/on-server2",
616 soup_server_get_port (server2));
618 loop = g_main_loop_new (NULL, TRUE);
621 base_uri = soup_uri_new ("http://127.0.0.1");
622 soup_uri_set_port (base_uri, port);
623 do_redirect_tests (base_uri);
624 do_connection_test (base_uri);
625 soup_uri_free (base_uri);
627 printf ("Listening on port %d\n", port);
628 g_main_loop_run (loop);
631 g_main_loop_unref (loop);
632 g_free (server2_uri);
633 soup_test_server_quit_unref (server);
634 soup_test_server_quit_unref (server2);