• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2008 Red Hat, Inc.
4  */
5 
6 #include "test-utils.h"
7 
8 static SoupSession *session;
9 static SoupURI *base_uri;
10 
11 typedef struct {
12 	SoupSession *session;
13 	SoupBuffer *chunks[3];
14 	int next, nwrote, nfreed;
15 	gboolean streaming;
16 } PutTestData;
17 
18 static void
write_next_chunk(SoupMessage * msg,gpointer user_data)19 write_next_chunk (SoupMessage *msg, gpointer user_data)
20 {
21 	PutTestData *ptd = user_data;
22 
23 	debug_printf (2, "  writing chunk %d\n", ptd->next);
24 
25 	if (ptd->streaming && ptd->next > 0) {
26 		soup_test_assert (ptd->chunks[ptd->next - 1] == NULL,
27 				  "next chunk requested before last one freed");
28 	}
29 
30 	if (ptd->next < G_N_ELEMENTS (ptd->chunks)) {
31 		soup_message_body_append_buffer (msg->request_body,
32 						 ptd->chunks[ptd->next]);
33 		soup_buffer_free (ptd->chunks[ptd->next]);
34 		ptd->next++;
35 	} else
36 		soup_message_body_complete (msg->request_body);
37 	soup_session_unpause_message (ptd->session, msg);
38 }
39 
40 /* This is not a supported part of the API. Use SOUP_MESSAGE_CAN_REBUILD
41  * instead.
42  */
43 static void
write_next_chunk_streaming_hack(SoupMessage * msg,gpointer user_data)44 write_next_chunk_streaming_hack (SoupMessage *msg, gpointer user_data)
45 {
46 	PutTestData *ptd = user_data;
47 	SoupBuffer *chunk;
48 
49 	debug_printf (2, "  freeing chunk at %d\n", ptd->nfreed);
50 	chunk = soup_message_body_get_chunk (msg->request_body, ptd->nfreed);
51 	if (chunk) {
52 		ptd->nfreed += chunk->length;
53 		soup_message_body_wrote_chunk (msg->request_body, chunk);
54 		soup_buffer_free (chunk);
55 	} else {
56 		soup_test_assert (chunk,
57 				  "written chunk does not exist");
58 	}
59 	write_next_chunk (msg, user_data);
60 }
61 
62 static void
wrote_body_data(SoupMessage * msg,SoupBuffer * chunk,gpointer user_data)63 wrote_body_data (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
64 {
65 	PutTestData *ptd = user_data;
66 
67 	debug_printf (2, "  wrote_body_data, %d bytes\n",
68 		      (int)chunk->length);
69 	ptd->nwrote += chunk->length;
70 }
71 
72 static void
clear_buffer_ptr(gpointer data)73 clear_buffer_ptr (gpointer data)
74 {
75 	SoupBuffer **buffer_ptr = data;
76 
77 	debug_printf (2, "  clearing chunk\n");
78 	if (*buffer_ptr) {
79 		(*buffer_ptr)->length = 0;
80 		g_free ((char *)(*buffer_ptr)->data);
81 		*buffer_ptr = NULL;
82 	} else {
83 		soup_test_assert (*buffer_ptr,
84 				  "chunk is already clear");
85 	}
86 }
87 
88 /* Put a chunk containing @text into *@buffer, set up so that it will
89  * clear out *@buffer when the chunk is freed, allowing us to make sure
90  * the set_accumulate(FALSE) is working.
91  */
92 static void
make_put_chunk(SoupBuffer ** buffer,const char * text)93 make_put_chunk (SoupBuffer **buffer, const char *text)
94 {
95 	*buffer = soup_buffer_new_with_owner (g_strdup (text), strlen (text),
96 					      buffer, clear_buffer_ptr);
97 }
98 
99 static void
setup_request_body(PutTestData * ptd)100 setup_request_body (PutTestData *ptd)
101 {
102 	make_put_chunk (&ptd->chunks[0], "one\r\n");
103 	make_put_chunk (&ptd->chunks[1], "two\r\n");
104 	make_put_chunk (&ptd->chunks[2], "three\r\n");
105 	ptd->next = ptd->nwrote = ptd->nfreed = 0;
106 }
107 
108 static void
restarted_streaming(SoupMessage * msg,gpointer user_data)109 restarted_streaming (SoupMessage *msg, gpointer user_data)
110 {
111 	PutTestData *ptd = user_data;
112 
113 	debug_printf (2, "  --restarting--\n");
114 
115 	/* We're streaming, and we had to restart. So the data need
116 	 * to be regenerated.
117 	 */
118 	setup_request_body (ptd);
119 
120 	/* The 302 redirect will turn it into a GET request and
121 	 * reset the body encoding back to "NONE". Fix that.
122 	 */
123 	soup_message_headers_set_encoding (msg->request_headers,
124 					   SOUP_ENCODING_CHUNKED);
125 	msg->method = SOUP_METHOD_PUT;
126 }
127 
128 static void
restarted_streaming_hack(SoupMessage * msg,gpointer user_data)129 restarted_streaming_hack (SoupMessage *msg, gpointer user_data)
130 {
131 	restarted_streaming (msg, user_data);
132 	soup_message_body_truncate (msg->request_body);
133 }
134 
135 typedef enum {
136 	HACKY_STREAMING  = (1 << 0),
137 	PROPER_STREAMING = (1 << 1),
138 	RESTART          = (1 << 2)
139 } RequestTestFlags;
140 
141 static void
do_request_test(gconstpointer data)142 do_request_test (gconstpointer data)
143 {
144 	RequestTestFlags flags = GPOINTER_TO_UINT (data);
145 	SoupURI *uri;
146 	PutTestData ptd;
147 	SoupMessage *msg;
148 	const char *client_md5, *server_md5;
149 	GChecksum *check;
150 	int i, length;
151 
152 	if (flags & RESTART)
153 		uri = soup_uri_new_with_base (base_uri, "/redirect");
154 	else
155 		uri = soup_uri_copy (base_uri);
156 
157 	ptd.session = session;
158 	setup_request_body (&ptd);
159 	ptd.streaming = flags & (HACKY_STREAMING | PROPER_STREAMING);
160 
161 	check = g_checksum_new (G_CHECKSUM_MD5);
162 	length = 0;
163 	for (i = 0; i < 3; i++) {
164 		g_checksum_update (check, (guchar *)ptd.chunks[i]->data,
165 				   ptd.chunks[i]->length);
166 		length += ptd.chunks[i]->length;
167 	}
168 	client_md5 = g_checksum_get_string (check);
169 
170 	msg = soup_message_new_from_uri ("PUT", uri);
171 	soup_message_headers_set_encoding (msg->request_headers, SOUP_ENCODING_CHUNKED);
172 	soup_message_body_set_accumulate (msg->request_body, FALSE);
173 	if (flags & HACKY_STREAMING) {
174 		g_signal_connect (msg, "wrote_chunk",
175 				  G_CALLBACK (write_next_chunk_streaming_hack), &ptd);
176 		if (flags & RESTART) {
177 			g_signal_connect (msg, "restarted",
178 					  G_CALLBACK (restarted_streaming_hack), &ptd);
179 		}
180 	} else {
181 		g_signal_connect (msg, "wrote_chunk",
182 				  G_CALLBACK (write_next_chunk), &ptd);
183 	}
184 
185 	if (flags & PROPER_STREAMING) {
186 		soup_message_set_flags (msg, SOUP_MESSAGE_CAN_REBUILD);
187 		if (flags & RESTART) {
188 			g_signal_connect (msg, "restarted",
189 					  G_CALLBACK (restarted_streaming), &ptd);
190 		}
191 	}
192 
193 	g_signal_connect (msg, "wrote_headers",
194 			  G_CALLBACK (write_next_chunk), &ptd);
195 	g_signal_connect (msg, "wrote_body_data",
196 			  G_CALLBACK (wrote_body_data), &ptd);
197 	soup_session_send_message (session, msg);
198 
199 	soup_test_assert_message_status (msg, SOUP_STATUS_CREATED);
200 	g_assert_null (msg->request_body->data);
201 	g_assert_cmpint (msg->request_body->length, ==, length);
202 	g_assert_cmpint (length, ==, ptd.nwrote);
203 
204 	server_md5 = soup_message_headers_get_one (msg->response_headers,
205 						   "Content-MD5");
206 	g_assert_cmpstr (client_md5, ==, server_md5);
207 
208 	g_object_unref (msg);
209 	g_checksum_free (check);
210 
211 	soup_uri_free (uri);
212 }
213 
214 typedef struct {
215 	SoupBuffer *current_chunk;
216 	GChecksum *check;
217 	int length;
218 } GetTestData;
219 
220 static SoupBuffer *
chunk_allocator(SoupMessage * msg,gsize max_len,gpointer user_data)221 chunk_allocator (SoupMessage *msg, gsize max_len, gpointer user_data)
222 {
223 	GetTestData *gtd = user_data;
224 
225 	debug_printf (2, "  allocating chunk\n");
226 
227 	soup_test_assert (gtd->current_chunk == NULL,
228 			  "error: next chunk allocated before last one freed");
229 	gtd->current_chunk = soup_buffer_new_with_owner (g_malloc (6), 6,
230 							 &gtd->current_chunk,
231 							 clear_buffer_ptr);
232 	return gtd->current_chunk;
233 }
234 
235 static void
got_chunk(SoupMessage * msg,SoupBuffer * chunk,gpointer user_data)236 got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
237 {
238 	GetTestData *gtd = user_data;
239 
240 	debug_printf (2, "  got chunk, %d bytes\n",
241 		      (int)chunk->length);
242 	if (chunk != gtd->current_chunk) {
243 		debug_printf (1, "chunk mismatch! %p vs %p\n",
244 			      chunk, gtd->current_chunk);
245 	}
246 
247 	g_checksum_update (gtd->check, (guchar *)chunk->data, chunk->length);
248 	gtd->length += chunk->length;
249 }
250 
251 static void
do_response_test(void)252 do_response_test (void)
253 {
254 	GetTestData gtd;
255 	SoupMessage *msg;
256 	const char *client_md5, *server_md5;
257 
258 	gtd.current_chunk = NULL;
259 	gtd.length = 0;
260 	gtd.check = g_checksum_new (G_CHECKSUM_MD5);
261 
262 	msg = soup_message_new_from_uri ("GET", base_uri);
263 	soup_message_body_set_accumulate (msg->response_body, FALSE);
264 	G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
265 	soup_message_set_chunk_allocator (msg, chunk_allocator, &gtd, NULL);
266 	G_GNUC_END_IGNORE_DEPRECATIONS;
267 	g_signal_connect (msg, "got_chunk",
268 			  G_CALLBACK (got_chunk), &gtd);
269 	soup_session_send_message (session, msg);
270 
271 	soup_test_assert_message_status (msg, SOUP_STATUS_OK);
272 	g_assert_null (msg->response_body->data);
273 	g_assert_cmpint (soup_message_headers_get_content_length (msg->response_headers), ==, gtd.length);
274 
275 	client_md5 = g_checksum_get_string (gtd.check);
276 	server_md5 = soup_message_headers_get_one (msg->response_headers,
277 						   "Content-MD5");
278 	g_assert_cmpstr (client_md5, ==, server_md5);
279 
280 	g_object_unref (msg);
281 	g_checksum_free (gtd.check);
282 }
283 
284 /* Make sure TEMPORARY buffers are handled properly with non-accumulating
285  * message bodies.
286  */
287 
288 static void
temp_test_wrote_chunk(SoupMessage * msg,gpointer session)289 temp_test_wrote_chunk (SoupMessage *msg, gpointer session)
290 {
291 	SoupBuffer *chunk;
292 
293 	chunk = soup_message_body_get_chunk (msg->request_body, 5);
294 
295 	/* When the bug is present, the second chunk will also be
296 	 * discarded after the first is written, which will cause
297 	 * the I/O to stall since soup-message-io will think it's
298 	 * done, but it hasn't written Content-Length bytes yet.
299 	 */
300 	if (chunk)
301 		soup_buffer_free (chunk);
302 	else {
303 		soup_test_assert (chunk, "Lost second chunk");
304 		soup_session_abort (session);
305 	}
306 
307 	g_signal_handlers_disconnect_by_func (msg, temp_test_wrote_chunk, session);
308 }
309 
310 static void
do_temporary_test(void)311 do_temporary_test (void)
312 {
313 	SoupMessage *msg;
314 	char *client_md5;
315 	const char *server_md5;
316 
317 	g_test_bug_base ("https://bugs.webkit.org/");
318 	g_test_bug ("18343");
319 
320 	msg = soup_message_new_from_uri ("PUT", base_uri);
321 	soup_message_body_append (msg->request_body, SOUP_MEMORY_TEMPORARY,
322 				  "one\r\n", 5);
323 	soup_message_body_append (msg->request_body, SOUP_MEMORY_STATIC,
324 				  "two\r\n", 5);
325 	soup_message_body_set_accumulate (msg->request_body, FALSE);
326 
327 	client_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5,
328 						    "one\r\ntwo\r\n", 10);
329 	g_signal_connect (msg, "wrote_chunk",
330 			  G_CALLBACK (temp_test_wrote_chunk), session);
331 	soup_session_send_message (session, msg);
332 
333 	soup_test_assert_message_status (msg, SOUP_STATUS_CREATED);
334 
335 	server_md5 = soup_message_headers_get_one (msg->response_headers,
336 						   "Content-MD5");
337 	g_assert_cmpstr (client_md5, ==, server_md5);
338 
339 	g_free (client_md5);
340 	g_object_unref (msg);
341 }
342 
343 #define LARGE_CHUNK_SIZE 1000000
344 
345 typedef struct {
346 	SoupBuffer *buf;
347 	gsize offset;
348 } LargeChunkData;
349 
350 static void
large_wrote_body_data(SoupMessage * msg,SoupBuffer * chunk,gpointer user_data)351 large_wrote_body_data (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
352 {
353 	LargeChunkData *lcd = user_data;
354 
355 	soup_assert_cmpmem (chunk->data, chunk->length,
356 			    lcd->buf->data + lcd->offset,
357 			    chunk->length);
358 	lcd->offset += chunk->length;
359 }
360 
361 static void
do_large_chunk_test(void)362 do_large_chunk_test (void)
363 {
364 	SoupMessage *msg;
365 	char *buf_data;
366 	int i;
367 	LargeChunkData lcd;
368 
369 	msg = soup_message_new_from_uri ("PUT", base_uri);
370 
371 	buf_data = g_malloc0 (LARGE_CHUNK_SIZE);
372 	for (i = 0; i < LARGE_CHUNK_SIZE; i++)
373 		buf_data[i] = i & 0xFF;
374 	lcd.buf = soup_buffer_new (SOUP_MEMORY_TAKE, buf_data, LARGE_CHUNK_SIZE);
375 	lcd.offset = 0;
376 	soup_message_body_append_buffer (msg->request_body, lcd.buf);
377 	soup_message_body_set_accumulate (msg->request_body, FALSE);
378 
379 	g_signal_connect (msg, "wrote_body_data",
380 			  G_CALLBACK (large_wrote_body_data), &lcd);
381 	soup_session_send_message (session, msg);
382 
383 	soup_test_assert_message_status (msg, SOUP_STATUS_CREATED);
384 
385 	soup_buffer_free (lcd.buf);
386 	g_object_unref (msg);
387 }
388 
389 static void
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)390 server_callback (SoupServer *server, SoupMessage *msg,
391 		 const char *path, GHashTable *query,
392 		 SoupClientContext *context, gpointer data)
393 {
394 	SoupMessageBody *md5_body;
395 	char *md5;
396 
397 	if (g_str_has_prefix (path, "/redirect")) {
398 		soup_message_set_redirect (msg, SOUP_STATUS_FOUND, "/");
399 		return;
400 	}
401 
402 	if (msg->method == SOUP_METHOD_GET) {
403 		soup_message_set_response (msg, "text/plain",
404 					   SOUP_MEMORY_STATIC,
405 					   "three\r\ntwo\r\none\r\n",
406 					   strlen ("three\r\ntwo\r\none\r\n"));
407 		soup_buffer_free (soup_message_body_flatten (msg->response_body));
408 		md5_body = msg->response_body;
409 		soup_message_set_status (msg, SOUP_STATUS_OK);
410 	} else if (msg->method == SOUP_METHOD_PUT) {
411 		soup_message_set_status (msg, SOUP_STATUS_CREATED);
412 		md5_body = msg->request_body;
413 	} else {
414 		soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
415 		return;
416 	}
417 
418 	md5 = g_compute_checksum_for_data (G_CHECKSUM_MD5,
419 					   (guchar *)md5_body->data,
420 					   md5_body->length);
421 	soup_message_headers_append (msg->response_headers,
422 				     "Content-MD5", md5);
423 	g_free (md5);
424 }
425 
426 int
main(int argc,char ** argv)427 main (int argc, char **argv)
428 {
429 	GMainLoop *loop;
430 	SoupServer *server;
431 	int ret;
432 
433 	test_init (argc, argv, NULL);
434 
435 	server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
436 	soup_server_add_handler (server, NULL,
437 				 server_callback, NULL, NULL);
438 
439 	loop = g_main_loop_new (NULL, TRUE);
440 
441 	base_uri = soup_test_server_get_uri (server, "http", NULL);
442 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
443 
444 	g_test_add_data_func ("/chunks/request/unstreamed", GINT_TO_POINTER (0), do_request_test);
445 	g_test_add_data_func ("/chunks/request/proper-streaming", GINT_TO_POINTER (PROPER_STREAMING), do_request_test);
446 	g_test_add_data_func ("/chunks/request/proper-streaming/restart", GINT_TO_POINTER (PROPER_STREAMING | RESTART), do_request_test);
447 	g_test_add_data_func ("/chunks/request/hacky-streaming", GINT_TO_POINTER (HACKY_STREAMING), do_request_test);
448 	g_test_add_data_func ("/chunks/request/hacky-streaming/restart", GINT_TO_POINTER (HACKY_STREAMING | RESTART), do_request_test);
449 	g_test_add_func ("/chunks/response", do_response_test);
450 	g_test_add_func ("/chunks/temporary", do_temporary_test);
451 	g_test_add_func ("/chunks/large", do_large_chunk_test);
452 
453 	ret = g_test_run ();
454 
455 	soup_test_session_abort_unref (session);
456 
457 	soup_uri_free (base_uri);
458 
459 	g_main_loop_unref (loop);
460 	soup_test_server_quit_unref (server);
461 
462 	test_cleanup ();
463 	return ret;
464 }
465