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)
91 debug_printf (1, " expected a header, but there are no more headers\n");
97 got_headers (SoupMessage *msg, int *headers_count)
99 SoupMessageHeadersIter iter;
101 const char* name, *value;
103 *headers_count = *headers_count + 1;
105 soup_message_headers_iter_init (&iter, msg->response_headers);
107 is_next = soup_message_headers_iter_next (&iter, &name, &value);
108 check_is_next (is_next);
110 if (g_str_equal (name, "Date")) {
111 is_next = soup_message_headers_iter_next (&iter, &name, &value);
112 check_is_next (is_next);
115 if (!g_str_equal (name, "Content-Type")) {
116 debug_printf (1, " expected `Content-Type' got %s\n", name);
120 if (!g_str_equal (value, "multipart/x-mixed-replace; boundary=cut-here")) {
121 debug_printf (1, " expected `multipart/x-mixed-replace; boundary=cut-here' got %s\n", value);
127 read_cb (GObject *source, GAsyncResult *asyncResult, gpointer data)
129 GMainLoop *loop = (GMainLoop*)data;
130 GInputStream *stream = G_INPUT_STREAM (source);
131 GError *error = NULL;
132 gssize bytes_read = g_input_stream_read_finish (stream, asyncResult, &error);
135 debug_printf (1, " failed read: %s\n", error->message);
138 g_object_unref (stream);
139 g_main_loop_quit (loop);
144 g_input_stream_close (stream, NULL, &error);
145 g_object_unref (stream);
148 debug_printf (1, " failed close: %s\n", error->message);
152 g_main_loop_quit (loop);
156 g_input_stream_read_async (stream, buffer, READ_BUFFER_SIZE,
157 G_PRIORITY_DEFAULT, NULL,
162 no_multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
164 GMainLoop *loop = (GMainLoop*)data;
165 SoupRequest *request = SOUP_REQUEST (source);
166 GError *error = NULL;
169 in = soup_request_send_finish (request, res, &error);
172 debug_printf (1, " failed send: %s\n", error->message);
175 g_main_loop_quit (loop);
179 g_input_stream_read_async (in, buffer, READ_BUFFER_SIZE,
180 G_PRIORITY_DEFAULT, NULL,
185 multipart_close_part_cb (GObject *source, GAsyncResult *res, gpointer data)
187 GInputStream *in = G_INPUT_STREAM (source);
188 GError *error = NULL;
190 g_input_stream_close_finish (in, res, &error);
192 debug_printf (1, " error closing stream: %s\n", error->message);
197 static void multipart_next_part_cb (GObject *source,
202 check_read (gsize nread, unsigned passes)
207 debug_printf (1, " expected to read 30 bytes, got: %d\n", (int)nread);
213 debug_printf (1, " expected to read 10 bytes, got: %d\n", (int)nread);
219 debug_printf (1, " expected to read 24 bytes, got: %d\n", (int)nread);
225 debug_printf (1, " expected to read 34 bytes, got: %d\n", (int)nread);
230 debug_printf (1, " unexpected read of size: %d\n", (int)nread);
236 multipart_read_cb (GObject *source, GAsyncResult *asyncResult, gpointer data)
238 GMainLoop *loop = (GMainLoop*)data;
239 GInputStream *in = G_INPUT_STREAM (source);
240 GError *error = NULL;
241 static gssize bytes_read_for_part = 0;
244 bytes_read = g_input_stream_read_finish (in, asyncResult, &error);
247 debug_printf (1, " failed read: %s\n", error->message);
250 g_input_stream_close_async (in, G_PRIORITY_DEFAULT, NULL,
251 multipart_close_part_cb, NULL);
254 g_main_loop_quit (loop);
258 /* Read 0 bytes - try to start reading another part. */
260 check_read (bytes_read_for_part, passes);
261 bytes_read_for_part = 0;
264 g_input_stream_close_async (in, G_PRIORITY_DEFAULT, NULL,
265 multipart_close_part_cb, NULL);
268 soup_multipart_input_stream_next_part_async (multipart, G_PRIORITY_DEFAULT, NULL,
269 multipart_next_part_cb, data);
273 bytes_read_for_part += bytes_read;
274 g_input_stream_read_async (in, buffer, READ_BUFFER_SIZE,
275 G_PRIORITY_DEFAULT, NULL,
276 multipart_read_cb, data);
280 check_headers (SoupMultipartInputStream* multipart, unsigned passes)
282 SoupMessageHeaders *headers;
283 SoupMessageHeadersIter iter;
285 const char *name, *value;
287 headers = soup_multipart_input_stream_get_headers (multipart);
288 soup_message_headers_iter_init (&iter, headers);
292 is_next = soup_message_headers_iter_next (&iter, &name, &value);
293 check_is_next (is_next);
295 if (!g_str_equal (name, "Content-Type")) {
296 debug_printf (1, " [0] expected `Content-Type' got %s\n", name);
300 if (!g_str_equal (value, "text/html")) {
301 debug_printf (1, " [0] expected `text/html' got %s\n", value);
305 is_next = soup_message_headers_iter_next (&iter, &name, &value);
306 check_is_next (is_next);
308 if (!g_str_equal (name, "Content-Length")) {
309 debug_printf (1, " [0] expected `Content-Length' got %s\n", name);
313 if (!g_str_equal (value, "30")) {
314 debug_printf (1, " [0] expected `30' got %s\n", value);
320 is_next = soup_message_headers_iter_next (&iter, &name, &value);
321 check_is_next (is_next);
323 if (!g_str_equal (name, "Content-Length")) {
324 debug_printf (1, " [1] expected `Content-Length' got %s\n", name);
328 if (!g_str_equal (value, "10")) {
329 debug_printf (1, " [1] expected `10' got %s\n", value);
336 is_next = soup_message_headers_iter_next (&iter, &name, &value);
337 check_is_next (is_next);
339 if (!g_str_equal (name, "Content-Type")) {
340 debug_printf (1, " [%d] expected `Content-Type' got %s\n", passes, name);
344 if (!g_str_equal (value, "text/css")) {
345 debug_printf (1, " [%d] expected `text/html' got %s\n", passes, value);
351 debug_printf (1, " unexpected part received\n");
357 multipart_next_part_cb (GObject *source, GAsyncResult *res, gpointer data)
359 GMainLoop *loop = (GMainLoop*)data;
360 GError *error = NULL;
362 gsize read_size = READ_BUFFER_SIZE;
364 g_assert (SOUP_MULTIPART_INPUT_STREAM (source) == multipart);
366 in = soup_multipart_input_stream_next_part_finish (multipart, res, &error);
369 debug_printf (1, " failed next part: %s\n", error->message);
370 g_clear_error (&error);
373 g_object_unref (multipart);
374 g_main_loop_quit (loop);
380 debug_printf (1, " expected 4 parts, got %u\n", passes);
384 g_object_unref (multipart);
385 g_main_loop_quit (loop);
389 check_headers (multipart, passes);
391 if (g_object_get_data (G_OBJECT (multipart), "multipart-small-reads"))
394 g_input_stream_read_async (in, buffer, read_size,
395 G_PRIORITY_DEFAULT, NULL,
396 multipart_read_cb, data);
400 multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
402 GMainLoop *loop = (GMainLoop*)data;
403 SoupRequest *request = SOUP_REQUEST (source);
404 GError *error = NULL;
406 SoupMessage *message;
408 in = soup_request_send_finish (request, res, &error);
409 message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
410 multipart = soup_multipart_input_stream_new (message, in);
411 g_object_unref (message);
415 debug_printf (1, " failed send: %s\n", error->message);
418 g_main_loop_quit (loop);
422 if (g_object_get_data (source, "multipart-small-reads"))
423 g_object_set_data (G_OBJECT (multipart), "multipart-small-reads", GINT_TO_POINTER(1));
425 soup_multipart_input_stream_next_part_async (multipart, G_PRIORITY_DEFAULT, NULL,
426 multipart_next_part_cb, data);
430 sync_multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
432 GMainLoop *loop = (GMainLoop*)data;
433 SoupRequest *request = SOUP_REQUEST (source);
434 GError *error = NULL;
436 SoupMessage *message;
437 char buffer[READ_BUFFER_SIZE];
440 in = soup_request_send_finish (request, res, &error);
441 message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
442 multipart = soup_multipart_input_stream_new (message, in);
443 g_object_unref (message);
447 debug_printf (1, " failed send: %s\n", error->message);
450 g_main_loop_quit (loop);
455 in = soup_multipart_input_stream_next_part (multipart, NULL, &error);
458 debug_printf (1, " failed sync next part: %s\n", error->message);
460 g_clear_error (&error);
467 check_headers (multipart, passes);
469 g_input_stream_read_all (in, (void*)buffer, sizeof (buffer), &bytes_read, NULL, &error);
472 debug_printf (1, " failed sync read: %s\n", error->message);
474 g_clear_error (&error);
479 check_read (bytes_read, passes);
486 debug_printf (1, " expected 4 parts, got %u\n", passes);
490 g_main_loop_quit (loop);
491 g_object_unref (multipart);
495 multipart_mode_to_string (MultipartMode mode)
497 if (mode == NO_MULTIPART)
498 return "NO_MULTIPART";
499 else if (mode == SYNC_MULTIPART)
500 return "SYNC_MULTIPART";
501 else if (mode == ASYNC_MULTIPART_SMALL_READS)
502 return "SYNC_MULTIPART_SMALL_READS";
504 return "ASYNC_MULTIPART";
508 test_multipart (int headers_expected, int sniffed_expected, MultipartMode multipart_mode)
510 GError* error = NULL;
511 SoupRequest* request = soup_session_request (session, base_uri_string, &error);
513 SoupMessage *msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
514 GMainLoop *loop = g_main_loop_new (NULL, TRUE);
515 int headers_count = 0;
516 int sniffed_count = 0;
518 const char *content_type;
519 gboolean message_is_multipart = FALSE;
521 debug_printf (1, "test_multipart(%s)\n", multipart_mode_to_string (multipart_mode));
523 /* This is used to track the number of parts. */
526 /* Force the server to close the connection. */
527 soup_message_headers_append (msg->request_headers,
528 "Connection", "close");
530 g_signal_connect (msg, "got_headers",
531 G_CALLBACK (got_headers), &headers_count);
533 g_signal_connect (msg, "content-sniffed",
534 G_CALLBACK (content_sniffed), &sniffed_count);
536 if (multipart_mode == ASYNC_MULTIPART)
537 soup_request_send_async (request, NULL, multipart_handling_cb, loop);
538 else if (multipart_mode == ASYNC_MULTIPART_SMALL_READS) {
539 g_object_set_data (G_OBJECT (request), "multipart-small-reads", GINT_TO_POINTER(1));
540 soup_request_send_async (request, NULL, multipart_handling_cb, loop);
541 } else if (multipart_mode == SYNC_MULTIPART)
542 soup_request_send_async (request, NULL, sync_multipart_handling_cb, loop);
544 soup_request_send_async (request, NULL, no_multipart_handling_cb, loop);
546 g_main_loop_run (loop);
548 content_type = soup_message_headers_get_content_type (msg->response_headers, ¶ms);
551 g_str_has_prefix (content_type, "multipart/") &&
552 g_hash_table_lookup (params, "boundary")) {
553 message_is_multipart = TRUE;
555 g_clear_pointer (¶ms, g_hash_table_unref);
557 if (!message_is_multipart) {
559 " Header does not indicate a multipart message!\n");
563 if (headers_count != headers_expected) {
565 " expected got_header %d times, got %d!\n",
566 headers_expected, headers_count);
570 if (sniffed_count != sniffed_expected) {
572 " expected content_sniffed %d times, got %d!\n",
573 sniffed_expected, sniffed_count);
577 g_object_unref (msg);
578 g_object_unref (request);
579 g_main_loop_unref (loop);
583 main (int argc, char **argv)
587 test_init (argc, argv, NULL);
589 buffer = g_malloc (READ_BUFFER_SIZE);
591 server = soup_test_server_new (FALSE);
592 soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
593 base_uri = soup_uri_new ("http://127.0.0.1");
594 soup_uri_set_port (base_uri, soup_server_get_port (server));
595 base_uri_string = soup_uri_to_string (base_uri, FALSE);
597 /* FIXME: I had to raise the number of connections allowed here, otherwise I
598 * was hitting the limit, which indicates some connections are not dying.
600 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
601 "use-thread-context", TRUE,
603 "max-conns-per-host", 20,
605 soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
607 test_multipart (1, 1, NO_MULTIPART);
608 test_multipart (1, 1, SYNC_MULTIPART);
609 test_multipart (1, 1, ASYNC_MULTIPART);
610 test_multipart (1, 1, ASYNC_MULTIPART_SMALL_READS);
612 soup_uri_free (base_uri);
613 g_free (base_uri_string);
616 soup_test_session_abort_unref (session);
617 soup_test_server_quit_unref (server);