Fix the retry-on-broken-connection codepath for SoupRequest
[platform/upstream/libsoup.git] / tests / coding-test.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Red Hat, Inc.
4  * Copyright (C) 2011 Igalia, S.L.
5  */
6
7 #include "test-utils.h"
8
9 SoupServer *server;
10 SoupURI *base_uri;
11
12 static void
13 server_callback (SoupServer *server, SoupMessage *msg,
14                  const char *path, GHashTable *query,
15                  SoupClientContext *context, gpointer data)
16 {
17         const char *accept_encoding, *options;
18         GSList *codings;
19         char *file = NULL, *contents;
20         gsize length;
21
22         options = soup_message_headers_get_one (msg->request_headers,
23                                                 "X-Test-Options");
24         if (!options)
25                 options = "";
26
27         accept_encoding = soup_message_headers_get_list (msg->request_headers,
28                                                          "Accept-Encoding");
29         if (accept_encoding && !soup_header_contains (options, "force-encode"))
30                 codings = soup_header_parse_quality_list (accept_encoding, NULL);
31         else
32                 codings = NULL;
33
34         if (codings) {
35                 gboolean claim_deflate, claim_gzip;
36                 const char *file_path = NULL, *encoding = NULL;
37
38                 claim_deflate = g_slist_find_custom (codings, "deflate", (GCompareFunc)g_ascii_strcasecmp) != NULL;
39                 claim_gzip = g_slist_find_custom (codings, "gzip", (GCompareFunc)g_ascii_strcasecmp) != NULL;
40
41                 if (claim_gzip && (!claim_deflate ||
42                                    (!soup_header_contains (options, "prefer-deflate-zlib") &&
43                                     !soup_header_contains (options, "prefer-deflate-raw")))) {
44                         file_path = SRCDIR "/resources%s.gz";
45                         encoding = "gzip";
46                 } else if (claim_deflate) {
47                         if (soup_header_contains (options, "prefer-deflate-raw")) {
48                                 file_path = SRCDIR "/resources%s.raw";
49                                 encoding = "deflate";
50                         } else {
51                                 file_path = SRCDIR "/resources%s.zlib";
52                                 encoding = "deflate";
53                         }
54                 }
55                 if (file_path && encoding) {
56                         file = g_strdup_printf (file_path, path);
57                         if (g_file_test (file, G_FILE_TEST_EXISTS)) {
58                                 soup_message_headers_append (msg->response_headers,
59                                                              "Content-Encoding",
60                                                              encoding);
61                         } else {
62                                 g_free (file);
63                                 file = NULL;
64                         }
65                 }
66         }
67
68         soup_header_free_list (codings);
69
70         if (!file)
71                 file = g_strdup_printf (SRCDIR "/resources%s", path);
72         if (!g_file_get_contents (file, &contents, &length, NULL)) {
73                 /* If path.gz exists but can't be read, we'll send back
74                  * the error with "Content-Encoding: gzip" but there's
75                  * no body, so, eh.
76                  */
77                 g_free (file);
78                 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
79                 return;
80         }
81         g_free (file);
82
83         if (soup_header_contains (options, "force-encode")) {
84                 const gchar *encoding = "gzip";
85
86                 if (soup_header_contains (options, "prefer-deflate-zlib") ||
87                     soup_header_contains (options, "prefer-deflate-raw"))
88                         encoding = "deflate";
89
90                 soup_message_headers_replace (msg->response_headers,
91                                               "Content-Encoding",
92                                               encoding);
93         }
94
95         /* Content-Type matches the "real" format, not the sent format */
96         if (g_str_has_suffix (path, ".gz")) {
97                 soup_message_headers_append (msg->response_headers,
98                                              "Content-Type",
99                                              "application/gzip");
100         } else {
101                 soup_message_headers_append (msg->response_headers,
102                                              "Content-Type",
103                                              "text/plain");
104         }
105
106         soup_message_set_status (msg, SOUP_STATUS_OK);
107         soup_message_headers_set_encoding (msg->response_headers, SOUP_ENCODING_CHUNKED);
108
109         soup_message_body_append (msg->response_body,
110                                   SOUP_MEMORY_TAKE, contents, length);
111
112         if (soup_header_contains (options, "trailing-junk")) {
113                 soup_message_body_append (msg->response_body, SOUP_MEMORY_COPY,
114                                           options, strlen (options));
115         }
116         soup_message_body_complete (msg->response_body);
117 }
118
119 typedef enum {
120         NO_CHECK,
121         EXPECT_DECODED,
122         EXPECT_NOT_DECODED
123 } MessageContentStatus;
124
125 static void
126 check_response (SoupMessage *msg,
127                 const char *expected_encoding,
128                 const char *expected_content_type,
129                 MessageContentStatus status)
130 {
131         const char *coding, *type;
132
133         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
134                 debug_printf (1, "    Unexpected status %d %s\n",
135                               msg->status_code, msg->reason_phrase);
136                 errors++;
137         }
138
139         coding = soup_message_headers_get_one (msg->response_headers, "Content-Encoding");
140         if (expected_encoding) {
141                 if (!coding || g_ascii_strcasecmp (coding, expected_encoding) != 0) {
142                         debug_printf (1, "    Unexpected Content-Encoding: %s\n",
143                                       coding ? coding : "(none)");
144                         errors++;
145                 }
146         } else {
147                 if (coding) {
148                         debug_printf (1, "    Unexpected Content-Encoding: %s\n",
149                                       coding);
150                         errors++;
151                 }
152         }
153
154         if (status != NO_CHECK) {
155                 if (status == EXPECT_DECODED) {
156                         if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_CONTENT_DECODED)) {
157                                 debug_printf (1, "    SOUP_MESSAGE_CONTENT_DECODED not set!\n");
158                                 errors++;
159                         }
160                 } else {
161                         if (soup_message_get_flags (msg) & SOUP_MESSAGE_CONTENT_DECODED) {
162                                 debug_printf (1, "    SOUP_MESSAGE_CONTENT_DECODED set!\n");
163                                 errors++;
164                         }
165                 }
166         }
167
168         type = soup_message_headers_get_one (msg->response_headers, "Content-Type");
169         if (!type || g_ascii_strcasecmp (type, expected_content_type) != 0) {
170                 debug_printf (1, "    Unexpected Content-Type: %s\n",
171                               type ? type : "(none)");
172                 errors++;
173         }
174 }
175
176 static void
177 check_msg_bodies (SoupMessage *msg1,
178                   SoupMessage *msg2,
179                   const char *msg1_type,
180                   const char *msg2_type)
181 {
182         if (msg1->response_body->length != msg2->response_body->length) {
183                 debug_printf (1, "    Message length mismatch: %lu (%s) vs %lu (%s)\n",
184                               (gulong)msg1->response_body->length,
185                               msg1_type,
186                               (gulong)msg2->response_body->length,
187                               msg2_type);
188                 errors++;
189         } else if (memcmp (msg1->response_body->data,
190                            msg2->response_body->data,
191                            msg1->response_body->length) != 0) {
192                 debug_printf (1, "    Message data mismatch (%s/%s)\n",
193                               msg1_type, msg2_type);
194                 errors++;
195         }
196 }
197
198 static void
199 do_coding_test (void)
200 {
201         SoupSession *session;
202         SoupMessage *msg, *msgz, *msgj, *msge, *msgzl, *msgzlj, *msgzle, *msgzlr, *msgzlre;
203         SoupURI *uri;
204
205         debug_printf (1, "SoupMessage tests\n");
206
207         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
208         uri = soup_uri_new_with_base (base_uri, "/mbox");
209
210         /* Plain text data, no claim */
211         debug_printf (1, "  GET /mbox, plain\n");
212         msg = soup_message_new_from_uri ("GET", uri);
213         soup_session_send_message (session, msg);
214         check_response (msg, NULL, "text/plain", EXPECT_NOT_DECODED);
215
216         /* Plain text data, claim gzip */
217         debug_printf (1, "  GET /mbox, Accept-Encoding: gzip\n");
218         soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_DECODER);
219         msgz = soup_message_new_from_uri ("GET", uri);
220         soup_session_send_message (session, msgz);
221         check_response (msgz, "gzip", "text/plain", EXPECT_DECODED);
222         check_msg_bodies (msg, msgz, "plain", "compressed");
223
224         /* Plain text data, claim gzip w/ junk */
225         debug_printf (1, "  GET /mbox, Accept-Encoding: gzip, plus trailing junk\n");
226         msgj = soup_message_new_from_uri ("GET", uri);
227         soup_message_headers_append (msgj->request_headers,
228                                      "X-Test-Options", "trailing-junk");
229         soup_session_send_message (session, msgj);
230         check_response (msgj, "gzip", "text/plain", EXPECT_DECODED);
231         check_msg_bodies (msg, msgj, "plain", "compressed w/ junk");
232
233         /* Plain text data, claim gzip with server error */
234         debug_printf (1, "  GET /mbox, Accept-Encoding: gzip, with server error\n");
235         msge = soup_message_new_from_uri ("GET", uri);
236         soup_message_headers_append (msge->request_headers,
237                                      "X-Test-Options", "force-encode");
238         soup_session_send_message (session, msge);
239         check_response (msge, "gzip", "text/plain", EXPECT_NOT_DECODED);
240
241         /* Failed content-decoding should have left the body untouched
242          * from what the server sent... which happens to be the
243          * uncompressed data.
244          */
245         check_msg_bodies (msg, msge, "plain", "mis-encoded");
246
247         /* Plain text data, claim deflate */
248         debug_printf (1, "  GET /mbox, Accept-Encoding: deflate\n");
249         msgzl = soup_message_new_from_uri ("GET", uri);
250         soup_message_headers_append (msgzl->request_headers,
251                                      "X-Test-Options", "prefer-deflate-zlib");
252         soup_session_send_message (session, msgzl);
253         check_response (msgzl, "deflate", "text/plain", EXPECT_DECODED);
254         check_msg_bodies (msg, msgzl, "plain", "compressed");
255
256         /* Plain text data, claim deflate w/ junk */
257         debug_printf (1, "  GET /mbox, Accept-Encoding: deflate, plus trailing junk\n");
258         msgzlj = soup_message_new_from_uri ("GET", uri);
259         soup_message_headers_append (msgzlj->request_headers,
260                                      "X-Test-Options", "prefer-deflate-zlib, trailing-junk");
261         soup_session_send_message (session, msgzlj);
262         check_response (msgzlj, "deflate", "text/plain", EXPECT_DECODED);
263         check_msg_bodies (msg, msgzlj, "plain", "compressed w/ junk");
264
265         /* Plain text data, claim deflate with server error */
266         debug_printf (1, "  GET /mbox, Accept-Encoding: deflate, with server error\n");
267         msgzle = soup_message_new_from_uri ("GET", uri);
268         soup_message_headers_append (msgzle->request_headers,
269                                      "X-Test-Options", "force-encode, prefer-deflate-zlib");
270         soup_session_send_message (session, msgzle);
271         check_response (msgzle, "deflate", "text/plain", EXPECT_NOT_DECODED);
272         check_msg_bodies (msg, msgzle, "plain", "mis-encoded");
273
274         /* Plain text data, claim deflate (no zlib headers)*/
275         debug_printf (1, "  GET /mbox, Accept-Encoding: deflate (raw data)\n");
276         msgzlr = soup_message_new_from_uri ("GET", uri);
277         soup_message_headers_append (msgzlr->request_headers,
278                                      "X-Test-Options", "prefer-deflate-raw");
279         soup_session_send_message (session, msgzlr);
280         check_response (msgzlr, "deflate", "text/plain", EXPECT_DECODED);
281         check_msg_bodies (msg, msgzlr, "plain", "compressed");
282
283         /* Plain text data, claim deflate with server error */
284         debug_printf (1, "  GET /mbox, Accept-Encoding: deflate (raw data), with server error\n");
285         msgzlre = soup_message_new_from_uri ("GET", uri);
286         soup_message_headers_append (msgzlre->request_headers,
287                                      "X-Test-Options", "force-encode, prefer-deflate-raw");
288         soup_session_send_message (session, msgzlre);
289         check_response (msgzlre, "deflate", "text/plain", EXPECT_NOT_DECODED);
290         check_msg_bodies (msg, msgzlre, "plain", "mis-encoded");
291
292         g_object_unref (msg);
293         g_object_unref (msgzlre);
294         g_object_unref (msgzlr);
295         g_object_unref (msgzlj);
296         g_object_unref (msgzle);
297         g_object_unref (msgzl);
298         g_object_unref (msgz);
299         g_object_unref (msgj);
300         g_object_unref (msge);
301         soup_uri_free (uri);
302
303         soup_test_session_abort_unref (session);
304 }
305
306 static void
307 read_finished (GObject *stream, GAsyncResult *result, gpointer user_data)
308 {
309         gssize *nread = user_data;
310         GError *error = NULL;
311
312         *nread = g_input_stream_read_finish (G_INPUT_STREAM (stream),
313                                              result, &error);
314         if (error) {
315                 debug_printf (1, "    Error reading: %s\n",
316                               error->message);
317                 g_error_free (error);
318                 errors++;
319         }
320 }
321
322 static GByteArray *
323 do_single_coding_req_test (SoupRequest *req,
324                            const char *expected_encoding,
325                            const char *expected_content_type,
326                            MessageContentStatus status)
327 {
328         GInputStream *stream;
329         SoupMessage *msg;
330         GByteArray *data;
331         guchar buf[1024];
332         gssize nread;
333         GError *error = NULL;
334
335         data = g_byte_array_new ();
336
337         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
338
339         stream = soup_test_request_send_async_as_sync (req, NULL, &error);
340         if (error) {
341                 debug_printf (1, "    Error sending request: %s\n",
342                               error->message);
343                 g_error_free (error);
344                 errors++;
345                 return data;
346         }
347
348         do {
349                 nread = -2;
350                 g_input_stream_read_async (stream, buf, sizeof (buf),
351                                            G_PRIORITY_DEFAULT,
352                                            NULL, read_finished, &nread);
353                 while (nread == -2)
354                         g_main_context_iteration (NULL, TRUE);
355
356                 if (nread > 0)
357                         g_byte_array_append (data, buf, nread);
358         } while (nread > 0);
359
360         soup_test_stream_close_async_as_sync (stream, NULL, &error);
361         if (error) {
362                 debug_printf (1, "    error closing stream: %s\n",
363                               error->message);
364                 g_error_free (error);
365                 errors++;
366         }
367
368         check_response (msg, expected_encoding, expected_content_type, status);
369         g_object_unref (msg);
370
371         return data;
372 }
373
374 static void
375 check_req_bodies (GByteArray *body1,
376                   GByteArray *body2,
377                   const char *msg1_type,
378                   const char *msg2_type)
379 {
380         if (body1->len != body2->len) {
381                 debug_printf (1, "    Message length mismatch: %lu (%s) vs %lu (%s)\n",
382                               (gulong)body1->len, msg1_type,
383                               (gulong)body2->len, msg2_type);
384                 errors++;
385         } else if (memcmp (body1->data, body2->data, body1->len) != 0) {
386                 debug_printf (1, "    Message data mismatch (%s/%s)\n",
387                               msg1_type, msg2_type);
388                 errors++;
389         }
390 }
391
392 static void
393 do_coding_req_test (void)
394 {
395         SoupSession *session;
396         SoupRequester *requester;
397         SoupRequest *req;
398         SoupMessage *msg;
399         SoupURI *uri;
400         GByteArray *plain, *cmp;
401
402         debug_printf (1, "\nSoupRequest tests\n");
403
404         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
405                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
406                                          SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
407                                          NULL);
408         requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
409         uri = soup_uri_new_with_base (base_uri, "/mbox");
410
411         /* Plain text data, no claim */
412         debug_printf (1, "  GET /mbox, plain\n");
413         req = soup_requester_request_uri (requester, uri, NULL);
414         plain = do_single_coding_req_test (req, NULL, "text/plain", EXPECT_NOT_DECODED);
415         g_object_unref (req);
416
417         /* Plain text data, claim gzip */
418         debug_printf (1, "  GET /mbox, Accept-Encoding: gzip\n");
419         soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_DECODER);
420         req = soup_requester_request_uri (requester, uri, NULL);
421         cmp = do_single_coding_req_test (req, "gzip", "text/plain", EXPECT_DECODED);
422         check_req_bodies (plain, cmp, "plain", "compressed");
423         g_byte_array_free (cmp, TRUE);
424         g_object_unref (req);
425
426         /* Plain text data, claim gzip w/ junk */
427         debug_printf (1, "  GET /mbox, Accept-Encoding: gzip, plus trailing junk\n");
428         req = soup_requester_request_uri (requester, uri, NULL);
429         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
430         soup_message_headers_append (msg->request_headers,
431                                      "X-Test-Options", "trailing-junk");
432         cmp = do_single_coding_req_test (req, "gzip", "text/plain", EXPECT_DECODED);
433         check_req_bodies (plain, cmp, "plain", "compressed w/ junk");
434         g_byte_array_free (cmp, TRUE);
435         g_object_unref (req);
436
437         /* Plain text data, claim gzip with server error */
438         debug_printf (1, "  GET /mbox, Accept-Encoding: gzip, with server error\n");
439         req = soup_requester_request_uri (requester, uri, NULL);
440         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
441         soup_message_headers_append (msg->request_headers,
442                                      "X-Test-Options", "force-encode");
443         cmp = do_single_coding_req_test (req, "gzip", "text/plain", EXPECT_NOT_DECODED);
444
445         /* Failed content-decoding should have left the body untouched
446          * from what the server sent... which happens to be the
447          * uncompressed data.
448          */
449         check_req_bodies (plain, cmp, "plain", "mis-encoded");
450         g_byte_array_free (cmp, TRUE);
451         g_object_unref (req);
452
453         /* Plain text data, claim deflate */
454         debug_printf (1, "  GET /mbox, Accept-Encoding: deflate\n");
455         req = soup_requester_request_uri (requester, uri, NULL);
456         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
457         soup_message_headers_append (msg->request_headers,
458                                      "X-Test-Options", "prefer-deflate-zlib");
459         cmp = do_single_coding_req_test (req, "deflate", "text/plain", EXPECT_DECODED);
460         check_req_bodies (plain, cmp, "plain", "compressed");
461         g_byte_array_free (cmp, TRUE);
462         g_object_unref (req);
463
464         /* Plain text data, claim deflate w/ junk */
465         debug_printf (1, "  GET /mbox, Accept-Encoding: deflate, plus trailing junk\n");
466         req = soup_requester_request_uri (requester, uri, NULL);
467         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
468         soup_message_headers_append (msg->request_headers,
469                                      "X-Test-Options", "prefer-deflate-zlib, trailing-junk");
470         cmp = do_single_coding_req_test (req, "deflate", "text/plain", EXPECT_DECODED);
471         check_req_bodies (plain, cmp, "plain", "compressed w/ junk");
472         g_byte_array_free (cmp, TRUE);
473         g_object_unref (req);
474
475         /* Plain text data, claim deflate with server error */
476         debug_printf (1, "  GET /mbox, Accept-Encoding: deflate, with server error\n");
477         req = soup_requester_request_uri (requester, uri, NULL);
478         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
479         soup_message_headers_append (msg->request_headers,
480                                      "X-Test-Options", "force-encode, prefer-deflate-zlib");
481         cmp = do_single_coding_req_test (req, "deflate", "text/plain", EXPECT_NOT_DECODED);
482         check_req_bodies (plain, cmp, "plain", "mis-encoded");
483         g_byte_array_free (cmp, TRUE);
484         g_object_unref (req);
485
486         /* Plain text data, claim deflate (no zlib headers)*/
487         debug_printf (1, "  GET /mbox, Accept-Encoding: deflate (raw data)\n");
488         req = soup_requester_request_uri (requester, uri, NULL);
489         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
490         soup_message_headers_append (msg->request_headers,
491                                      "X-Test-Options", "prefer-deflate-raw");
492         cmp = do_single_coding_req_test (req, "deflate", "text/plain", EXPECT_DECODED);
493         check_req_bodies (plain, cmp, "plain", "compressed");
494         g_byte_array_free (cmp, TRUE);
495         g_object_unref (req);
496
497         /* Plain text data, claim deflate with server error */
498         debug_printf (1, "  GET /mbox, Accept-Encoding: deflate (raw data), with server error\n");
499         req = soup_requester_request_uri (requester, uri, NULL);
500         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
501         soup_message_headers_append (msg->request_headers,
502                                      "X-Test-Options", "force-encode, prefer-deflate-raw");
503         cmp = do_single_coding_req_test (req, "deflate", "text/plain", EXPECT_NOT_DECODED);
504         check_req_bodies (plain, cmp, "plain", "mis-encoded");
505         g_byte_array_free (cmp, TRUE);
506         g_object_unref (req);
507
508         g_byte_array_free (plain, TRUE);
509         soup_uri_free (uri);
510
511         soup_test_session_abort_unref (session);
512 }
513
514 int
515 main (int argc, char **argv)
516 {
517         test_init (argc, argv, NULL);
518
519         server = soup_test_server_new (TRUE);
520         soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
521         base_uri = soup_uri_new ("http://127.0.0.1/");
522         soup_uri_set_port (base_uri, soup_server_get_port (server));
523
524         do_coding_test ();
525         do_coding_req_test ();
526
527         soup_uri_free (base_uri);
528         soup_test_server_quit_unref (server);
529
530         test_cleanup ();
531         return errors != 0;
532 }