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