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