/* -*- 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;
}