1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 #include "test-utils.h"
4
5 SoupBuffer *full_response;
6 int total_length;
7 char *test_response;
8
9 static void
check_part(SoupMessageHeaders * headers,const char * body,gsize body_len,gboolean check_start_end,int expected_start,int expected_end)10 check_part (SoupMessageHeaders *headers, const char *body, gsize body_len,
11 gboolean check_start_end, int expected_start, int expected_end)
12 {
13 goffset start, end, total_length;
14
15 debug_printf (1, " Content-Range: %s\n",
16 soup_message_headers_get_one (headers, "Content-Range"));
17
18 if (!soup_message_headers_get_content_range (headers, &start, &end, &total_length)) {
19 soup_test_assert (FALSE, "Could not find/parse Content-Range");
20 return;
21 }
22
23 if (total_length != full_response->length && total_length != -1) {
24 soup_test_assert (FALSE,
25 "Unexpected total length %" G_GINT64_FORMAT " in response\n",
26 total_length);
27 return;
28 }
29
30 if (check_start_end) {
31 if ((expected_start >= 0 && start != expected_start) ||
32 (expected_start < 0 && start != full_response->length + expected_start)) {
33 soup_test_assert (FALSE,
34 "Unexpected range start %" G_GINT64_FORMAT " in response\n",
35 start);
36 return;
37 }
38
39 if ((expected_end >= 0 && end != expected_end) ||
40 (expected_end < 0 && end != full_response->length - 1)) {
41 soup_test_assert (FALSE,
42 "Unexpected range end %" G_GINT64_FORMAT " in response\n",
43 end);
44 return;
45 }
46 }
47
48 if (end - start + 1 != body_len) {
49 soup_test_assert (FALSE, "Range length (%d) does not match body length (%d)\n",
50 (int)(end - start) + 1,
51 (int)body_len);
52 return;
53 }
54
55 memcpy (test_response + start, body, body_len);
56 }
57
58 static void
do_single_range(SoupSession * session,SoupMessage * msg,int start,int end,gboolean succeed)59 do_single_range (SoupSession *session, SoupMessage *msg,
60 int start, int end, gboolean succeed)
61 {
62 const char *content_type;
63
64 debug_printf (1, " Range: %s\n",
65 soup_message_headers_get_one (msg->request_headers, "Range"));
66
67 soup_session_send_message (session, msg);
68
69 if (!succeed) {
70 soup_test_assert_message_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
71 if (msg->status_code != SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) {
72 const char *content_range;
73
74 content_range = soup_message_headers_get_one (msg->response_headers,
75 "Content-Range");
76 if (content_range)
77 debug_printf (1, " Content-Range: %s\n", content_range);
78 }
79
80 g_object_unref (msg);
81 return;
82 }
83
84 soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
85
86 content_type = soup_message_headers_get_content_type (
87 msg->response_headers, NULL);
88 g_assert_cmpstr (content_type, !=, "multipart/byteranges");
89
90 check_part (msg->response_headers, msg->response_body->data,
91 msg->response_body->length, TRUE, start, end);
92 g_object_unref (msg);
93 }
94
95 static void
request_single_range(SoupSession * session,const char * uri,int start,int end,gboolean succeed)96 request_single_range (SoupSession *session, const char *uri,
97 int start, int end, gboolean succeed)
98 {
99 SoupMessage *msg;
100
101 msg = soup_message_new ("GET", uri);
102 soup_message_headers_set_range (msg->request_headers, start, end);
103 do_single_range (session, msg, start, end, succeed);
104 }
105
106 static void
do_multi_range(SoupSession * session,SoupMessage * msg,int expected_return_ranges)107 do_multi_range (SoupSession *session, SoupMessage *msg,
108 int expected_return_ranges)
109 {
110 SoupMultipart *multipart;
111 const char *content_type;
112 int i, length;
113
114 debug_printf (1, " Range: %s\n",
115 soup_message_headers_get_one (msg->request_headers, "Range"));
116
117 soup_session_send_message (session, msg);
118
119 soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
120
121 content_type = soup_message_headers_get_content_type (msg->response_headers, NULL);
122 g_assert_cmpstr (content_type, ==, "multipart/byteranges");
123
124 multipart = soup_multipart_new_from_message (msg->response_headers,
125 msg->response_body);
126 if (!multipart) {
127 soup_test_assert (FALSE, "Could not parse multipart");
128 g_object_unref (msg);
129 return;
130 }
131
132 length = soup_multipart_get_length (multipart);
133 g_assert_cmpint (length, ==, expected_return_ranges);
134
135 for (i = 0; i < length; i++) {
136 SoupMessageHeaders *headers;
137 SoupBuffer *body;
138
139 debug_printf (1, " Part %d\n", i + 1);
140 soup_multipart_get_part (multipart, i, &headers, &body);
141 check_part (headers, body->data, body->length, FALSE, 0, 0);
142 }
143
144 soup_multipart_free (multipart);
145 g_object_unref (msg);
146 }
147
148 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)149 request_double_range (SoupSession *session, const char *uri,
150 int first_start, int first_end,
151 int second_start, int second_end,
152 int expected_return_ranges)
153 {
154 SoupMessage *msg;
155 SoupRange ranges[2];
156
157 msg = soup_message_new ("GET", uri);
158 ranges[0].start = first_start;
159 ranges[0].end = first_end;
160 ranges[1].start = second_start;
161 ranges[1].end = second_end;
162 soup_message_headers_set_ranges (msg->request_headers, ranges, 2);
163
164 if (expected_return_ranges == 1) {
165 do_single_range (session, msg,
166 MIN (first_start, second_start),
167 MAX (first_end, second_end),
168 TRUE);
169 } else
170 do_multi_range (session, msg, expected_return_ranges);
171 }
172
173 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)174 request_triple_range (SoupSession *session, const char *uri,
175 int first_start, int first_end,
176 int second_start, int second_end,
177 int third_start, int third_end,
178 int expected_return_ranges)
179 {
180 SoupMessage *msg;
181 SoupRange ranges[3];
182
183 msg = soup_message_new ("GET", uri);
184 ranges[0].start = first_start;
185 ranges[0].end = first_end;
186 ranges[1].start = second_start;
187 ranges[1].end = second_end;
188 ranges[2].start = third_start;
189 ranges[2].end = third_end;
190 soup_message_headers_set_ranges (msg->request_headers, ranges, 3);
191
192 if (expected_return_ranges == 1) {
193 do_single_range (session, msg,
194 MIN (first_start, MIN (second_start, third_start)),
195 MAX (first_end, MAX (second_end, third_end)),
196 TRUE);
197 } else
198 do_multi_range (session, msg, expected_return_ranges);
199 }
200
201 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)202 request_semi_invalid_range (SoupSession *session, const char *uri,
203 int first_good_start, int first_good_end,
204 int bad_start, int bad_end,
205 int second_good_start, int second_good_end)
206 {
207 SoupMessage *msg;
208 SoupRange ranges[3];
209
210 msg = soup_message_new ("GET", uri);
211 ranges[0].start = first_good_start;
212 ranges[0].end = first_good_end;
213 ranges[1].start = bad_start;
214 ranges[1].end = bad_end;
215 ranges[2].start = second_good_start;
216 ranges[2].end = second_good_end;
217 soup_message_headers_set_ranges (msg->request_headers, ranges, 3);
218
219 do_multi_range (session, msg, 2);
220 }
221
222 static void
do_range_test(SoupSession * session,const char * uri,gboolean expect_coalesce,gboolean expect_partial_coalesce)223 do_range_test (SoupSession *session, const char *uri,
224 gboolean expect_coalesce, gboolean expect_partial_coalesce)
225 {
226 int twelfths = full_response->length / 12;
227
228 memset (test_response, 0, full_response->length);
229
230 /* We divide the response into 12 ranges and request them
231 * as follows:
232 *
233 * 0: A (first single request)
234 * 1: D (2nd part of triple request)
235 * 2: C (1st part of double request)
236 * 3: D (1st part of triple request)
237 * 4: F (trickier overlapping request)
238 * 5: C (2nd part of double request)
239 * 6: D (3rd part of triple request)
240 * 7: E (overlapping request)
241 * 8: E (overlapping request)
242 * 9: F (trickier overlapping request)
243 * 10: F (trickier overlapping request)
244 * 11: B (second and third single requests)
245 */
246
247 /* A: 0, simple request */
248 debug_printf (1, "Requesting %d-%d\n", 0 * twelfths, 1 * twelfths);
249 request_single_range (session, uri,
250 0 * twelfths, 1 * twelfths,
251 TRUE);
252
253 /* B: 11, end-relative request. These two are mostly redundant
254 * in terms of data coverage, but they may still catch
255 * Range-header-generating bugs.
256 */
257 debug_printf (1, "Requesting %d-\n", 11 * twelfths);
258 request_single_range (session, uri,
259 11 * twelfths, -1,
260 TRUE);
261 debug_printf (1, "Requesting -%d\n", 1 * twelfths);
262 request_single_range (session, uri,
263 -1 * twelfths, -1,
264 TRUE);
265
266 /* C: 2 and 5 */
267 debug_printf (1, "Requesting %d-%d,%d-%d\n",
268 2 * twelfths, 3 * twelfths,
269 5 * twelfths, 6 * twelfths);
270 request_double_range (session, uri,
271 2 * twelfths, 3 * twelfths,
272 5 * twelfths, 6 * twelfths,
273 2);
274
275 /* D: 1, 3, 6 */
276 debug_printf (1, "Requesting %d-%d,%d-%d,%d-%d\n",
277 3 * twelfths, 4 * twelfths,
278 1 * twelfths, 2 * twelfths,
279 6 * twelfths, 7 * twelfths);
280 request_triple_range (session, uri,
281 3 * twelfths, 4 * twelfths,
282 1 * twelfths, 2 * twelfths,
283 6 * twelfths, 7 * twelfths,
284 3);
285
286 /* E: 7 and 8: should coalesce into a single response */
287 debug_printf (1, "Requesting %d-%d,%d-%d (can coalesce)\n",
288 7 * twelfths, 8 * twelfths,
289 8 * twelfths, 9 * twelfths);
290 request_double_range (session, uri,
291 7 * twelfths, 8 * twelfths,
292 8 * twelfths, 9 * twelfths,
293 expect_coalesce ? 1 : 2);
294
295 /* F: 4, 9, 10: 9 and 10 should coalesce even though 4 was
296 * requested between them. (Also, they actually overlap in
297 * this case, as opposed to just touching.)
298 */
299 debug_printf (1, "Requesting %d-%d,%d-%d,%d-%d (can partially coalesce)\n",
300 9 * twelfths, 10 * twelfths + 5,
301 4 * twelfths, 5 * twelfths,
302 10 * twelfths - 5, 11 * twelfths);
303 request_triple_range (session, uri,
304 9 * twelfths, 10 * twelfths + 5,
305 4 * twelfths, 5 * twelfths,
306 10 * twelfths - 5, 11 * twelfths,
307 expect_partial_coalesce ? 2 : 3);
308
309 soup_assert_cmpmem (full_response->data, full_response->length,
310 test_response, full_response->length);
311
312 debug_printf (1, "Requesting (invalid) %d-%d\n",
313 (int) full_response->length + 1,
314 (int) full_response->length + 100);
315 request_single_range (session, uri,
316 full_response->length + 1, full_response->length + 100,
317 FALSE);
318
319 debug_printf (1, "Requesting (semi-invalid) 1-10,%d-%d,20-30\n",
320 (int) full_response->length + 1,
321 (int) full_response->length + 100);
322 request_semi_invalid_range (session, uri,
323 1, 10,
324 full_response->length + 1, full_response->length + 100,
325 20, 30);
326 }
327
328 static void
do_apache_range_test(void)329 do_apache_range_test (void)
330 {
331 SoupSession *session;
332
333 SOUP_TEST_SKIP_IF_NO_APACHE;
334
335 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
336
337 do_range_test (session, "http://127.0.0.1:47524/", TRUE, FALSE);
338
339 soup_test_session_abort_unref (session);
340 }
341
342 static void
server_handler(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * client,gpointer user_data)343 server_handler (SoupServer *server,
344 SoupMessage *msg,
345 const char *path,
346 GHashTable *query,
347 SoupClientContext *client,
348 gpointer user_data)
349 {
350 soup_message_set_status (msg, SOUP_STATUS_OK);
351 soup_message_body_append_buffer (msg->response_body,
352 full_response);
353 }
354
355 static void
do_libsoup_range_test(void)356 do_libsoup_range_test (void)
357 {
358 SoupSession *session;
359 SoupServer *server;
360 SoupURI *base_uri;
361 char *base_uri_str;
362
363 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
364
365 server = soup_test_server_new (SOUP_TEST_SERVER_DEFAULT);
366 soup_server_add_handler (server, NULL, server_handler, NULL, NULL);
367 base_uri = soup_test_server_get_uri (server, "http", NULL);
368 base_uri_str = soup_uri_to_string (base_uri, FALSE);
369 do_range_test (session, base_uri_str, TRUE, TRUE);
370 soup_uri_free (base_uri);
371 g_free (base_uri_str);
372 soup_test_server_quit_unref (server);
373
374 soup_test_session_abort_unref (session);
375 }
376
377 int
main(int argc,char ** argv)378 main (int argc, char **argv)
379 {
380 int ret;
381
382 test_init (argc, argv, NULL);
383 apache_init ();
384
385 full_response = soup_test_get_index ();
386 test_response = g_malloc0 (full_response->length);
387
388 g_test_add_func ("/ranges/apache", do_apache_range_test);
389 g_test_add_func ("/ranges/libsoup", do_libsoup_range_test);
390
391 ret = g_test_run ();
392
393 g_free (test_response);
394
395 test_cleanup ();
396 return ret;
397 }
398