1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2011 Collabora Ltd.
11 #include "test-utils.h"
13 #define READ_BUFFER_SIZE 8192
19 ASYNC_MULTIPART_SMALL_READS
24 char *base_uri_string;
26 SoupMultipartInputStream *multipart;
30 /* This payload contains 4 different responses.
32 * First, a text/html response with a Content-Length (31);
33 * Second, a response lacking Content-Type with Content-Length (11);
34 * Third, a text/css response with no Content-Length;
35 * Fourth, same as the third, but with different content;
37 const char *payload = \
39 "Content-Type: text/html\n"
40 "Content-Length: 30\r\n" \
42 "<html><body>Hey!</body></html>" \
43 "\r\n--cut-here\r\n" \
44 "Content-Length: 10\r\n" \
47 "\r\n--cut-here\r\n" \
48 "Content-Type: text/css\r\n" \
50 ".soup { before: rocks; }" \
51 "\r\n--cut-here\n" /* Tests boundary ending in a single \n. */ \
52 "Content-Type: text/css\r\n" \
54 "#soup { background-color: black; }" \
58 server_callback (SoupServer *server, SoupMessage *msg,
59 const char *path, GHashTable *query,
60 SoupClientContext *context, gpointer data)
62 if (msg->method != SOUP_METHOD_GET) {
63 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
67 soup_message_set_status (msg, SOUP_STATUS_OK);
69 soup_message_headers_append (msg->response_headers,
70 "Content-Type", "multipart/x-mixed-replace; boundary=cut-here");
72 soup_message_body_append (msg->response_body,
77 soup_message_body_complete (msg->response_body);
81 content_sniffed (SoupMessage *msg, char *content_type, GHashTable *params, int *sniffed_count)
83 *sniffed_count = *sniffed_count + 1;
84 debug_printf (2, " content-sniffed -> %s\n", content_type);
88 check_is_next (gboolean is_next)
90 soup_test_assert (is_next,
91 "expected a header, but there are no more headers");
95 got_headers (SoupMessage *msg, int *headers_count)
97 SoupMessageHeadersIter iter;
99 const char* name, *value;
101 *headers_count = *headers_count + 1;
103 soup_message_headers_iter_init (&iter, msg->response_headers);
105 is_next = soup_message_headers_iter_next (&iter, &name, &value);
106 check_is_next (is_next);
108 if (g_str_equal (name, "Date")) {
109 is_next = soup_message_headers_iter_next (&iter, &name, &value);
110 check_is_next (is_next);
113 g_assert_cmpstr (name, ==, "Content-Type");
114 g_assert_cmpstr (value, ==, "multipart/x-mixed-replace; boundary=cut-here");
118 read_cb (GObject *source, GAsyncResult *asyncResult, gpointer data)
120 GMainLoop *loop = (GMainLoop*)data;
121 GInputStream *stream = G_INPUT_STREAM (source);
122 GError *error = NULL;
125 bytes_read = g_input_stream_read_finish (stream, asyncResult, &error);
126 g_assert_no_error (error);
128 g_object_unref (stream);
129 g_main_loop_quit (loop);
134 g_input_stream_close (stream, NULL, &error);
135 g_assert_no_error (error);
136 g_object_unref (stream);
137 g_main_loop_quit (loop);
141 g_input_stream_read_async (stream, buffer, READ_BUFFER_SIZE,
142 G_PRIORITY_DEFAULT, NULL,
147 no_multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
149 GMainLoop *loop = (GMainLoop*)data;
150 SoupRequest *request = SOUP_REQUEST (source);
151 GError *error = NULL;
154 in = soup_request_send_finish (request, res, &error);
155 g_assert_no_error (error);
157 g_main_loop_quit (loop);
161 g_input_stream_read_async (in, buffer, READ_BUFFER_SIZE,
162 G_PRIORITY_DEFAULT, NULL,
167 multipart_close_part_cb (GObject *source, GAsyncResult *res, gpointer data)
169 GInputStream *in = G_INPUT_STREAM (source);
170 GError *error = NULL;
172 g_input_stream_close_finish (in, res, &error);
173 g_assert_no_error (error);
176 static void multipart_next_part_cb (GObject *source,
181 check_read (gsize nread, unsigned passes)
185 g_assert_cmpint (nread, ==, 30);
188 g_assert_cmpint (nread, ==, 10);
191 g_assert_cmpint (nread, ==, 24);
194 g_assert_cmpint (nread, ==, 34);
197 soup_test_assert (FALSE, "unexpected read of size: %d", (int)nread);
203 multipart_read_cb (GObject *source, GAsyncResult *asyncResult, gpointer data)
205 GMainLoop *loop = (GMainLoop*)data;
206 GInputStream *in = G_INPUT_STREAM (source);
207 GError *error = NULL;
208 static gssize bytes_read_for_part = 0;
211 bytes_read = g_input_stream_read_finish (in, asyncResult, &error);
212 g_assert_no_error (error);
214 g_input_stream_close_async (in, G_PRIORITY_DEFAULT, NULL,
215 multipart_close_part_cb, NULL);
218 g_main_loop_quit (loop);
222 /* Read 0 bytes - try to start reading another part. */
224 check_read (bytes_read_for_part, passes);
225 bytes_read_for_part = 0;
228 g_input_stream_close_async (in, G_PRIORITY_DEFAULT, NULL,
229 multipart_close_part_cb, NULL);
232 soup_multipart_input_stream_next_part_async (multipart, G_PRIORITY_DEFAULT, NULL,
233 multipart_next_part_cb, data);
237 bytes_read_for_part += bytes_read;
238 g_input_stream_read_async (in, buffer, READ_BUFFER_SIZE,
239 G_PRIORITY_DEFAULT, NULL,
240 multipart_read_cb, data);
244 check_headers (SoupMultipartInputStream* multipart, unsigned passes)
246 SoupMessageHeaders *headers;
247 SoupMessageHeadersIter iter;
249 const char *name, *value;
251 headers = soup_multipart_input_stream_get_headers (multipart);
252 soup_message_headers_iter_init (&iter, headers);
256 is_next = soup_message_headers_iter_next (&iter, &name, &value);
257 check_is_next (is_next);
259 g_assert_cmpstr (name, ==, "Content-Type");
260 g_assert_cmpstr (value, ==, "text/html");
262 is_next = soup_message_headers_iter_next (&iter, &name, &value);
263 check_is_next (is_next);
265 g_assert_cmpstr (name, ==, "Content-Length");
266 g_assert_cmpstr (value, ==, "30");
270 is_next = soup_message_headers_iter_next (&iter, &name, &value);
271 check_is_next (is_next);
273 g_assert_cmpstr (name, ==, "Content-Length");
274 g_assert_cmpstr (value, ==, "10");
279 is_next = soup_message_headers_iter_next (&iter, &name, &value);
280 check_is_next (is_next);
282 g_assert_cmpstr (name, ==, "Content-Type");
283 g_assert_cmpstr (value, ==, "text/css");
287 soup_test_assert (FALSE, "unexpected part received");
293 multipart_next_part_cb (GObject *source, GAsyncResult *res, gpointer data)
295 GMainLoop *loop = (GMainLoop*)data;
296 GError *error = NULL;
298 gsize read_size = READ_BUFFER_SIZE;
300 g_assert (SOUP_MULTIPART_INPUT_STREAM (source) == multipart);
302 in = soup_multipart_input_stream_next_part_finish (multipart, res, &error);
303 g_assert_no_error (error);
305 g_clear_error (&error);
306 g_object_unref (multipart);
307 g_main_loop_quit (loop);
312 g_assert_cmpint (passes, ==, 4);
313 g_object_unref (multipart);
314 g_main_loop_quit (loop);
318 check_headers (multipart, passes);
320 if (g_object_get_data (G_OBJECT (multipart), "multipart-small-reads"))
323 g_input_stream_read_async (in, buffer, read_size,
324 G_PRIORITY_DEFAULT, NULL,
325 multipart_read_cb, data);
329 multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
331 GMainLoop *loop = (GMainLoop*)data;
332 SoupRequest *request = SOUP_REQUEST (source);
333 GError *error = NULL;
335 SoupMessage *message;
337 in = soup_request_send_finish (request, res, &error);
338 g_assert_no_error (error);
340 g_main_loop_quit (loop);
344 message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
345 multipart = soup_multipart_input_stream_new (message, in);
346 g_object_unref (message);
349 if (g_object_get_data (source, "multipart-small-reads"))
350 g_object_set_data (G_OBJECT (multipart), "multipart-small-reads", GINT_TO_POINTER(1));
352 soup_multipart_input_stream_next_part_async (multipart, G_PRIORITY_DEFAULT, NULL,
353 multipart_next_part_cb, data);
357 sync_multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
359 GMainLoop *loop = (GMainLoop*)data;
360 SoupRequest *request = SOUP_REQUEST (source);
361 GError *error = NULL;
363 SoupMessage *message;
364 char buffer[READ_BUFFER_SIZE];
367 in = soup_request_send_finish (request, res, &error);
368 g_assert_no_error (error);
370 g_main_loop_quit (loop);
374 message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
375 multipart = soup_multipart_input_stream_new (message, in);
376 g_object_unref (message);
380 in = soup_multipart_input_stream_next_part (multipart, NULL, &error);
381 g_assert_no_error (error);
383 g_clear_error (&error);
390 check_headers (multipart, passes);
392 g_input_stream_read_all (in, (void*)buffer, sizeof (buffer), &bytes_read, NULL, &error);
393 g_assert_no_error (error);
395 g_clear_error (&error);
400 check_read (bytes_read, passes);
406 g_assert_cmpint (passes, ==, 4);
408 g_main_loop_quit (loop);
409 g_object_unref (multipart);
413 test_multipart (gconstpointer data)
415 int headers_expected = 1, sniffed_expected = 1;
416 MultipartMode multipart_mode = GPOINTER_TO_INT (data);
417 SoupRequest* request;
420 int headers_count = 0;
421 int sniffed_count = 0;
423 const char *content_type;
424 gboolean message_is_multipart = FALSE;
425 GError* error = NULL;
427 request = soup_session_request (session, base_uri_string, &error);
428 g_assert_no_error (error);
432 msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
434 /* This is used to track the number of parts. */
437 /* Force the server to close the connection. */
438 soup_message_headers_append (msg->request_headers,
439 "Connection", "close");
441 g_signal_connect (msg, "got_headers",
442 G_CALLBACK (got_headers), &headers_count);
444 g_signal_connect (msg, "content-sniffed",
445 G_CALLBACK (content_sniffed), &sniffed_count);
447 loop = g_main_loop_new (NULL, TRUE);
449 if (multipart_mode == ASYNC_MULTIPART)
450 soup_request_send_async (request, NULL, multipart_handling_cb, loop);
451 else if (multipart_mode == ASYNC_MULTIPART_SMALL_READS) {
452 g_object_set_data (G_OBJECT (request), "multipart-small-reads", GINT_TO_POINTER(1));
453 soup_request_send_async (request, NULL, multipart_handling_cb, loop);
454 } else if (multipart_mode == SYNC_MULTIPART)
455 soup_request_send_async (request, NULL, sync_multipart_handling_cb, loop);
457 soup_request_send_async (request, NULL, no_multipart_handling_cb, loop);
459 g_main_loop_run (loop);
461 content_type = soup_message_headers_get_content_type (msg->response_headers, ¶ms);
464 g_str_has_prefix (content_type, "multipart/") &&
465 g_hash_table_lookup (params, "boundary")) {
466 message_is_multipart = TRUE;
468 g_clear_pointer (¶ms, g_hash_table_unref);
470 g_assert_true (message_is_multipart);
471 g_assert_cmpint (headers_count, ==, headers_expected);
472 g_assert_cmpint (sniffed_count, ==, sniffed_expected);
474 g_object_unref (msg);
475 g_object_unref (request);
476 g_main_loop_unref (loop);
480 main (int argc, char **argv)
485 test_init (argc, argv, NULL);
487 buffer = g_malloc (READ_BUFFER_SIZE);
489 server = soup_test_server_new (FALSE);
490 soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
491 base_uri = soup_uri_new ("http://127.0.0.1");
492 soup_uri_set_port (base_uri, soup_server_get_port (server));
493 base_uri_string = soup_uri_to_string (base_uri, FALSE);
495 /* FIXME: I had to raise the number of connections allowed here, otherwise I
496 * was hitting the limit, which indicates some connections are not dying.
498 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
499 "use-thread-context", TRUE,
501 "max-conns-per-host", 20,
503 soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
505 g_test_add_data_func ("/multipart/no", GINT_TO_POINTER (NO_MULTIPART), test_multipart);
506 g_test_add_data_func ("/multipart/sync", GINT_TO_POINTER (SYNC_MULTIPART), test_multipart);
507 g_test_add_data_func ("/multipart/async", GINT_TO_POINTER (ASYNC_MULTIPART), test_multipart);
508 g_test_add_data_func ("/multipart/async-small-reads", GINT_TO_POINTER (ASYNC_MULTIPART_SMALL_READS), test_multipart);
512 soup_uri_free (base_uri);
513 g_free (base_uri_string);
516 soup_test_session_abort_unref (session);
517 soup_test_server_quit_unref (server);