/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ #include "test-utils.h" SoupBuffer *full_response; int total_length; char *test_response; static void check_part (SoupMessageHeaders *headers, const char *body, gsize body_len, gboolean check_start_end, int expected_start, int expected_end) { goffset start, end, total_length; debug_printf (1, " Content-Range: %s\n", soup_message_headers_get_one (headers, "Content-Range")); if (!soup_message_headers_get_content_range (headers, &start, &end, &total_length)) { soup_test_assert (FALSE, "Could not find/parse Content-Range"); return; } if (total_length != full_response->length && total_length != -1) { soup_test_assert (FALSE, "Unexpected total length %" G_GINT64_FORMAT " in response\n", total_length); return; } if (check_start_end) { if ((expected_start >= 0 && start != expected_start) || (expected_start < 0 && start != full_response->length + expected_start)) { soup_test_assert (FALSE, "Unexpected range start %" G_GINT64_FORMAT " in response\n", start); return; } if ((expected_end >= 0 && end != expected_end) || (expected_end < 0 && end != full_response->length - 1)) { soup_test_assert (FALSE, "Unexpected range end %" G_GINT64_FORMAT " in response\n", end); return; } } if (end - start + 1 != body_len) { soup_test_assert (FALSE, "Range length (%d) does not match body length (%d)\n", (int)(end - start) + 1, (int)body_len); return; } memcpy (test_response + start, body, body_len); } static void do_single_range (SoupSession *session, SoupMessage *msg, int start, int end, gboolean succeed) { const char *content_type; debug_printf (1, " Range: %s\n", soup_message_headers_get_one (msg->request_headers, "Range")); soup_session_send_message (session, msg); if (!succeed) { soup_test_assert_message_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE); if (msg->status_code != SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) { const char *content_range; content_range = soup_message_headers_get_one (msg->response_headers, "Content-Range"); if (content_range) debug_printf (1, " Content-Range: %s\n", content_range); } g_object_unref (msg); return; } soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT); content_type = soup_message_headers_get_content_type ( msg->response_headers, NULL); g_assert_cmpstr (content_type, !=, "multipart/byteranges"); check_part (msg->response_headers, msg->response_body->data, msg->response_body->length, TRUE, start, end); g_object_unref (msg); } static void request_single_range (SoupSession *session, const char *uri, int start, int end, gboolean succeed) { SoupMessage *msg; msg = soup_message_new ("GET", uri); soup_message_headers_set_range (msg->request_headers, start, end); do_single_range (session, msg, start, end, succeed); } static void do_multi_range (SoupSession *session, SoupMessage *msg, int expected_return_ranges) { SoupMultipart *multipart; const char *content_type; int i, length; debug_printf (1, " Range: %s\n", soup_message_headers_get_one (msg->request_headers, "Range")); soup_session_send_message (session, msg); soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT); content_type = soup_message_headers_get_content_type (msg->response_headers, NULL); g_assert_cmpstr (content_type, ==, "multipart/byteranges"); multipart = soup_multipart_new_from_message (msg->response_headers, msg->response_body); if (!multipart) { soup_test_assert (FALSE, "Could not parse multipart"); g_object_unref (msg); return; } length = soup_multipart_get_length (multipart); g_assert_cmpint (length, ==, expected_return_ranges); for (i = 0; i < length; i++) { SoupMessageHeaders *headers; SoupBuffer *body; debug_printf (1, " Part %d\n", i + 1); soup_multipart_get_part (multipart, i, &headers, &body); check_part (headers, body->data, body->length, FALSE, 0, 0); } soup_multipart_free (multipart); g_object_unref (msg); } static void request_double_range (SoupSession *session, const char *uri, int first_start, int first_end, int second_start, int second_end, int expected_return_ranges) { SoupMessage *msg; SoupRange ranges[2]; msg = soup_message_new ("GET", uri); ranges[0].start = first_start; ranges[0].end = first_end; ranges[1].start = second_start; ranges[1].end = second_end; soup_message_headers_set_ranges (msg->request_headers, ranges, 2); if (expected_return_ranges == 1) { do_single_range (session, msg, MIN (first_start, second_start), MAX (first_end, second_end), TRUE); } else do_multi_range (session, msg, expected_return_ranges); } static void request_triple_range (SoupSession *session, const char *uri, int first_start, int first_end, int second_start, int second_end, int third_start, int third_end, int expected_return_ranges) { SoupMessage *msg; SoupRange ranges[3]; msg = soup_message_new ("GET", uri); ranges[0].start = first_start; ranges[0].end = first_end; ranges[1].start = second_start; ranges[1].end = second_end; ranges[2].start = third_start; ranges[2].end = third_end; soup_message_headers_set_ranges (msg->request_headers, ranges, 3); if (expected_return_ranges == 1) { do_single_range (session, msg, MIN (first_start, MIN (second_start, third_start)), MAX (first_end, MAX (second_end, third_end)), TRUE); } else do_multi_range (session, msg, expected_return_ranges); } static void request_semi_invalid_range (SoupSession *session, const char *uri, int first_good_start, int first_good_end, int bad_start, int bad_end, int second_good_start, int second_good_end) { SoupMessage *msg; SoupRange ranges[3]; msg = soup_message_new ("GET", uri); ranges[0].start = first_good_start; ranges[0].end = first_good_end; ranges[1].start = bad_start; ranges[1].end = bad_end; ranges[2].start = second_good_start; ranges[2].end = second_good_end; soup_message_headers_set_ranges (msg->request_headers, ranges, 3); do_multi_range (session, msg, 2); } static void do_range_test (SoupSession *session, const char *uri, gboolean expect_coalesce, gboolean expect_partial_coalesce) { int twelfths = full_response->length / 12; memset (test_response, 0, full_response->length); /* We divide the response into 12 ranges and request them * as follows: * * 0: A (first single request) * 1: D (2nd part of triple request) * 2: C (1st part of double request) * 3: D (1st part of triple request) * 4: F (trickier overlapping request) * 5: C (2nd part of double request) * 6: D (3rd part of triple request) * 7: E (overlapping request) * 8: E (overlapping request) * 9: F (trickier overlapping request) * 10: F (trickier overlapping request) * 11: B (second and third single requests) */ /* A: 0, simple request */ debug_printf (1, "Requesting %d-%d\n", 0 * twelfths, 1 * twelfths); request_single_range (session, uri, 0 * twelfths, 1 * twelfths, TRUE); /* B: 11, end-relative request. These two are mostly redundant * in terms of data coverage, but they may still catch * Range-header-generating bugs. */ debug_printf (1, "Requesting %d-\n", 11 * twelfths); request_single_range (session, uri, 11 * twelfths, -1, TRUE); debug_printf (1, "Requesting -%d\n", 1 * twelfths); request_single_range (session, uri, -1 * twelfths, -1, TRUE); /* C: 2 and 5 */ debug_printf (1, "Requesting %d-%d,%d-%d\n", 2 * twelfths, 3 * twelfths, 5 * twelfths, 6 * twelfths); request_double_range (session, uri, 2 * twelfths, 3 * twelfths, 5 * twelfths, 6 * twelfths, 2); /* D: 1, 3, 6 */ debug_printf (1, "Requesting %d-%d,%d-%d,%d-%d\n", 3 * twelfths, 4 * twelfths, 1 * twelfths, 2 * twelfths, 6 * twelfths, 7 * twelfths); request_triple_range (session, uri, 3 * twelfths, 4 * twelfths, 1 * twelfths, 2 * twelfths, 6 * twelfths, 7 * twelfths, 3); /* E: 7 and 8: should coalesce into a single response */ debug_printf (1, "Requesting %d-%d,%d-%d (can coalesce)\n", 7 * twelfths, 8 * twelfths, 8 * twelfths, 9 * twelfths); request_double_range (session, uri, 7 * twelfths, 8 * twelfths, 8 * twelfths, 9 * twelfths, expect_coalesce ? 1 : 2); /* F: 4, 9, 10: 9 and 10 should coalesce even though 4 was * requested between them. (Also, they actually overlap in * this case, as opposed to just touching.) */ debug_printf (1, "Requesting %d-%d,%d-%d,%d-%d (can partially coalesce)\n", 9 * twelfths, 10 * twelfths + 5, 4 * twelfths, 5 * twelfths, 10 * twelfths - 5, 11 * twelfths); request_triple_range (session, uri, 9 * twelfths, 10 * twelfths + 5, 4 * twelfths, 5 * twelfths, 10 * twelfths - 5, 11 * twelfths, expect_partial_coalesce ? 2 : 3); soup_assert_cmpmem (full_response->data, full_response->length, test_response, full_response->length); debug_printf (1, "Requesting (invalid) %d-%d\n", (int) full_response->length + 1, (int) full_response->length + 100); request_single_range (session, uri, full_response->length + 1, full_response->length + 100, FALSE); debug_printf (1, "Requesting (semi-invalid) 1-10,%d-%d,20-30\n", (int) full_response->length + 1, (int) full_response->length + 100); request_semi_invalid_range (session, uri, 1, 10, full_response->length + 1, full_response->length + 100, 20, 30); } static void do_apache_range_test (void) { SoupSession *session; SOUP_TEST_SKIP_IF_NO_APACHE; session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); do_range_test (session, "http://127.0.0.1:47524/", TRUE, FALSE); soup_test_session_abort_unref (session); } static void server_handler (SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query, SoupClientContext *client, gpointer user_data) { soup_message_set_status (msg, SOUP_STATUS_OK); soup_message_body_append_buffer (msg->response_body, full_response); } static void do_libsoup_range_test (void) { SoupSession *session; SoupServer *server; SoupURI *base_uri; char *base_uri_str; session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); server = soup_test_server_new (SOUP_TEST_SERVER_DEFAULT); soup_server_add_handler (server, NULL, server_handler, NULL, NULL); base_uri = soup_test_server_get_uri (server, "http", NULL); base_uri_str = soup_uri_to_string (base_uri, FALSE); do_range_test (session, base_uri_str, TRUE, TRUE); soup_uri_free (base_uri); g_free (base_uri_str); soup_test_server_quit_unref (server); soup_test_session_abort_unref (session); } int main (int argc, char **argv) { int ret; test_init (argc, argv, NULL); apache_init (); full_response = soup_test_get_index (); test_response = g_malloc0 (full_response->length); g_test_add_func ("/ranges/apache", do_apache_range_test); g_test_add_func ("/ranges/libsoup", do_libsoup_range_test); ret = g_test_run (); g_free (test_response); test_cleanup (); return ret; }