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