soup-message-io: use gio streams rather than SoupSocket
[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 void
25 write_next_chunk (SoupMessage *msg, gpointer user_data)
26 {
27         PutTestData *ptd = user_data;
28
29         debug_printf (2, "  writing chunk %d\n", ptd->next);
30
31         if (ptd->streaming && ptd->next > 0 && ptd->chunks[ptd->next - 1]) {
32                 debug_printf (1, "  error: next chunk requested before last one freed!\n");
33                 errors++;
34         }
35
36         if (ptd->next < G_N_ELEMENTS (ptd->chunks)) {
37                 soup_message_body_append_buffer (msg->request_body,
38                                                  ptd->chunks[ptd->next]);
39                 soup_buffer_free (ptd->chunks[ptd->next]);
40                 ptd->next++;
41         } else
42                 soup_message_body_complete (msg->request_body);
43         soup_session_unpause_message (ptd->session, msg);
44 }
45
46 /* This is not a supported part of the API. Use SOUP_MESSAGE_CAN_REBUILD
47  * instead.
48  */
49 static void
50 write_next_chunk_streaming_hack (SoupMessage *msg, gpointer user_data)
51 {
52         PutTestData *ptd = user_data;
53         SoupBuffer *chunk;
54
55         debug_printf (2, "  freeing chunk at %d\n", ptd->nfreed);
56         chunk = soup_message_body_get_chunk (msg->request_body, ptd->nfreed);
57         if (chunk) {
58                 ptd->nfreed += chunk->length;
59                 soup_message_body_wrote_chunk (msg->request_body, chunk);
60                 soup_buffer_free (chunk);
61         } else {
62                 debug_printf (1, "  error: written chunk does not exist!\n");
63                 errors++;
64         }
65         write_next_chunk (msg, user_data);
66 }
67
68 static void
69 wrote_body_data (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
70 {
71         PutTestData *ptd = user_data;
72
73         debug_printf (2, "  wrote_body_data, %d bytes\n",
74                       (int)chunk->length);
75         ptd->nwrote += chunk->length;
76 }
77
78 static void
79 clear_buffer_ptr (gpointer data)
80 {
81         SoupBuffer **buffer_ptr = data;
82
83         debug_printf (2, "  clearing chunk\n");
84         if (*buffer_ptr) {
85                 (*buffer_ptr)->length = 0;
86                 g_free ((char *)(*buffer_ptr)->data);
87                 *buffer_ptr = NULL;
88         } else {
89                 debug_printf (2, "  chunk is already clear!\n");
90                 errors++;
91         }
92 }
93
94 /* Put a chunk containing @text into *@buffer, set up so that it will
95  * clear out *@buffer when the chunk is freed, allowing us to make sure
96  * the set_accumulate(FALSE) is working.
97  */
98 static void
99 make_put_chunk (SoupBuffer **buffer, const char *text)
100 {
101         *buffer = soup_buffer_new_with_owner (g_strdup (text), strlen (text),
102                                               buffer, clear_buffer_ptr);
103 }
104
105 static void
106 setup_request_body (PutTestData *ptd)
107 {
108         make_put_chunk (&ptd->chunks[0], "one\r\n");
109         make_put_chunk (&ptd->chunks[1], "two\r\n");
110         make_put_chunk (&ptd->chunks[2], "three\r\n");
111         ptd->next = ptd->nwrote = ptd->nfreed = 0;
112 }
113
114 static void
115 restarted_streaming (SoupMessage *msg, gpointer user_data)
116 {
117         PutTestData *ptd = user_data;
118
119         debug_printf (2, "  --restarting--\n");
120
121         /* We're streaming, and we had to restart. So the data need
122          * to be regenerated.
123          */
124         setup_request_body (ptd);
125
126         /* The 302 redirect will turn it into a GET request and
127          * reset the body encoding back to "NONE". Fix that.
128          */
129         soup_message_headers_set_encoding (msg->request_headers,
130                                            SOUP_ENCODING_CHUNKED);
131         msg->method = SOUP_METHOD_PUT;
132 }
133
134 static void
135 restarted_streaming_hack (SoupMessage *msg, gpointer user_data)
136 {
137         restarted_streaming (msg, user_data);
138         soup_message_body_truncate (msg->request_body);
139 }
140
141 typedef enum {
142         HACKY_STREAMING  = (1 << 0),
143         PROPER_STREAMING = (1 << 1),
144         RESTART          = (1 << 2)
145 } RequestTestFlags;
146
147 static void
148 do_request_test (SoupSession *session, SoupURI *base_uri, RequestTestFlags flags)
149 {
150         SoupURI *uri = base_uri;
151         PutTestData ptd;
152         SoupMessage *msg;
153         const char *client_md5, *server_md5;
154         GChecksum *check;
155         int i, length;
156
157         debug_printf (1, "PUT");
158         if (flags & HACKY_STREAMING)
159                 debug_printf (1, " w/ hacky streaming");
160         else if (flags & PROPER_STREAMING)
161                 debug_printf (1, " w/ proper streaming");
162         if (flags & RESTART) {
163                 debug_printf (1, " and restart");
164                 uri = soup_uri_copy (base_uri);
165                 soup_uri_set_path (uri, "/redirect");
166         }
167         debug_printf (1, "\n");
168
169         ptd.session = session;
170         setup_request_body (&ptd);
171         ptd.streaming = flags & (HACKY_STREAMING | PROPER_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         if (flags & HACKY_STREAMING) {
186                 g_signal_connect (msg, "wrote_chunk",
187                                   G_CALLBACK (write_next_chunk_streaming_hack), &ptd);
188                 if (flags & RESTART) {
189                         g_signal_connect (msg, "restarted",
190                                           G_CALLBACK (restarted_streaming_hack), &ptd);
191                 }
192         } else {
193                 g_signal_connect (msg, "wrote_chunk",
194                                   G_CALLBACK (write_next_chunk), &ptd);
195         }
196
197         if (flags & PROPER_STREAMING) {
198                 soup_message_set_flags (msg, SOUP_MESSAGE_CAN_REBUILD);
199                 if (flags & RESTART) {
200                         g_signal_connect (msg, "restarted",
201                                           G_CALLBACK (restarted_streaming), &ptd);
202                 }
203         }
204
205         g_signal_connect (msg, "wrote_headers",
206                           G_CALLBACK (write_next_chunk), &ptd);
207         g_signal_connect (msg, "wrote_body_data",
208                           G_CALLBACK (wrote_body_data), &ptd);
209         soup_session_send_message (session, msg);
210
211         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
212                 debug_printf (1, "  message failed: %d %s\n",
213                               msg->status_code, msg->reason_phrase);
214                 errors++;
215         }
216
217         if (msg->request_body->data) {
218                 debug_printf (1, "  msg->request_body set!\n");
219                 errors++;
220         }
221         if (msg->request_body->length != length || length != ptd.nwrote) {
222                 debug_printf (1, "  sent length mismatch: %d vs %d vs %d\n",
223                               (int)msg->request_body->length, length, ptd.nwrote);
224                 errors++;
225         }
226
227         server_md5 = soup_message_headers_get_one (msg->response_headers,
228                                                    "Content-MD5");
229         if (!server_md5 || strcmp (client_md5, server_md5) != 0) {
230                 debug_printf (1, "  client/server data mismatch: %s vs %s\n",
231                               client_md5, server_md5 ? server_md5 : "(null)");
232                 errors++;
233         }
234
235         g_object_unref (msg);
236         g_checksum_free (check);
237
238         if (uri != base_uri)
239                 soup_uri_free (uri);
240 }
241
242 typedef struct {
243         SoupBuffer *current_chunk;
244         GChecksum *check;
245         int length;
246 } GetTestData;
247
248 static SoupBuffer *
249 chunk_allocator (SoupMessage *msg, gsize max_len, gpointer user_data)
250 {
251         GetTestData *gtd = user_data;
252
253         debug_printf (2, "  allocating chunk\n");
254
255         if (gtd->current_chunk) {
256                 debug_printf (1, "  error: next chunk allocated before last one freed!\n");
257                 errors++;
258         }
259         gtd->current_chunk = soup_buffer_new_with_owner (g_malloc (6), 6,
260                                                          &gtd->current_chunk,
261                                                          clear_buffer_ptr);
262         return gtd->current_chunk;
263 }
264
265 static void
266 got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
267 {
268         GetTestData *gtd = user_data;
269
270         debug_printf (2, "  got chunk, %d bytes\n",
271                       (int)chunk->length);
272         if (chunk != gtd->current_chunk) {
273                 debug_printf (1, "chunk mismatch! %p vs %p\n",
274                               chunk, gtd->current_chunk);
275         }
276
277         g_checksum_update (gtd->check, (guchar *)chunk->data, chunk->length);
278         gtd->length += chunk->length;
279 }
280
281 static void
282 do_response_test (SoupSession *session, SoupURI *base_uri)
283 {
284         GetTestData gtd;
285         SoupMessage *msg;
286         const char *client_md5, *server_md5;
287
288         debug_printf (1, "GET\n");
289
290         gtd.current_chunk = NULL;
291         gtd.length = 0;
292         gtd.check = g_checksum_new (G_CHECKSUM_MD5);
293
294         msg = soup_message_new_from_uri ("GET", base_uri);
295         soup_message_body_set_accumulate (msg->response_body, FALSE);
296         soup_message_set_chunk_allocator (msg, chunk_allocator, &gtd, NULL);
297         g_signal_connect (msg, "got_chunk",
298                           G_CALLBACK (got_chunk), &gtd);
299         soup_session_send_message (session, msg);
300
301         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
302                 debug_printf (1, "  message failed: %d %s\n",
303                               msg->status_code, msg->reason_phrase);
304                 errors++;
305         }
306
307         if (msg->response_body->data) {
308                 debug_printf (1, "  msg->response_body set!\n");
309                 errors++;
310         }
311         if (soup_message_headers_get_content_length (msg->response_headers) != gtd.length) {
312                 debug_printf (1, "  received length mismatch: %d vs %d\n",
313                               (int)soup_message_headers_get_content_length (msg->response_headers), gtd.length);
314                 errors++;
315         }
316
317         client_md5 = g_checksum_get_string (gtd.check);
318         server_md5 = soup_message_headers_get_one (msg->response_headers,
319                                                    "Content-MD5");
320         if (!server_md5 || strcmp (client_md5, server_md5) != 0) {
321                 debug_printf (1, "  client/server data mismatch: %s vs %s\n",
322                               client_md5, server_md5 ? server_md5 : "(null)");
323                 errors++;
324         }
325
326         g_object_unref (msg);
327         g_checksum_free (gtd.check);
328 }
329
330 /* Make sure TEMPORARY buffers are handled properly with non-accumulating
331  * message bodies. Part of https://bugs.webkit.org/show_bug.cgi?id=18343
332  */
333
334 static void
335 temp_test_wrote_chunk (SoupMessage *msg, gpointer session)
336 {
337         SoupBuffer *chunk;
338
339         chunk = soup_message_body_get_chunk (msg->request_body, 5);
340
341         /* When the bug is present, the second chunk will also be
342          * discarded after the first is written, which will cause
343          * the I/O to stall since soup-message-io will think it's
344          * done, but it hasn't written Content-Length bytes yet.
345          */
346         if (!chunk) {
347                 debug_printf (1, "  Lost second chunk!\n");
348                 errors++;
349                 soup_session_abort (session);
350         } else
351                 soup_buffer_free (chunk);
352
353         g_signal_handlers_disconnect_by_func (msg, temp_test_wrote_chunk, session);
354 }
355
356 static void
357 do_temporary_test (SoupSession *session, SoupURI *base_uri)
358 {
359         SoupMessage *msg;
360         char *client_md5;
361         const char *server_md5;
362
363         debug_printf (1, "PUT w/ temporary buffers\n");
364
365         msg = soup_message_new_from_uri ("PUT", base_uri);
366         soup_message_body_append (msg->request_body, SOUP_MEMORY_TEMPORARY,
367                                   "one\r\n", 5);
368         soup_message_body_append (msg->request_body, SOUP_MEMORY_STATIC,
369                                   "two\r\n", 5);
370         soup_message_body_set_accumulate (msg->request_body, FALSE);
371
372         client_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5,
373                                                     "one\r\ntwo\r\n", 10);
374         g_signal_connect (msg, "wrote_chunk",
375                           G_CALLBACK (temp_test_wrote_chunk), session);
376         soup_session_send_message (session, msg);
377
378         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
379                 debug_printf (1, "  message failed: %d %s\n",
380                               msg->status_code, msg->reason_phrase);
381                 errors++;
382         }
383
384         server_md5 = soup_message_headers_get_one (msg->response_headers,
385                                                    "Content-MD5");
386         if (!server_md5 || strcmp (client_md5, server_md5) != 0) {
387                 debug_printf (1, "  client/server data mismatch: %s vs %s\n",
388                               client_md5, server_md5 ? server_md5 : "(null)");
389                 errors++;
390         }
391
392         g_free (client_md5);
393         g_object_unref (msg);
394 }
395
396 #define LARGE_CHUNK_SIZE 1000000
397
398 typedef struct {
399         SoupBuffer *buf;
400         gsize offset;
401 } LargeChunkData;
402
403 static void
404 large_wrote_body_data (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
405 {
406         LargeChunkData *lcd = user_data;
407
408         if (memcmp (chunk->data, lcd->buf->data + lcd->offset, chunk->length) != 0) {
409                 debug_printf (1, "  chunk data mismatch at %ld\n", (long)lcd->offset);
410                 errors++;
411         } else
412                 debug_printf (2, "  chunk data match at %ld\n", (long)lcd->offset);
413         lcd->offset += chunk->length;
414 }
415
416 static void
417 do_large_chunk_test (SoupSession *session, SoupURI *base_uri)
418 {
419         SoupMessage *msg;
420         char *buf_data;
421         int i;
422         LargeChunkData lcd;
423
424         debug_printf (1, "PUT w/ large chunk\n");
425
426         msg = soup_message_new_from_uri ("PUT", base_uri);
427
428         buf_data = g_malloc0 (LARGE_CHUNK_SIZE);
429         for (i = 0; i < LARGE_CHUNK_SIZE; i++)
430                 buf_data[i] = i & 0xFF;
431         lcd.buf = soup_buffer_new (SOUP_MEMORY_TAKE, buf_data, LARGE_CHUNK_SIZE);
432         lcd.offset = 0;
433         soup_message_body_append_buffer (msg->request_body, lcd.buf);
434         soup_message_body_set_accumulate (msg->request_body, FALSE);
435
436         g_signal_connect (msg, "wrote_body_data",
437                           G_CALLBACK (large_wrote_body_data), &lcd);
438         soup_session_send_message (session, msg);
439
440         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
441                 debug_printf (1, "  message failed: %d %s\n",
442                               msg->status_code, msg->reason_phrase);
443                 errors++;
444         }
445
446         soup_buffer_free (lcd.buf);
447         g_object_unref (msg);
448 }
449
450 static void
451 do_chunk_tests (SoupURI *base_uri)
452 {
453         SoupSession *session;
454
455         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
456         do_request_test (session, base_uri, 0);
457         debug_printf (2, "\n\n");
458         do_request_test (session, base_uri, PROPER_STREAMING);
459         debug_printf (2, "\n\n");
460         do_request_test (session, base_uri, PROPER_STREAMING | RESTART);
461         debug_printf (2, "\n\n");
462         do_request_test (session, base_uri, HACKY_STREAMING);
463         debug_printf (2, "\n\n");
464         do_request_test (session, base_uri, HACKY_STREAMING | RESTART);
465         debug_printf (2, "\n\n");
466         do_response_test (session, base_uri);
467         debug_printf (2, "\n\n");
468         do_temporary_test (session, base_uri);
469         debug_printf (2, "\n\n");
470         do_large_chunk_test (session, base_uri);
471         soup_test_session_abort_unref (session);
472 }
473
474 static void
475 server_callback (SoupServer *server, SoupMessage *msg,
476                  const char *path, GHashTable *query,
477                  SoupClientContext *context, gpointer data)
478 {
479         SoupMessageBody *md5_body;
480         char *md5;
481
482         if (g_str_has_prefix (path, "/redirect")) {
483                 soup_message_set_redirect (msg, SOUP_STATUS_FOUND, "/");
484                 return;
485         }
486
487         if (msg->method == SOUP_METHOD_GET) {
488                 soup_message_set_response (msg, "text/plain",
489                                            SOUP_MEMORY_STATIC,
490                                            "three\r\ntwo\r\none\r\n",
491                                            strlen ("three\r\ntwo\r\none\r\n"));
492                 soup_buffer_free (soup_message_body_flatten (msg->response_body));
493                 md5_body = msg->response_body;
494                 soup_message_set_status (msg, SOUP_STATUS_OK);
495         } else if (msg->method == SOUP_METHOD_PUT) {
496                 soup_message_set_status (msg, SOUP_STATUS_CREATED);
497                 md5_body = msg->request_body;
498         } else {
499                 soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
500                 return;
501         }
502
503         md5 = g_compute_checksum_for_data (G_CHECKSUM_MD5,
504                                            (guchar *)md5_body->data,
505                                            md5_body->length);
506         soup_message_headers_append (msg->response_headers,
507                                      "Content-MD5", md5);
508         g_free (md5);
509 }
510
511 int
512 main (int argc, char **argv)
513 {
514         GMainLoop *loop;
515         SoupServer *server;
516         guint port;
517         SoupURI *base_uri;
518
519         test_init (argc, argv, NULL);
520
521         server = soup_test_server_new (TRUE);
522         soup_server_add_handler (server, NULL,
523                                  server_callback, NULL, NULL);
524         port =  soup_server_get_port (server);
525
526         loop = g_main_loop_new (NULL, TRUE);
527
528         base_uri = soup_uri_new ("http://127.0.0.1");
529         soup_uri_set_port (base_uri, port);
530         do_chunk_tests (base_uri);
531         soup_uri_free (base_uri);
532
533         g_main_loop_unref (loop);
534         soup_test_server_quit_unref (server);
535
536         test_cleanup ();
537         return errors != 0;
538 }