Bug 576760 – soup_message_headers_get_content_type returns bad headers
[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;
21 } PutTestData;
22
23 static SoupBuffer *
24 error_chunk_allocator (SoupMessage *msg, gsize max_len, gpointer user_data)
25 {
26         /* This should never be called, because there is no response body. */
27         debug_printf (1, "  error_chunk_allocator called!\n");
28         errors++;
29         return soup_buffer_new (SOUP_MEMORY_TAKE, g_malloc (100), 100);
30 }
31
32 static void
33 write_next_chunk (SoupMessage *msg, gpointer user_data)
34 {
35         PutTestData *ptd = user_data;
36
37         debug_printf (2, "  writing chunk\n");
38
39         if (ptd->next > 0 && ptd->chunks[ptd->next - 1]) {
40                 debug_printf (1, "  error: next chunk requested before last one freed!\n");
41                 errors++;
42         }
43
44         if (ptd->next < G_N_ELEMENTS (ptd->chunks)) {
45                 soup_message_body_append_buffer (msg->request_body,
46                                                  ptd->chunks[ptd->next]);
47                 soup_buffer_free (ptd->chunks[ptd->next]);
48                 ptd->next++;
49         } else
50                 soup_message_body_complete (msg->request_body);
51         soup_session_unpause_message (ptd->session, msg);
52 }
53
54 static void
55 wrote_body_data (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
56 {
57         PutTestData *ptd = user_data;
58
59         debug_printf (2, "  wrote_body_data, %d bytes\n",
60                       (int)chunk->length);
61         ptd->nwrote += chunk->length;
62 }
63
64 static void
65 clear_buffer_ptr (gpointer data)
66 {
67         SoupBuffer **buffer_ptr = data;
68
69         debug_printf (2, "  clearing chunk\n");
70         if (*buffer_ptr) {
71                 (*buffer_ptr)->length = 0;
72                 g_free ((char *)(*buffer_ptr)->data);
73                 *buffer_ptr = NULL;
74         } else {
75                 debug_printf (2, "  chunk is already clear!\n");
76                 errors++;
77         }
78 }
79
80 /* Put a chunk containing @text into *@buffer, set up so that it will
81  * clear out *@buffer when the chunk is freed, allowing us to make sure
82  * the set_accumulate(FALSE) is working.
83  */
84 static void
85 make_put_chunk (SoupBuffer **buffer, const char *text)
86 {
87         *buffer = soup_buffer_new_with_owner (g_strdup (text), strlen (text),
88                                               buffer, clear_buffer_ptr);
89 }
90
91 static void
92 do_request_test (SoupSession *session, SoupURI *base_uri)
93 {
94         PutTestData ptd;
95         SoupMessage *msg;
96         const char *client_md5, *server_md5;
97         GChecksum *check;
98         int i, length;
99
100         debug_printf (1, "PUT\n");
101
102         ptd.session = session;
103         make_put_chunk (&ptd.chunks[0], "one\r\n");
104         make_put_chunk (&ptd.chunks[1], "two\r\n");
105         make_put_chunk (&ptd.chunks[2], "three\r\n");
106         ptd.next = ptd.nwrote = 0;
107
108         check = g_checksum_new (G_CHECKSUM_MD5);
109         length = 0;
110         for (i = 0; i < 3; i++) {
111                 g_checksum_update (check, (guchar *)ptd.chunks[i]->data,
112                                    ptd.chunks[i]->length);
113                 length += ptd.chunks[i]->length;
114         }
115         client_md5 = g_checksum_get_string (check);
116
117         msg = soup_message_new_from_uri ("PUT", base_uri);
118         soup_message_headers_set_encoding (msg->request_headers, SOUP_ENCODING_CHUNKED);
119         soup_message_body_set_accumulate (msg->request_body, FALSE);
120         soup_message_set_chunk_allocator (msg, error_chunk_allocator, NULL, NULL);
121         g_signal_connect (msg, "wrote_headers",
122                           G_CALLBACK (write_next_chunk), &ptd);
123         g_signal_connect (msg, "wrote_chunk",
124                           G_CALLBACK (write_next_chunk), &ptd);
125         g_signal_connect (msg, "wrote_body_data",
126                           G_CALLBACK (wrote_body_data), &ptd);
127         soup_session_send_message (session, msg);
128
129         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
130                 debug_printf (1, "  message failed: %d %s\n",
131                               msg->status_code, msg->reason_phrase);
132                 errors++;
133         }
134
135         if (msg->request_body->data) {
136                 debug_printf (1, "  msg->request_body set!\n");
137                 errors++;
138         }
139         if (msg->request_body->length != length || length != ptd.nwrote) {
140                 debug_printf (1, "  sent length mismatch: %d vs %d vs %d\n",
141                               (int)msg->request_body->length, length, ptd.nwrote);
142                 errors++;
143         }
144
145         server_md5 = soup_message_headers_get_one (msg->response_headers,
146                                                    "Content-MD5");
147         if (!server_md5 || strcmp (client_md5, server_md5) != 0) {
148                 debug_printf (1, "  client/server data mismatch: %s vs %s\n",
149                               client_md5, server_md5 ? server_md5 : "(null)");
150                 errors++;
151         }
152
153         g_object_unref (msg);
154         g_checksum_free (check);
155 }
156
157 typedef struct {
158         SoupBuffer *current_chunk;
159         GChecksum *check;
160         int length;
161 } GetTestData;
162
163 static SoupBuffer *
164 chunk_allocator (SoupMessage *msg, gsize max_len, gpointer user_data)
165 {
166         GetTestData *gtd = user_data;
167
168         debug_printf (2, "  allocating chunk\n");
169
170         if (gtd->current_chunk) {
171                 debug_printf (1, "  error: next chunk allocated before last one freed!\n");
172                 errors++;
173         }
174         gtd->current_chunk = soup_buffer_new_with_owner (g_malloc (6), 6,
175                                                          &gtd->current_chunk,
176                                                          clear_buffer_ptr);
177         return gtd->current_chunk;
178 }
179
180 static void
181 got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
182 {
183         GetTestData *gtd = user_data;
184
185         debug_printf (2, "  got chunk, %d bytes\n",
186                       (int)chunk->length);
187         if (chunk != gtd->current_chunk) {
188                 debug_printf (1, "chunk mismatch! %p vs %p\n",
189                               chunk, gtd->current_chunk);
190         }
191
192         g_checksum_update (gtd->check, (guchar *)chunk->data, chunk->length);
193         gtd->length += chunk->length;
194 }
195
196 static void
197 do_response_test (SoupSession *session, SoupURI *base_uri)
198 {
199         GetTestData gtd;
200         SoupMessage *msg;
201         const char *client_md5, *server_md5;
202
203         debug_printf (1, "GET\n");
204
205         gtd.current_chunk = NULL;
206         gtd.length = 0;
207         gtd.check = g_checksum_new (G_CHECKSUM_MD5);
208
209         msg = soup_message_new_from_uri ("GET", base_uri);
210         soup_message_body_set_accumulate (msg->response_body, FALSE);
211         soup_message_set_chunk_allocator (msg, chunk_allocator, &gtd, NULL);
212         g_signal_connect (msg, "got_chunk",
213                           G_CALLBACK (got_chunk), &gtd);
214         soup_session_send_message (session, msg);
215
216         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
217                 debug_printf (1, "  message failed: %d %s\n",
218                               msg->status_code, msg->reason_phrase);
219                 errors++;
220         }
221
222         if (msg->response_body->data) {
223                 debug_printf (1, "  msg->response_body set!\n");
224                 errors++;
225         }
226         if (soup_message_headers_get_content_length (msg->response_headers) != gtd.length) {
227                 debug_printf (1, "  received length mismatch: %d vs %d\n",
228                               (int)soup_message_headers_get_content_length (msg->response_headers), gtd.length);
229                 errors++;
230         }
231
232         client_md5 = g_checksum_get_string (gtd.check);
233         server_md5 = soup_message_headers_get_one (msg->response_headers,
234                                                    "Content-MD5");
235         if (!server_md5 || strcmp (client_md5, server_md5) != 0) {
236                 debug_printf (1, "  client/server data mismatch: %s vs %s\n",
237                               client_md5, server_md5 ? server_md5 : "(null)");
238                 errors++;
239         }
240
241         g_object_unref (msg);
242         g_checksum_free (gtd.check);
243 }
244
245 /* Make sure TEMPORARY buffers are handled properly with non-accumulating
246  * message bodies. Part of https://bugs.webkit.org/show_bug.cgi?id=18343
247  */
248
249 static void
250 temp_test_wrote_chunk (SoupMessage *msg, gpointer session)
251 {
252         /* When the bug is present, the second chunk will also be
253          * discarded after the first is written, which will cause
254          * the I/O to stall since soup-message-io will think it's
255          * done, but it hasn't written Content-Length bytes yet.
256          * So add in another chunk to keep it going.
257          */
258         if (!soup_message_body_get_chunk (msg->request_body,
259                                           5)) {
260                 debug_printf (1, "  Lost second chunk!\n");
261                 errors++;
262                 soup_session_abort (session);
263         }
264
265         g_signal_handlers_disconnect_by_func (msg, temp_test_wrote_chunk, session);
266 }
267
268 static void
269 do_temporary_test (SoupSession *session, SoupURI *base_uri)
270 {
271         SoupMessage *msg;
272         const char *client_md5, *server_md5;
273
274         debug_printf (1, "PUT w/ temporary buffers\n");
275
276         msg = soup_message_new_from_uri ("PUT", base_uri);
277         soup_message_body_append (msg->request_body, SOUP_MEMORY_TEMPORARY,
278                                   "one\r\n", 5);
279         soup_message_body_append (msg->request_body, SOUP_MEMORY_STATIC,
280                                   "two\r\n", 5);
281         soup_message_body_set_accumulate (msg->request_body, FALSE);
282
283         client_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5,
284                                                     "one\r\ntwo\r\n", 10);
285         g_signal_connect (msg, "wrote_chunk",
286                           G_CALLBACK (temp_test_wrote_chunk), session);
287         soup_session_send_message (session, msg);
288
289         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
290                 debug_printf (1, "  message failed: %d %s\n",
291                               msg->status_code, msg->reason_phrase);
292                 errors++;
293         }
294
295         server_md5 = soup_message_headers_get_one (msg->response_headers,
296                                                    "Content-MD5");
297         if (!server_md5 || strcmp (client_md5, server_md5) != 0) {
298                 debug_printf (1, "  client/server data mismatch: %s vs %s\n",
299                               client_md5, server_md5 ? server_md5 : "(null)");
300                 errors++;
301         }
302
303         g_object_unref (msg);
304 }
305
306 static void
307 do_chunk_tests (SoupURI *base_uri)
308 {
309         SoupSession *session;
310
311         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
312         do_request_test (session, base_uri);
313         debug_printf (2, "\n\n");
314         do_response_test (session, base_uri);
315         debug_printf (2, "\n\n");
316         do_temporary_test (session, base_uri);
317         soup_test_session_abort_unref (session);
318 }
319
320 static void
321 server_callback (SoupServer *server, SoupMessage *msg,
322                  const char *path, GHashTable *query,
323                  SoupClientContext *context, gpointer data)
324 {
325         SoupMessageBody *md5_body;
326         char *md5;
327
328         if (msg->method == SOUP_METHOD_GET) {
329                 soup_message_set_response (msg, "text/plain",
330                                            SOUP_MEMORY_STATIC,
331                                            "three\r\ntwo\r\none\r\n",
332                                            strlen ("three\r\ntwo\r\none\r\n"));
333                 soup_buffer_free (soup_message_body_flatten (msg->response_body));
334                 md5_body = msg->response_body;
335                 soup_message_set_status (msg, SOUP_STATUS_OK);
336         } else if (msg->method == SOUP_METHOD_PUT) {
337                 soup_message_set_status (msg, SOUP_STATUS_CREATED);
338                 md5_body = msg->request_body;
339         } else {
340                 soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
341                 return;
342         }
343
344         md5 = g_compute_checksum_for_data (G_CHECKSUM_MD5,
345                                            (guchar *)md5_body->data,
346                                            md5_body->length);
347         soup_message_headers_append (msg->response_headers,
348                                      "Content-MD5", md5);
349         g_free (md5);
350 }
351
352 int
353 main (int argc, char **argv)
354 {
355         GMainLoop *loop;
356         SoupServer *server;
357         guint port;
358         SoupURI *base_uri;
359
360         test_init (argc, argv, NULL);
361
362         server = soup_test_server_new (TRUE);
363         soup_server_add_handler (server, NULL,
364                                  server_callback, NULL, NULL);
365         port =  soup_server_get_port (server);
366
367         loop = g_main_loop_new (NULL, TRUE);
368
369         base_uri = soup_uri_new ("http://127.0.0.1");
370         soup_uri_set_port (base_uri, port);
371         do_chunk_tests (base_uri);
372         soup_uri_free (base_uri);
373
374         g_main_loop_unref (loop);
375
376         test_cleanup ();
377         return errors != 0;
378 }