Add restarted streaming chunk-test, which is where it really starts to get fun
[platform/upstream/libsoup.git] / tests / chunk-test.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2008 Red Hat, Inc.
4  */
5
6 #include "config.h"
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11
12 #include <glib.h>
13 #include <libsoup/soup.h>
14
15 #include "test-utils.h"
16
17 typedef struct {
18         SoupSession *session;
19         SoupBuffer *chunks[3];
20         int next, nwrote, nfreed;
21         gboolean streaming;
22 } PutTestData;
23
24 static SoupBuffer *
25 error_chunk_allocator (SoupMessage *msg, gsize max_len, gpointer user_data)
26 {
27         /* This should never be called, because there is no response body. */
28         debug_printf (1, "  error_chunk_allocator called!\n");
29         errors++;
30         return soup_buffer_new (SOUP_MEMORY_TAKE, g_malloc (100), 100);
31 }
32
33 static void
34 write_next_chunk (SoupMessage *msg, gpointer user_data)
35 {
36         PutTestData *ptd = user_data;
37
38         debug_printf (2, "  writing chunk %d\n", ptd->next);
39
40         if (ptd->next > 0 && ptd->chunks[ptd->next - 1]) {
41                 if (ptd->streaming) {
42                         debug_printf (1, "  error: next chunk requested before last one freed!\n");
43                         errors++;
44                 } else {
45                         debug_printf (0, "  ignoring bug in test case... FIXME!\n");
46                 }
47         }
48
49         if (ptd->next < G_N_ELEMENTS (ptd->chunks)) {
50                 soup_message_body_append_buffer (msg->request_body,
51                                                  ptd->chunks[ptd->next]);
52                 soup_buffer_free (ptd->chunks[ptd->next]);
53                 ptd->next++;
54         } else
55                 soup_message_body_complete (msg->request_body);
56         soup_session_unpause_message (ptd->session, msg);
57 }
58
59 /* This is not a supported part of the API. Don't try this at home */
60 static void
61 write_next_chunk_streaming_hack (SoupMessage *msg, gpointer user_data)
62 {
63         PutTestData *ptd = user_data;
64         SoupBuffer *chunk;
65
66         debug_printf (2, "  freeing chunk at %d\n", ptd->nfreed);
67         chunk = soup_message_body_get_chunk (msg->request_body, ptd->nfreed);
68         if (chunk) {
69                 ptd->nfreed += chunk->length;
70                 soup_message_body_wrote_chunk (msg->request_body, chunk);
71                 soup_buffer_free (chunk);
72         } else {
73                 debug_printf (1, "  error: written chunk does not exist!\n");
74                 errors++;
75         }
76         write_next_chunk (msg, user_data);
77 }
78
79 static void
80 wrote_body_data (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
81 {
82         PutTestData *ptd = user_data;
83
84         debug_printf (2, "  wrote_body_data, %d bytes\n",
85                       (int)chunk->length);
86         ptd->nwrote += chunk->length;
87 }
88
89 static void
90 clear_buffer_ptr (gpointer data)
91 {
92         SoupBuffer **buffer_ptr = data;
93
94         debug_printf (2, "  clearing chunk\n");
95         if (*buffer_ptr) {
96                 (*buffer_ptr)->length = 0;
97                 g_free ((char *)(*buffer_ptr)->data);
98                 *buffer_ptr = NULL;
99         } else {
100                 debug_printf (2, "  chunk is already clear!\n");
101                 errors++;
102         }
103 }
104
105 /* Put a chunk containing @text into *@buffer, set up so that it will
106  * clear out *@buffer when the chunk is freed, allowing us to make sure
107  * the set_accumulate(FALSE) is working.
108  */
109 static void
110 make_put_chunk (SoupBuffer **buffer, const char *text)
111 {
112         *buffer = soup_buffer_new_with_owner (g_strdup (text), strlen (text),
113                                               buffer, clear_buffer_ptr);
114 }
115
116 static void
117 restarted_streaming_hack (SoupMessage *msg, gpointer user_data)
118 {
119         PutTestData *ptd = user_data;
120
121         /* We're streaming, and we had to restart. So the data need
122            to be regenerated. That's the *point* of this test; we don't
123            *want* it to accumulate in the request body */
124         make_put_chunk (&ptd->chunks[0], "one\r\n");
125         make_put_chunk (&ptd->chunks[1], "two\r\n");
126         make_put_chunk (&ptd->chunks[2], "three\r\n");
127         ptd->next = ptd->nwrote = ptd->nfreed = 0;
128
129         debug_printf (2, "  truncating request body on restart\n");
130         soup_message_body_truncate (msg->request_body);
131
132         /* The redirect will turn it into a GET request. Fix that... */
133         soup_message_headers_set_encoding (msg->request_headers, SOUP_ENCODING_CHUNKED);
134         msg->method = SOUP_METHOD_PUT;
135 }
136
137 static void
138 do_request_test (SoupSession *session, SoupURI *base_uri, int test)
139 {
140         SoupURI *uri = base_uri;
141         PutTestData ptd;
142         SoupMessage *msg;
143         const char *client_md5, *server_md5;
144         GChecksum *check;
145         int i, length;
146         gboolean streaming = FALSE;
147
148         switch (test) {
149         case 0:
150                 debug_printf (1, "PUT\n");
151                 break;
152
153         case 1:
154                 debug_printf (1, "PUT w/ streaming\n");
155                 streaming = TRUE;
156                 break;
157
158         case 2:
159                 debug_printf (1, "PUT w/ streaming and restart\n");
160                 streaming = TRUE;
161                 uri = soup_uri_copy (base_uri);
162                 soup_uri_set_path (uri, "/redirect");
163                 break;
164         }
165
166         ptd.session = session;
167         make_put_chunk (&ptd.chunks[0], "one\r\n");
168         make_put_chunk (&ptd.chunks[1], "two\r\n");
169         make_put_chunk (&ptd.chunks[2], "three\r\n");
170         ptd.next = ptd.nwrote = ptd.nfreed = 0;
171         ptd.streaming = streaming;
172
173         check = g_checksum_new (G_CHECKSUM_MD5);
174         length = 0;
175         for (i = 0; i < 3; i++) {
176                 g_checksum_update (check, (guchar *)ptd.chunks[i]->data,
177                                    ptd.chunks[i]->length);
178                 length += ptd.chunks[i]->length;
179         }
180         client_md5 = g_checksum_get_string (check);
181
182         msg = soup_message_new_from_uri ("PUT", uri);
183         soup_message_headers_set_encoding (msg->request_headers, SOUP_ENCODING_CHUNKED);
184         soup_message_body_set_accumulate (msg->request_body, FALSE);
185         soup_message_set_chunk_allocator (msg, error_chunk_allocator, NULL, NULL);
186         if (streaming) {
187                 g_signal_connect (msg, "wrote_chunk",
188                                   G_CALLBACK (write_next_chunk_streaming_hack), &ptd);
189                 g_signal_connect (msg, "restarted",
190                                   G_CALLBACK (restarted_streaming_hack), &ptd);
191         } else {
192                 g_signal_connect (msg, "wrote_chunk",
193                                   G_CALLBACK (write_next_chunk), &ptd);
194         }
195         g_signal_connect (msg, "wrote_headers",
196                           G_CALLBACK (write_next_chunk), &ptd);
197         g_signal_connect (msg, "wrote_body_data",
198                           G_CALLBACK (wrote_body_data), &ptd);
199         soup_session_send_message (session, msg);
200
201         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
202                 debug_printf (1, "  message failed: %d %s\n",
203                               msg->status_code, msg->reason_phrase);
204                 errors++;
205         }
206
207         if (msg->request_body->data) {
208                 debug_printf (1, "  msg->request_body set!\n");
209                 errors++;
210         }
211         if (msg->request_body->length != length || length != ptd.nwrote) {
212                 debug_printf (1, "  sent length mismatch: %d vs %d vs %d\n",
213                               (int)msg->request_body->length, length, ptd.nwrote);
214                 errors++;
215         }
216
217         server_md5 = soup_message_headers_get_one (msg->response_headers,
218                                                    "Content-MD5");
219         if (!server_md5 || strcmp (client_md5, server_md5) != 0) {
220                 debug_printf (1, "  client/server data mismatch: %s vs %s\n",
221                               client_md5, server_md5 ? server_md5 : "(null)");
222                 errors++;
223         }
224
225         g_object_unref (msg);
226         g_checksum_free (check);
227
228         if (uri != base_uri)
229                 soup_uri_free (uri);
230 }
231
232 typedef struct {
233         SoupBuffer *current_chunk;
234         GChecksum *check;
235         int length;
236 } GetTestData;
237
238 static SoupBuffer *
239 chunk_allocator (SoupMessage *msg, gsize max_len, gpointer user_data)
240 {
241         GetTestData *gtd = user_data;
242
243         debug_printf (2, "  allocating chunk\n");
244
245         if (gtd->current_chunk) {
246                 debug_printf (1, "  error: next chunk allocated before last one freed!\n");
247                 errors++;
248         }
249         gtd->current_chunk = soup_buffer_new_with_owner (g_malloc (6), 6,
250                                                          &gtd->current_chunk,
251                                                          clear_buffer_ptr);
252         return gtd->current_chunk;
253 }
254
255 static void
256 got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
257 {
258         GetTestData *gtd = user_data;
259
260         debug_printf (2, "  got chunk, %d bytes\n",
261                       (int)chunk->length);
262         if (chunk != gtd->current_chunk) {
263                 debug_printf (1, "chunk mismatch! %p vs %p\n",
264                               chunk, gtd->current_chunk);
265         }
266
267         g_checksum_update (gtd->check, (guchar *)chunk->data, chunk->length);
268         gtd->length += chunk->length;
269 }
270
271 static void
272 do_response_test (SoupSession *session, SoupURI *base_uri)
273 {
274         GetTestData gtd;
275         SoupMessage *msg;
276         const char *client_md5, *server_md5;
277
278         debug_printf (1, "GET\n");
279
280         gtd.current_chunk = NULL;
281         gtd.length = 0;
282         gtd.check = g_checksum_new (G_CHECKSUM_MD5);
283
284         msg = soup_message_new_from_uri ("GET", base_uri);
285         soup_message_body_set_accumulate (msg->response_body, FALSE);
286         soup_message_set_chunk_allocator (msg, chunk_allocator, &gtd, NULL);
287         g_signal_connect (msg, "got_chunk",
288                           G_CALLBACK (got_chunk), &gtd);
289         soup_session_send_message (session, msg);
290
291         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
292                 debug_printf (1, "  message failed: %d %s\n",
293                               msg->status_code, msg->reason_phrase);
294                 errors++;
295         }
296
297         if (msg->response_body->data) {
298                 debug_printf (1, "  msg->response_body set!\n");
299                 errors++;
300         }
301         if (soup_message_headers_get_content_length (msg->response_headers) != gtd.length) {
302                 debug_printf (1, "  received length mismatch: %d vs %d\n",
303                               (int)soup_message_headers_get_content_length (msg->response_headers), gtd.length);
304                 errors++;
305         }
306
307         client_md5 = g_checksum_get_string (gtd.check);
308         server_md5 = soup_message_headers_get_one (msg->response_headers,
309                                                    "Content-MD5");
310         if (!server_md5 || strcmp (client_md5, server_md5) != 0) {
311                 debug_printf (1, "  client/server data mismatch: %s vs %s\n",
312                               client_md5, server_md5 ? server_md5 : "(null)");
313                 errors++;
314         }
315
316         g_object_unref (msg);
317         g_checksum_free (gtd.check);
318 }
319
320 /* Make sure TEMPORARY buffers are handled properly with non-accumulating
321  * message bodies. Part of https://bugs.webkit.org/show_bug.cgi?id=18343
322  */
323
324 static void
325 temp_test_wrote_chunk (SoupMessage *msg, gpointer session)
326 {
327         SoupBuffer *chunk;
328
329         chunk = soup_message_body_get_chunk (msg->request_body, 5);
330
331         /* When the bug is present, the second chunk will also be
332          * discarded after the first is written, which will cause
333          * the I/O to stall since soup-message-io will think it's
334          * done, but it hasn't written Content-Length bytes yet.
335          */
336         if (!chunk) {
337                 debug_printf (1, "  Lost second chunk!\n");
338                 errors++;
339                 soup_session_abort (session);
340         } else
341                 soup_buffer_free (chunk);
342
343         g_signal_handlers_disconnect_by_func (msg, temp_test_wrote_chunk, session);
344 }
345
346 static void
347 do_temporary_test (SoupSession *session, SoupURI *base_uri)
348 {
349         SoupMessage *msg;
350         char *client_md5;
351         const char *server_md5;
352
353         debug_printf (1, "PUT w/ temporary buffers\n");
354
355         msg = soup_message_new_from_uri ("PUT", base_uri);
356         soup_message_body_append (msg->request_body, SOUP_MEMORY_TEMPORARY,
357                                   "one\r\n", 5);
358         soup_message_body_append (msg->request_body, SOUP_MEMORY_STATIC,
359                                   "two\r\n", 5);
360         soup_message_body_set_accumulate (msg->request_body, FALSE);
361
362         client_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5,
363                                                     "one\r\ntwo\r\n", 10);
364         g_signal_connect (msg, "wrote_chunk",
365                           G_CALLBACK (temp_test_wrote_chunk), session);
366         soup_session_send_message (session, msg);
367
368         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
369                 debug_printf (1, "  message failed: %d %s\n",
370                               msg->status_code, msg->reason_phrase);
371                 errors++;
372         }
373
374         server_md5 = soup_message_headers_get_one (msg->response_headers,
375                                                    "Content-MD5");
376         if (!server_md5 || strcmp (client_md5, server_md5) != 0) {
377                 debug_printf (1, "  client/server data mismatch: %s vs %s\n",
378                               client_md5, server_md5 ? server_md5 : "(null)");
379                 errors++;
380         }
381
382         g_free (client_md5);
383         g_object_unref (msg);
384 }
385
386 static void
387 do_chunk_tests (SoupURI *base_uri)
388 {
389         SoupSession *session;
390
391         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
392         do_request_test (session, base_uri, 0);
393         debug_printf (2, "\n\n");
394         do_request_test (session, base_uri, 1);
395         debug_printf (2, "\n\n");
396         do_request_test (session, base_uri, 2);
397         debug_printf (2, "\n\n");
398         do_response_test (session, base_uri);
399         debug_printf (2, "\n\n");
400         do_temporary_test (session, base_uri);
401         soup_test_session_abort_unref (session);
402 }
403
404 static void
405 server_callback (SoupServer *server, SoupMessage *msg,
406                  const char *path, GHashTable *query,
407                  SoupClientContext *context, gpointer data)
408 {
409         SoupMessageBody *md5_body;
410         char *md5;
411
412         if (g_str_has_prefix (path, "/redirect")) {
413                 soup_message_set_status (msg, SOUP_STATUS_FOUND);
414                 soup_message_headers_replace (msg->response_headers,
415                                               "Location", "/");
416                 return;
417         }
418
419         if (msg->method == SOUP_METHOD_GET) {
420                 soup_message_set_response (msg, "text/plain",
421                                            SOUP_MEMORY_STATIC,
422                                            "three\r\ntwo\r\none\r\n",
423                                            strlen ("three\r\ntwo\r\none\r\n"));
424                 soup_buffer_free (soup_message_body_flatten (msg->response_body));
425                 md5_body = msg->response_body;
426                 soup_message_set_status (msg, SOUP_STATUS_OK);
427         } else if (msg->method == SOUP_METHOD_PUT) {
428                 soup_message_set_status (msg, SOUP_STATUS_CREATED);
429                 md5_body = msg->request_body;
430         } else {
431                 soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
432                 return;
433         }
434
435         md5 = g_compute_checksum_for_data (G_CHECKSUM_MD5,
436                                            (guchar *)md5_body->data,
437                                            md5_body->length);
438         soup_message_headers_append (msg->response_headers,
439                                      "Content-MD5", md5);
440         g_free (md5);
441 }
442
443 int
444 main (int argc, char **argv)
445 {
446         GMainLoop *loop;
447         SoupServer *server;
448         guint port;
449         SoupURI *base_uri;
450
451         test_init (argc, argv, NULL);
452
453         server = soup_test_server_new (TRUE);
454         soup_server_add_handler (server, NULL,
455                                  server_callback, NULL, NULL);
456         port =  soup_server_get_port (server);
457
458         loop = g_main_loop_new (NULL, TRUE);
459
460         base_uri = soup_uri_new ("http://127.0.0.1");
461         soup_uri_set_port (base_uri, port);
462         do_chunk_tests (base_uri);
463         soup_uri_free (base_uri);
464
465         g_main_loop_unref (loop);
466         soup_test_server_quit_unref (server);
467
468         test_cleanup ();
469         return errors != 0;
470 }