• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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