tests: add cache-test
[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         if (!is_next) {
91                 debug_printf (1, "  expected a header, but there are no more headers\n");
92                 errors++;
93         }
94 }
95
96 static void
97 got_headers (SoupMessage *msg, int *headers_count)
98 {
99         SoupMessageHeadersIter iter;
100         gboolean is_next;
101         const char* name, *value;
102
103         *headers_count = *headers_count + 1;
104
105         soup_message_headers_iter_init (&iter, msg->response_headers);
106
107         is_next = soup_message_headers_iter_next (&iter, &name, &value);
108         check_is_next (is_next);
109
110         if (g_str_equal (name, "Date")) {
111                 is_next = soup_message_headers_iter_next (&iter, &name, &value);
112                 check_is_next (is_next);
113         }
114
115         if (!g_str_equal (name, "Content-Type")) {
116                 debug_printf (1, "  expected `Content-Type' got %s\n", name);
117                 errors++;
118         }
119
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);
122                 errors++;
123         }
124 }
125
126 static void
127 read_cb (GObject *source, GAsyncResult *asyncResult, gpointer data)
128 {
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);
133
134         if (error) {
135                 debug_printf (1, "  failed read: %s\n", error->message);
136                 errors++;
137
138                 g_object_unref (stream);
139                 g_main_loop_quit (loop);
140                 return;
141         }
142
143         if (!bytes_read) {
144                 g_input_stream_close (stream, NULL, &error);
145                 g_object_unref (stream);
146
147                 if (error) {
148                         debug_printf (1, "  failed close: %s\n", error->message);
149                         errors++;
150                 }
151
152                 g_main_loop_quit (loop);
153                 return;
154         }
155
156         g_input_stream_read_async (stream, buffer, READ_BUFFER_SIZE,
157                                    G_PRIORITY_DEFAULT, NULL,
158                                    read_cb, data);
159 }
160
161 static void
162 no_multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
163 {
164         GMainLoop *loop = (GMainLoop*)data;
165         SoupRequest *request = SOUP_REQUEST (source);
166         GError *error = NULL;
167         GInputStream* in;
168
169         in = soup_request_send_finish (request, res, &error);
170
171         if (error) {
172                 debug_printf (1, "  failed send: %s\n", error->message);
173                 errors++;
174
175                 g_main_loop_quit (loop);
176                 return;
177         }
178
179         g_input_stream_read_async (in, buffer, READ_BUFFER_SIZE,
180                                    G_PRIORITY_DEFAULT, NULL,
181                                    read_cb, data);
182 }
183
184 static void
185 multipart_close_part_cb (GObject *source, GAsyncResult *res, gpointer data)
186 {
187         GInputStream *in = G_INPUT_STREAM (source);
188         GError *error = NULL;
189
190         g_input_stream_close_finish (in, res, &error);
191         if (error) {
192                 debug_printf (1, "  error closing stream: %s\n", error->message);
193                 errors++;
194         }
195 }
196
197 static void multipart_next_part_cb (GObject *source,
198                                     GAsyncResult *res,
199                                     gpointer data);
200
201 static void
202 check_read (gsize nread, unsigned passes)
203 {
204         switch (passes) {
205         case 0:
206                 if (nread != 30) {
207                         debug_printf (1, "  expected to read 30 bytes, got: %d\n", (int)nread);
208                         errors++;
209                 }
210                 break;
211         case 1:
212                 if (nread != 10) {
213                         debug_printf (1, "  expected to read 10 bytes, got: %d\n", (int)nread);
214                         errors++;
215                 }
216                 break;
217         case 2:
218                 if (nread != 24) {
219                         debug_printf (1, "  expected to read 24 bytes, got: %d\n", (int)nread);
220                         errors++;
221                 }
222                 break;
223         case 3:
224                 if (nread != 34) {
225                         debug_printf (1, "  expected to read 34 bytes, got: %d\n", (int)nread);
226                         errors++;
227                 }
228                 break;
229         default:
230                 debug_printf (1, "  unexpected read of size: %d\n", (int)nread);
231                 errors++;
232         }
233 }
234
235 static void
236 multipart_read_cb (GObject *source, GAsyncResult *asyncResult, gpointer data)
237 {
238         GMainLoop *loop = (GMainLoop*)data;
239         GInputStream *in = G_INPUT_STREAM (source);
240         GError *error = NULL;
241         static gssize bytes_read_for_part = 0;
242         gssize bytes_read;
243
244         bytes_read = g_input_stream_read_finish (in, asyncResult, &error);
245
246         if (error) {
247                 debug_printf (1, "  failed read: %s\n", error->message);
248                 errors++;
249
250                 g_input_stream_close_async (in, G_PRIORITY_DEFAULT, NULL,
251                                             multipart_close_part_cb, NULL);
252                 g_object_unref (in);
253
254                 g_main_loop_quit (loop);
255                 return;
256         }
257
258         /* Read 0 bytes - try to start reading another part. */
259         if (!bytes_read) {
260                 check_read (bytes_read_for_part, passes);
261                 bytes_read_for_part = 0;
262                 passes++;
263
264                 g_input_stream_close_async (in, G_PRIORITY_DEFAULT, NULL,
265                                             multipart_close_part_cb, NULL);
266                 g_object_unref (in);
267
268                 soup_multipart_input_stream_next_part_async (multipart, G_PRIORITY_DEFAULT, NULL,
269                                                              multipart_next_part_cb, data);
270                 return;
271         }
272
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);
277 }
278
279 static void
280 check_headers (SoupMultipartInputStream* multipart, unsigned passes)
281 {
282         SoupMessageHeaders *headers;
283         SoupMessageHeadersIter iter;
284         gboolean is_next;
285         const char *name, *value;
286
287         headers = soup_multipart_input_stream_get_headers (multipart);
288         soup_message_headers_iter_init (&iter, headers);
289
290         switch (passes) {
291         case 0:
292                 is_next = soup_message_headers_iter_next (&iter, &name, &value);
293                 check_is_next (is_next);
294
295                 if (!g_str_equal (name, "Content-Type")) {
296                         debug_printf (1, "  [0] expected `Content-Type' got %s\n", name);
297                         errors++;
298                 }
299
300                 if (!g_str_equal (value, "text/html")) {
301                         debug_printf (1, "  [0] expected `text/html' got %s\n", value);
302                         errors++;
303                 }
304
305                 is_next = soup_message_headers_iter_next (&iter, &name, &value);
306                 check_is_next (is_next);
307
308                 if (!g_str_equal (name, "Content-Length")) {
309                         debug_printf (1, "  [0] expected `Content-Length' got %s\n", name);
310                         errors++;
311                 }
312
313                 if (!g_str_equal (value, "30")) {
314                         debug_printf (1, "  [0] expected `30' got %s\n", value);
315                         errors++;
316                 }
317
318                 break;
319         case 1:
320                 is_next = soup_message_headers_iter_next (&iter, &name, &value);
321                 check_is_next (is_next);
322
323                 if (!g_str_equal (name, "Content-Length")) {
324                         debug_printf (1, "  [1] expected `Content-Length' got %s\n", name);
325                         errors++;
326                 }
327
328                 if (!g_str_equal (value, "10")) {
329                         debug_printf (1, "  [1] expected `10' got %s\n", value);
330                         errors++;
331                 }
332
333                 break;
334         case 2:
335         case 3:
336                 is_next = soup_message_headers_iter_next (&iter, &name, &value);
337                 check_is_next (is_next);
338
339                 if (!g_str_equal (name, "Content-Type")) {
340                         debug_printf (1, "  [%d] expected `Content-Type' got %s\n", passes, name);
341                         errors++;
342                 }
343
344                 if (!g_str_equal (value, "text/css")) {
345                         debug_printf (1, "  [%d] expected `text/html' got %s\n", passes, value);
346                         errors++;
347                 }
348
349                 break;
350         default:
351                 debug_printf (1, "  unexpected part received\n");
352                 break;
353         }
354 }
355
356 static void
357 multipart_next_part_cb (GObject *source, GAsyncResult *res, gpointer data)
358 {
359         GMainLoop *loop = (GMainLoop*)data;
360         GError *error = NULL;
361         GInputStream *in;
362         gsize read_size = READ_BUFFER_SIZE;
363
364         g_assert (SOUP_MULTIPART_INPUT_STREAM (source) == multipart);
365
366         in = soup_multipart_input_stream_next_part_finish (multipart, res, &error);
367
368         if (error) {
369                 debug_printf (1, "  failed next part: %s\n", error->message);
370                 g_clear_error (&error);
371                 errors++;
372
373                 g_object_unref (multipart);
374                 g_main_loop_quit (loop);
375                 return;
376         }
377
378         if (!in) {
379                 if (passes != 4) {
380                         debug_printf (1, "  expected 4 parts, got %u\n", passes);
381                         errors++;
382                 }
383
384                 g_object_unref (multipart);
385                 g_main_loop_quit (loop);
386                 return;
387         }
388
389         check_headers (multipart, passes);
390
391         if (g_object_get_data (G_OBJECT (multipart), "multipart-small-reads"))
392                 read_size = 4;
393
394         g_input_stream_read_async (in, buffer, read_size,
395                                    G_PRIORITY_DEFAULT, NULL,
396                                    multipart_read_cb, data);
397 }
398
399 static void
400 multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
401 {
402         GMainLoop *loop = (GMainLoop*)data;
403         SoupRequest *request = SOUP_REQUEST (source);
404         GError *error = NULL;
405         GInputStream *in;
406         SoupMessage *message;
407
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);
412         g_object_unref (in);
413
414         if (error) {
415                 debug_printf (1, "  failed send: %s\n", error->message);
416                 errors++;
417
418                 g_main_loop_quit (loop);
419                 return;
420         }
421
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));
424
425         soup_multipart_input_stream_next_part_async (multipart, G_PRIORITY_DEFAULT, NULL,
426                                                      multipart_next_part_cb, data);
427 }
428
429 static void
430 sync_multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data)
431 {
432         GMainLoop *loop = (GMainLoop*)data;
433         SoupRequest *request = SOUP_REQUEST (source);
434         GError *error = NULL;
435         GInputStream *in;
436         SoupMessage *message;
437         char buffer[READ_BUFFER_SIZE];
438         gsize bytes_read;
439
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);
444         g_object_unref (in);
445
446         if (error) {
447                 debug_printf (1, "  failed send: %s\n", error->message);
448                 errors++;
449
450                 g_main_loop_quit (loop);
451                 return;
452         }
453
454         while (TRUE) {
455                 in = soup_multipart_input_stream_next_part (multipart, NULL, &error);
456
457                 if (error) {
458                         debug_printf (1, "  failed sync next part: %s\n", error->message);
459                         errors++;
460                         g_clear_error (&error);
461                         break;
462                 }
463
464                 if (!in)
465                         break;
466
467                 check_headers (multipart, passes);
468
469                 g_input_stream_read_all (in, (void*)buffer, sizeof (buffer), &bytes_read, NULL, &error);
470
471                 if (error) {
472                         debug_printf (1, "  failed sync read: %s\n", error->message);
473                         errors++;
474                         g_clear_error (&error);
475                         g_object_unref (in);
476                         break;
477                 }
478
479                 check_read (bytes_read, passes);
480
481                 passes++;
482                 g_object_unref (in);
483         }
484
485         if (passes != 4) {
486                 debug_printf (1, "  expected 4 parts, got %u\n", passes);
487                 errors++;
488         }
489
490         g_main_loop_quit (loop);
491         g_object_unref (multipart);
492 }
493
494 static const char*
495 multipart_mode_to_string (MultipartMode mode)
496 {
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";
503
504         return "ASYNC_MULTIPART";
505 }
506
507 static void
508 test_multipart (int headers_expected, int sniffed_expected, MultipartMode multipart_mode)
509 {
510         GError* error = NULL;
511         SoupRequest* request = soup_session_request (session, base_uri_string, &error);
512
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;
517         GHashTable *params;
518         const char *content_type;
519         gboolean message_is_multipart = FALSE;
520
521         debug_printf (1, "test_multipart(%s)\n", multipart_mode_to_string (multipart_mode));
522
523         /* This is used to track the number of parts. */
524         passes = 0;
525
526         /* Force the server to close the connection. */
527         soup_message_headers_append (msg->request_headers,
528                                      "Connection", "close");
529
530         g_signal_connect (msg, "got_headers",
531                           G_CALLBACK (got_headers), &headers_count);
532
533         g_signal_connect (msg, "content-sniffed",
534                           G_CALLBACK (content_sniffed), &sniffed_count);
535
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);
543         else
544                 soup_request_send_async (request, NULL, no_multipart_handling_cb, loop);
545
546         g_main_loop_run (loop);
547
548         content_type = soup_message_headers_get_content_type (msg->response_headers, &params);
549
550         if (content_type &&
551             g_str_has_prefix (content_type, "multipart/") &&
552             g_hash_table_lookup (params, "boundary")) {
553                 message_is_multipart = TRUE;
554         }
555         g_clear_pointer (&params, g_hash_table_unref);
556
557         if (!message_is_multipart) {
558                 debug_printf (1,
559                               "  Header does not indicate a multipart message!\n");
560                 errors++;
561         }
562
563         if (headers_count != headers_expected) {
564                 debug_printf (1,
565                               "  expected got_header %d times, got %d!\n",
566                               headers_expected, headers_count);
567                 errors++;
568         }
569
570         if (sniffed_count != sniffed_expected) {
571                 debug_printf (1,
572                               "  expected content_sniffed %d times, got %d!\n",
573                               sniffed_expected, sniffed_count);
574                 errors++;
575         }
576
577         g_object_unref (msg);
578         g_object_unref (request);
579         g_main_loop_unref (loop);
580 }
581
582 int
583 main (int argc, char **argv)
584 {
585         SoupServer *server;
586
587         test_init (argc, argv, NULL);
588
589         buffer = g_malloc (READ_BUFFER_SIZE);
590
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);
596
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.
599          */
600         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
601                                          "use-thread-context", TRUE,
602                                          "max-conns", 20,
603                                          "max-conns-per-host", 20,
604                                          NULL);
605         soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
606
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);
611
612         soup_uri_free (base_uri);
613         g_free (base_uri_string);
614         g_free (buffer);
615
616         soup_test_session_abort_unref (session);
617         soup_test_server_quit_unref (server);
618         test_cleanup ();
619         return errors != 0;
620 }