• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2011 Collabora Ltd.
4  */
5 
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 
10 #include "test-utils.h"
11 
12 #ifndef G_OS_WIN32
13 #include <unistd.h>
14 #endif
15 
16 #define READ_BUFFER_SIZE 8192
17 
18 typedef enum {
19 	NO_MULTIPART,
20 	SYNC_MULTIPART,
21 	ASYNC_MULTIPART,
22 	ASYNC_MULTIPART_SMALL_READS
23 } MultipartMode;
24 
25 char *buffer;
26 SoupSession *session;
27 char *base_uri_string;
28 SoupURI *base_uri;
29 SoupMultipartInputStream *multipart;
30 unsigned passes;
31 
32 
33 /* This payload contains 4 different responses.
34  *
35  * First, a text/html response with a Content-Length (31);
36  * Second, a response lacking Content-Type with Content-Length (11);
37  * Third, a text/css response with no Content-Length;
38  * Fourth, same as the third, but with different content;
39  */
40 const char *payload = \
41 	"--cut-here\r\n" \
42 	"Content-Type: text/html\n"
43 	"Content-Length: 30\r\n" \
44 	"\r\n" \
45 	"<html><body>Hey!</body></html>" \
46 	"\r\n--cut-here\r\n" \
47 	"Content-Length: 10\r\n" \
48 	"\r\n" \
49 	"soup rocks" \
50 	"\r\n--cut-here\r\n" \
51 	"Content-Type: text/css\r\n" \
52 	"\r\n" \
53 	".soup { before: rocks; }" \
54 	"\r\n--cut-here\n" /* Tests boundary ending in a single \n. */ \
55 	"Content-Type: text/css\r\n" \
56 	"\r\n" \
57 	"#soup { background-color: black; }" \
58         "\r\n--cut-here--";
59 
60 static void
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)61 server_callback (SoupServer *server, SoupMessage *msg,
62 		 const char *path, GHashTable *query,
63 		 SoupClientContext *context, gpointer data)
64 {
65 	if (msg->method != SOUP_METHOD_GET) {
66 		soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
67 		return;
68 	}
69 
70 	soup_message_set_status (msg, SOUP_STATUS_OK);
71 
72 	soup_message_headers_append (msg->response_headers,
73 				     "Content-Type", "multipart/x-mixed-replace; boundary=cut-here");
74 
75 	soup_message_body_append (msg->response_body,
76 				  SOUP_MEMORY_STATIC,
77 				  payload,
78 				  strlen (payload));
79 
80 	soup_message_body_complete (msg->response_body);
81 }
82 
83 static void
content_sniffed(SoupMessage * msg,char * content_type,GHashTable * params,int * sniffed_count)84 content_sniffed (SoupMessage *msg, char *content_type, GHashTable *params, int *sniffed_count)
85 {
86 	*sniffed_count = *sniffed_count + 1;
87 	debug_printf (2, "  content-sniffed -> %s\n", content_type);
88 }
89 
90 static void
check_is_next(gboolean is_next)91 check_is_next (gboolean is_next)
92 {
93 	soup_test_assert (is_next,
94 			  "expected a header, but there are no more headers");
95 }
96 
97 static void
got_headers(SoupMessage * msg,int * headers_count)98 got_headers (SoupMessage *msg, int *headers_count)
99 {
100 	SoupMessageHeadersIter iter;
101 	gboolean is_next;
102 	const char* name, *value;
103 
104 	*headers_count = *headers_count + 1;
105 
106 	soup_message_headers_iter_init (&iter, msg->response_headers);
107 
108 	is_next = soup_message_headers_iter_next (&iter, &name, &value);
109 	check_is_next (is_next);
110 
111 	if (g_str_equal (name, "Date")) {
112 		is_next = soup_message_headers_iter_next (&iter, &name, &value);
113 		check_is_next (is_next);
114 	}
115 
116 	g_assert_cmpstr (name, ==, "Content-Type");
117 	g_assert_cmpstr (value, ==, "multipart/x-mixed-replace; boundary=cut-here");
118 }
119 
120 static void
read_cb(GObject * source,GAsyncResult * asyncResult,gpointer data)121 read_cb (GObject *source, GAsyncResult *asyncResult, gpointer data)
122 {
123 	GMainLoop *loop = (GMainLoop*)data;
124 	GInputStream *stream = G_INPUT_STREAM (source);
125 	GError *error = NULL;
126 	gssize bytes_read;
127 
128 	bytes_read = g_input_stream_read_finish (stream, asyncResult, &error);
129 	g_assert_no_error (error);
130 	if (error) {
131 		g_object_unref (stream);
132 		g_main_loop_quit (loop);
133 		return;
134 	}
135 
136 	if (!bytes_read) {
137 		g_input_stream_close (stream, NULL, &error);
138 		g_assert_no_error (error);
139 		g_object_unref (stream);
140 		g_main_loop_quit (loop);
141 		return;
142 	}
143 
144 	g_input_stream_read_async (stream, buffer, READ_BUFFER_SIZE,
145 				   G_PRIORITY_DEFAULT, NULL,
146 				   read_cb, data);
147 }
148 
149 static void
no_multipart_handling_cb(GObject * source,GAsyncResult * res,gpointer data)150 no_multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
151 {
152 	GMainLoop *loop = (GMainLoop*)data;
153 	SoupRequest *request = SOUP_REQUEST (source);
154 	GError *error = NULL;
155 	GInputStream* in;
156 
157 	in = soup_request_send_finish (request, res, &error);
158 	g_assert_no_error (error);
159 	if (error) {
160 		g_main_loop_quit (loop);
161 		return;
162 	}
163 
164 	g_input_stream_read_async (in, buffer, READ_BUFFER_SIZE,
165 				   G_PRIORITY_DEFAULT, NULL,
166 				   read_cb, data);
167 }
168 
169 static void
multipart_close_part_cb(GObject * source,GAsyncResult * res,gpointer data)170 multipart_close_part_cb (GObject *source, GAsyncResult *res, gpointer data)
171 {
172 	GInputStream *in = G_INPUT_STREAM (source);
173 	GError *error = NULL;
174 
175 	g_input_stream_close_finish (in, res, &error);
176 	g_assert_no_error (error);
177 }
178 
179 static void multipart_next_part_cb (GObject *source,
180 				    GAsyncResult *res,
181 				    gpointer data);
182 
183 static void
check_read(gsize nread,unsigned passes)184 check_read (gsize nread, unsigned passes)
185 {
186 	switch (passes) {
187 	case 0:
188 		g_assert_cmpint (nread, ==, 30);
189 		break;
190 	case 1:
191 		g_assert_cmpint (nread, ==, 10);
192 		break;
193 	case 2:
194 		g_assert_cmpint (nread, ==, 24);
195 		break;
196 	case 3:
197 		g_assert_cmpint (nread, ==, 34);
198 		break;
199 	default:
200 		soup_test_assert (FALSE, "unexpected read of size: %d", (int)nread);
201 		break;
202 	}
203 }
204 
205 static void
multipart_read_cb(GObject * source,GAsyncResult * asyncResult,gpointer data)206 multipart_read_cb (GObject *source, GAsyncResult *asyncResult, gpointer data)
207 {
208 	GMainLoop *loop = (GMainLoop*)data;
209 	GInputStream *in = G_INPUT_STREAM (source);
210 	GError *error = NULL;
211 	static gssize bytes_read_for_part = 0;
212 	gssize bytes_read;
213 
214 	bytes_read = g_input_stream_read_finish (in, asyncResult, &error);
215 	g_assert_no_error (error);
216 	if (error) {
217 		g_input_stream_close_async (in, G_PRIORITY_DEFAULT, NULL,
218 					    multipart_close_part_cb, NULL);
219 		g_object_unref (in);
220 
221 		g_main_loop_quit (loop);
222 		return;
223 	}
224 
225 	/* Read 0 bytes - try to start reading another part. */
226 	if (!bytes_read) {
227 		check_read (bytes_read_for_part, passes);
228 		bytes_read_for_part = 0;
229 		passes++;
230 
231 		g_input_stream_close_async (in, G_PRIORITY_DEFAULT, NULL,
232 					    multipart_close_part_cb, NULL);
233 		g_object_unref (in);
234 
235 		soup_multipart_input_stream_next_part_async (multipart, G_PRIORITY_DEFAULT, NULL,
236 							     multipart_next_part_cb, data);
237 		return;
238 	}
239 
240 	bytes_read_for_part += bytes_read;
241 	g_input_stream_read_async (in, buffer, READ_BUFFER_SIZE,
242 				   G_PRIORITY_DEFAULT, NULL,
243 				   multipart_read_cb, data);
244 }
245 
246 static void
check_headers(SoupMultipartInputStream * multipart,unsigned passes)247 check_headers (SoupMultipartInputStream* multipart, unsigned passes)
248 {
249 	SoupMessageHeaders *headers;
250 	SoupMessageHeadersIter iter;
251 	gboolean is_next;
252 	const char *name, *value;
253 
254 	headers = soup_multipart_input_stream_get_headers (multipart);
255 	soup_message_headers_iter_init (&iter, headers);
256 
257 	switch (passes) {
258 	case 0:
259 		is_next = soup_message_headers_iter_next (&iter, &name, &value);
260 		check_is_next (is_next);
261 
262 		g_assert_cmpstr (name, ==, "Content-Type");
263 		g_assert_cmpstr (value, ==, "text/html");
264 
265 		is_next = soup_message_headers_iter_next (&iter, &name, &value);
266 		check_is_next (is_next);
267 
268 		g_assert_cmpstr (name, ==, "Content-Length");
269 		g_assert_cmpstr (value, ==, "30");
270 
271 		break;
272 	case 1:
273 		is_next = soup_message_headers_iter_next (&iter, &name, &value);
274 		check_is_next (is_next);
275 
276 		g_assert_cmpstr (name, ==, "Content-Length");
277 		g_assert_cmpstr (value, ==, "10");
278 
279 		break;
280 	case 2:
281 	case 3:
282 		is_next = soup_message_headers_iter_next (&iter, &name, &value);
283 		check_is_next (is_next);
284 
285 		g_assert_cmpstr (name, ==, "Content-Type");
286 		g_assert_cmpstr (value, ==, "text/css");
287 
288 		break;
289 	default:
290 		soup_test_assert (FALSE, "unexpected part received");
291 		break;
292 	}
293 }
294 
295 static void
multipart_next_part_cb(GObject * source,GAsyncResult * res,gpointer data)296 multipart_next_part_cb (GObject *source, GAsyncResult *res, gpointer data)
297 {
298 	GMainLoop *loop = (GMainLoop*)data;
299 	GError *error = NULL;
300 	GInputStream *in;
301 	gsize read_size = READ_BUFFER_SIZE;
302 
303 	g_assert (SOUP_MULTIPART_INPUT_STREAM (source) == multipart);
304 
305 	in = soup_multipart_input_stream_next_part_finish (multipart, res, &error);
306 	g_assert_no_error (error);
307 	if (error) {
308 		g_clear_error (&error);
309 		g_object_unref (multipart);
310 		g_main_loop_quit (loop);
311 		return;
312 	}
313 
314 	if (!in) {
315 		g_assert_cmpint (passes, ==, 4);
316 		g_object_unref (multipart);
317 		g_main_loop_quit (loop);
318 		return;
319 	}
320 
321 	check_headers (multipart, passes);
322 
323 	if (g_object_get_data (G_OBJECT (multipart), "multipart-small-reads"))
324 		read_size = 4;
325 
326 	g_input_stream_read_async (in, buffer, read_size,
327 				   G_PRIORITY_DEFAULT, NULL,
328 				   multipart_read_cb, data);
329 }
330 
331 static void
multipart_handling_cb(GObject * source,GAsyncResult * res,gpointer data)332 multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
333 {
334 	GMainLoop *loop = (GMainLoop*)data;
335 	SoupRequest *request = SOUP_REQUEST (source);
336 	GError *error = NULL;
337 	GInputStream *in;
338 	SoupMessage *message;
339 
340 	in = soup_request_send_finish (request, res, &error);
341 	g_assert_no_error (error);
342 	if (error) {
343 		g_main_loop_quit (loop);
344 		return;
345 	}
346 
347 	message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
348 	multipart = soup_multipart_input_stream_new (message, in);
349 	g_object_unref (message);
350 	g_object_unref (in);
351 
352 	if (g_object_get_data (source, "multipart-small-reads"))
353 		g_object_set_data (G_OBJECT (multipart), "multipart-small-reads", GINT_TO_POINTER(1));
354 
355 	soup_multipart_input_stream_next_part_async (multipart, G_PRIORITY_DEFAULT, NULL,
356 						     multipart_next_part_cb, data);
357 }
358 
359 static void
sync_multipart_handling_cb(GObject * source,GAsyncResult * res,gpointer data)360 sync_multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
361 {
362 	GMainLoop *loop = (GMainLoop*)data;
363 	SoupRequest *request = SOUP_REQUEST (source);
364 	GError *error = NULL;
365 	GInputStream *in;
366 	SoupMessage *message;
367 	char buffer[READ_BUFFER_SIZE];
368 	gsize bytes_read;
369 
370 	in = soup_request_send_finish (request, res, &error);
371 	g_assert_no_error (error);
372 	if (error) {
373 		g_main_loop_quit (loop);
374 		return;
375 	}
376 
377 	message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
378 	multipart = soup_multipart_input_stream_new (message, in);
379 	g_object_unref (message);
380 	g_object_unref (in);
381 
382 	while (TRUE) {
383 		in = soup_multipart_input_stream_next_part (multipart, NULL, &error);
384 		g_assert_no_error (error);
385 		if (error) {
386 			g_clear_error (&error);
387 			break;
388 		}
389 
390 		if (!in)
391 			break;
392 
393 		check_headers (multipart, passes);
394 
395 		g_input_stream_read_all (in, (void*)buffer, sizeof (buffer), &bytes_read, NULL, &error);
396 		g_assert_no_error (error);
397 		if (error) {
398 			g_clear_error (&error);
399 			g_object_unref (in);
400 			break;
401 		}
402 
403 		check_read (bytes_read, passes);
404 
405 		passes++;
406 		g_object_unref (in);
407 	}
408 
409 	g_assert_cmpint (passes, ==, 4);
410 
411 	g_main_loop_quit (loop);
412 	g_object_unref (multipart);
413 }
414 
415 static void
test_multipart(gconstpointer data)416 test_multipart (gconstpointer data)
417 {
418 	int headers_expected = 1, sniffed_expected = 1;
419 	MultipartMode multipart_mode = GPOINTER_TO_INT (data);
420 	SoupRequest* request;
421 	SoupMessage *msg;
422 	GMainLoop *loop;
423 	int headers_count = 0;
424 	int sniffed_count = 0;
425 	GHashTable *params;
426 	const char *content_type;
427 	gboolean message_is_multipart = FALSE;
428 	GError* error = NULL;
429 
430 	request = soup_session_request (session, base_uri_string, &error);
431 	g_assert_no_error (error);
432 	if (error)
433 		return;
434 
435 	msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
436 
437 	/* This is used to track the number of parts. */
438 	passes = 0;
439 
440 	/* Force the server to close the connection. */
441 	soup_message_headers_append (msg->request_headers,
442 				     "Connection", "close");
443 
444 	g_signal_connect (msg, "got_headers",
445 			  G_CALLBACK (got_headers), &headers_count);
446 
447 	g_signal_connect (msg, "content-sniffed",
448 			  G_CALLBACK (content_sniffed), &sniffed_count);
449 
450 	loop = g_main_loop_new (NULL, TRUE);
451 
452 	if (multipart_mode == ASYNC_MULTIPART)
453 		soup_request_send_async (request, NULL, multipart_handling_cb, loop);
454 	else if (multipart_mode == ASYNC_MULTIPART_SMALL_READS) {
455 		g_object_set_data (G_OBJECT (request), "multipart-small-reads", GINT_TO_POINTER(1));
456 		soup_request_send_async (request, NULL, multipart_handling_cb, loop);
457 	} else if (multipart_mode == SYNC_MULTIPART)
458 		soup_request_send_async (request, NULL, sync_multipart_handling_cb, loop);
459 	else
460 		soup_request_send_async (request, NULL, no_multipart_handling_cb, loop);
461 
462 	g_main_loop_run (loop);
463 
464 	content_type = soup_message_headers_get_content_type (msg->response_headers, &params);
465 
466 	if (content_type &&
467 	    g_str_has_prefix (content_type, "multipart/") &&
468 	    g_hash_table_lookup (params, "boundary")) {
469 		message_is_multipart = TRUE;
470 	}
471 	g_clear_pointer (&params, g_hash_table_unref);
472 
473 	g_assert_true (message_is_multipart);
474 	g_assert_cmpint (headers_count, ==, headers_expected);
475 	g_assert_cmpint (sniffed_count, ==, sniffed_expected);
476 
477 	g_object_unref (msg);
478 	g_object_unref (request);
479 	g_main_loop_unref (loop);
480 }
481 
482 int
main(int argc,char ** argv)483 main (int argc, char **argv)
484 {
485 	SoupServer *server;
486 	int ret;
487 
488 	test_init (argc, argv, NULL);
489 
490 	buffer = g_malloc (READ_BUFFER_SIZE);
491 
492 	server = soup_test_server_new (SOUP_TEST_SERVER_DEFAULT);
493 	soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
494 	base_uri = soup_test_server_get_uri (server, "http", NULL);
495 	base_uri_string = soup_uri_to_string (base_uri, FALSE);
496 
497 	/* FIXME: I had to raise the number of connections allowed here, otherwise I
498 	 * was hitting the limit, which indicates some connections are not dying.
499 	 */
500 	session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
501 					 "use-thread-context", TRUE,
502 					 "max-conns", 20,
503 					 "max-conns-per-host", 20,
504 					 NULL);
505 	soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
506 
507 	g_test_add_data_func ("/multipart/no", GINT_TO_POINTER (NO_MULTIPART), test_multipart);
508 	g_test_add_data_func ("/multipart/sync", GINT_TO_POINTER (SYNC_MULTIPART), test_multipart);
509 	g_test_add_data_func ("/multipart/async", GINT_TO_POINTER (ASYNC_MULTIPART), test_multipart);
510 	g_test_add_data_func ("/multipart/async-small-reads", GINT_TO_POINTER (ASYNC_MULTIPART_SMALL_READS), test_multipart);
511 
512 	ret = g_test_run ();
513 
514 	soup_uri_free (base_uri);
515 	g_free (base_uri_string);
516 	g_free (buffer);
517 
518 	soup_test_session_abort_unref (session);
519 	soup_test_server_quit_unref (server);
520 
521 	test_cleanup ();
522 	return ret;
523 }
524