Remove extern "C" wrapping other includes
[platform/upstream/libsoup.git] / tests / redirect-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 SoupURI *base_uri;
9 char *server2_uri;
10 SoupSession *async_session, *sync_session;
11
12 typedef struct {
13         const char *method;
14         const char *path;
15         guint status_code;
16         gboolean repeat;
17 } TestRequest;
18
19 typedef struct {
20         TestRequest requests[3];
21         guint final_status;
22         const char *bugref;
23 } TestCase;
24
25 static TestCase tests[] = {
26         /* A redirecty response to a GET or HEAD should cause a redirect */
27
28         { { { "GET", "/301", 301 },
29             { "GET", "/", 200 },
30             { NULL } }, 200, NULL },
31         { { { "GET", "/302", 302 },
32             { "GET", "/", 200 },
33             { NULL } }, 200, NULL },
34         { { { "GET", "/303", 303 },
35             { "GET", "/", 200 },
36             { NULL } }, 200, NULL },
37         { { { "GET", "/307", 307 },
38             { "GET", "/", 200 },
39             { NULL } }, 200, NULL },
40         { { { "GET", "/308", 308 },
41             { "GET", "/", 200 },
42             { NULL } }, 200, NULL },
43         { { { "HEAD", "/301", 301 },
44             { "HEAD", "/", 200 },
45             { NULL } }, 200, "551190" },
46         { { { "HEAD", "/302", 302 },
47             { "HEAD", "/", 200 },
48             { NULL } }, 200, "551190" },
49         /* 303 is a nonsensical response to HEAD, but some sites do
50          * it anyway. :-/
51          */
52         { { { "HEAD", "/303", 303 },
53             { "HEAD", "/", 200 },
54             { NULL } }, 200, "600830" },
55         { { { "HEAD", "/307", 307 },
56             { "HEAD", "/", 200 },
57             { NULL } }, 200, "551190" },
58         { { { "HEAD", "/308", 308 },
59             { "HEAD", "/", 200 },
60             { NULL } }, 200, "551190" },
61
62         /* A non-redirecty response to a GET or HEAD should not */
63
64         { { { "GET", "/300", 300 },
65             { NULL } }, 300, NULL },
66         { { { "GET", "/304", 304 },
67             { NULL } }, 304, NULL },
68         { { { "GET", "/305", 305 },
69             { NULL } }, 305, NULL },
70         { { { "GET", "/306", 306 },
71             { NULL } }, 306, NULL },
72         { { { "HEAD", "/300", 300 },
73             { NULL } }, 300, "551190" },
74         { { { "HEAD", "/304", 304 },
75             { NULL } }, 304, "551190" },
76         { { { "HEAD", "/305", 305 },
77             { NULL } }, 305, "551190" },
78         { { { "HEAD", "/306", 306 },
79             { NULL } }, 306, "551190" },
80         
81         /* Test double-redirect */
82
83         { { { "GET", "/301/302", 301 },
84             { "GET", "/302", 302 },
85             { "GET", "/", 200 } }, 200, NULL },
86         { { { "HEAD", "/301/302", 301 },
87             { "HEAD", "/302", 302 },
88             { "HEAD", "/", 200 } }, 200, "551190" },
89
90         /* POST should only automatically redirect on 301, 302 and 303 */
91
92         { { { "POST", "/301", 301 },
93             { "GET", "/", 200 },
94             { NULL } }, 200, "586692" },
95         { { { "POST", "/302", 302 },
96             { "GET", "/", 200 },
97             { NULL } }, 200, NULL },
98         { { { "POST", "/303", 303 },
99             { "GET", "/", 200 },
100             { NULL } }, 200, NULL },
101         { { { "POST", "/307", 307 },
102             { NULL } }, 307, NULL },
103
104         /* Test behavior with recoverably-bad Location header */
105         { { { "GET", "/bad", 302 },
106             { "GET", "/bad%20with%20spaces", 200 },
107             { NULL } }, 200, "566530" },
108
109         /* Test behavior with irrecoverably-bad Location header */
110         { { { "GET", "/bad-no-host", 302 },
111             { NULL } }, SOUP_STATUS_MALFORMED, "528882" },
112
113         /* Test infinite redirection */
114         { { { "GET", "/bad-recursive", 302, TRUE },
115             { NULL } }, SOUP_STATUS_TOO_MANY_REDIRECTS, "604383" },
116
117         /* Test redirection to a different server */
118         { { { "GET", "/server2", 302 },
119             { "GET", "/on-server2", 200 },
120             { NULL } }, 200, NULL },
121 };
122 static const int n_tests = G_N_ELEMENTS (tests);
123
124 static void
125 got_headers (SoupMessage *msg, gpointer user_data)
126 {
127         TestRequest **treq = user_data;
128         const char *location;
129
130         debug_printf (2, "    -> %d %s\n", msg->status_code,
131                       msg->reason_phrase);
132         location = soup_message_headers_get_one (msg->response_headers,
133                                                  "Location");
134         if (location)
135                 debug_printf (2, "       Location: %s\n", location);
136
137         if (!(*treq)->method)
138                 return;
139
140         soup_test_assert_message_status (msg, (*treq)->status_code);
141 }
142
143 static void
144 restarted (SoupMessage *msg, gpointer user_data)
145 {
146         TestRequest **treq = user_data;
147         SoupURI *uri = soup_message_get_uri (msg);
148
149         debug_printf (2, "    %s %s\n", msg->method, uri->path);
150
151         if ((*treq)->method && !(*treq)->repeat)
152                 (*treq)++;
153
154         soup_test_assert ((*treq)->method,
155                           "Expected to be done");
156
157         g_assert_cmpstr (msg->method, ==, (*treq)->method);
158         g_assert_cmpstr (uri->path, ==, (*treq)->path);
159 }
160
161 static void
162 do_message_api_test (SoupSession *session, TestCase *test)
163 {
164         SoupURI *uri;
165         SoupMessage *msg;
166         TestRequest *treq;
167
168         if (test->bugref)
169                 g_test_bug (test->bugref);
170
171         uri = soup_uri_new_with_base (base_uri, test->requests[0].path);
172         msg = soup_message_new_from_uri (test->requests[0].method, uri);
173         soup_uri_free (uri);
174
175         if (msg->method == SOUP_METHOD_POST) {
176                 soup_message_set_request (msg, "text/plain",
177                                           SOUP_MEMORY_STATIC,
178                                           "post body",
179                                           strlen ("post body"));
180         }
181
182         treq = &test->requests[0];
183         g_signal_connect (msg, "got_headers",
184                           G_CALLBACK (got_headers), &treq);
185         g_signal_connect (msg, "restarted",
186                           G_CALLBACK (restarted), &treq);
187
188         soup_session_send_message (session, msg);
189
190         soup_test_assert_message_status (msg, test->final_status);
191
192         g_object_unref (msg);
193 }
194
195 static void
196 do_request_api_test (SoupSession *session, TestCase *test)
197 {
198         SoupURI *uri;
199         SoupRequestHTTP *reqh;
200         SoupMessage *msg;
201         TestRequest *treq;
202         GInputStream *stream;
203         GError *error = NULL;
204
205         if (test->bugref)
206                 g_test_bug (test->bugref);
207
208         uri = soup_uri_new_with_base (base_uri, test->requests[0].path);
209         reqh = soup_session_request_http_uri (session,
210                                               test->requests[0].method,
211                                               uri, &error);
212         soup_uri_free (uri);
213         g_assert_no_error (error);
214         if (error) {
215                 g_error_free (error);
216                 return;
217         }
218
219         msg = soup_request_http_get_message (reqh);
220         if (msg->method == SOUP_METHOD_POST) {
221                 soup_message_set_request (msg, "text/plain",
222                                           SOUP_MEMORY_STATIC,
223                                           "post body",
224                                           strlen ("post body"));
225         }
226
227         treq = &test->requests[0];
228         g_signal_connect (msg, "got_headers",
229                           G_CALLBACK (got_headers), &treq);
230         g_signal_connect (msg, "restarted",
231                           G_CALLBACK (restarted), &treq);
232
233         stream = soup_test_request_send (SOUP_REQUEST (reqh), NULL, 0, &error);
234
235         if (SOUP_STATUS_IS_TRANSPORT_ERROR (test->final_status) &&
236             test->final_status != SOUP_STATUS_MALFORMED) {
237                 g_assert_error (error, SOUP_HTTP_ERROR, test->final_status);
238                 g_clear_error (&error);
239
240                 g_assert_null (stream);
241                 g_clear_object (&stream);
242
243                 g_object_unref (msg);
244                 g_object_unref (reqh);
245                 return;
246         }
247
248         g_assert_no_error (error);
249         if (error) {
250                 g_error_free (error);
251                 g_object_unref (msg);
252                 g_object_unref (reqh);
253                 return;
254         }
255
256         soup_test_request_read_all (SOUP_REQUEST (reqh), stream, NULL, &error);
257         g_assert_no_error (error);
258         g_clear_error (&error);
259
260         soup_test_request_close_stream (SOUP_REQUEST (reqh), stream, NULL, &error);
261         g_assert_no_error (error);
262         g_clear_error (&error);
263         g_object_unref (stream);
264
265         if (test->final_status == SOUP_STATUS_MALFORMED)
266                 g_assert_cmpint (msg->status_code, ==, test->requests[0].status_code);
267         else
268                 g_assert_cmpint (msg->status_code, ==, test->final_status);
269
270         g_object_unref (msg);
271         g_object_unref (reqh);
272 }
273
274 static void
275 do_async_msg_api_test (gconstpointer test)
276 {
277         do_message_api_test (async_session, (TestCase *)test);
278 }
279
280 static void
281 do_async_req_api_test (gconstpointer test)
282 {
283         do_request_api_test (async_session, (TestCase *)test);
284 }
285
286 static void
287 do_sync_msg_api_test (gconstpointer test)
288 {
289         do_message_api_test (sync_session, (TestCase *)test);
290 }
291
292 static void
293 do_sync_req_api_test (gconstpointer test)
294 {
295         do_request_api_test (sync_session, (TestCase *)test);
296 }
297
298 static void
299 server_callback (SoupServer *server, SoupMessage *msg,
300                  const char *path, GHashTable *query,
301                  SoupClientContext *context, gpointer data)
302 {
303         char *remainder;
304         guint status_code;
305
306         /* Make sure that a HTTP/1.0 redirect doesn't cause an
307          * HTTP/1.0 re-request. (#521848)
308          */
309         if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
310                 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
311                 return;
312         }
313
314         if (g_str_has_prefix (path, "/bad")) {
315                 if (!strcmp (path, "/bad")) {
316                         soup_message_set_status (msg, SOUP_STATUS_FOUND);
317                         soup_message_headers_replace (msg->response_headers,
318                                                       "Location",
319                                                       "/bad with spaces");
320                 } else if (!strcmp (path, "/bad-recursive")) {
321                         soup_message_set_status (msg, SOUP_STATUS_FOUND);
322                         soup_message_headers_replace (msg->response_headers,
323                                                       "Location",
324                                                       "/bad-recursive");
325                 } else if (!strcmp (path, "/bad-no-host")) {
326                         soup_message_set_status (msg, SOUP_STATUS_FOUND);
327                         soup_message_headers_replace (msg->response_headers,
328                                                       "Location",
329                                                       "about:blank");
330                 } else if (!strcmp (path, "/bad with spaces"))
331                         soup_message_set_status (msg, SOUP_STATUS_OK);
332                 else
333                         soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
334                 return;
335         } else if (!strcmp (path, "/server2")) {
336                 soup_message_set_status (msg, SOUP_STATUS_FOUND);
337                 soup_message_headers_replace (msg->response_headers,
338                                               "Location",
339                                               server2_uri);
340                 return;
341         } else if (!strcmp (path, "/")) {
342                 if (msg->method != SOUP_METHOD_GET &&
343                     msg->method != SOUP_METHOD_HEAD) {
344                         soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
345                         return;
346                 }
347
348                 /* Make sure that redirecting a POST clears the body */
349                 if (msg->request_body->length) {
350                         soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
351                         return;
352                 }
353
354                 soup_message_set_status (msg, SOUP_STATUS_OK);
355
356                 /* FIXME: this is wrong, though it doesn't matter for
357                  * the purposes of this test, and to do the right
358                  * thing currently we'd have to set Content-Length by
359                  * hand.
360                  */
361                 if (msg->method != SOUP_METHOD_HEAD) {
362                         soup_message_set_response (msg, "text/plain",
363                                                    SOUP_MEMORY_STATIC,
364                                                    "OK\r\n", 4);
365                 }
366                 return;
367         }
368
369         status_code = strtoul (path + 1, &remainder, 10);
370         if (!SOUP_STATUS_IS_REDIRECTION (status_code) ||
371             (*remainder && *remainder != '/')) {
372                 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
373                 return;
374         }
375
376         /* See above comment re bug 521848. We only test this on the
377          * double-redirects so that we get connection-reuse testing
378          * the rest of the time.
379          */
380         if (*remainder == '/')
381                 soup_message_set_http_version (msg, SOUP_HTTP_1_0);
382
383         soup_message_set_redirect (msg, status_code,
384                                    *remainder ? remainder : "/");
385 }
386
387 static void
388 server2_callback (SoupServer *server, SoupMessage *msg,
389                   const char *path, GHashTable *query,
390                   SoupClientContext *context, gpointer data)
391 {
392         soup_message_set_status (msg, SOUP_STATUS_OK);
393 }
394
395 int
396 main (int argc, char **argv)
397 {
398         GMainLoop *loop;
399         SoupServer *server, *server2;
400         SoupURI *uri2;
401         char *path;
402         int n, ret;
403
404         test_init (argc, argv, NULL);
405
406         server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
407         soup_server_add_handler (server, NULL,
408                                  server_callback, NULL, NULL);
409         base_uri = soup_test_server_get_uri (server, "http", NULL);
410
411         server2 = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
412         soup_server_add_handler (server2, NULL,
413                                  server2_callback, NULL, NULL);
414         uri2 = soup_test_server_get_uri (server2, "http", NULL);
415         soup_uri_set_path (uri2, "/on-server2");
416         server2_uri = soup_uri_to_string (uri2, FALSE);
417         soup_uri_free (uri2);
418
419         loop = g_main_loop_new (NULL, TRUE);
420
421         async_session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
422                                                SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
423                                                NULL);
424         sync_session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
425
426         for (n = 0; n < n_tests; n++) {
427                 path = g_strdup_printf ("/redirect/async/msg/%d-%s-%d", n,
428                                         tests[n].requests[0].method,
429                                         tests[n].requests[0].status_code);
430                 g_test_add_data_func (path, &tests[n], do_async_msg_api_test);
431                 g_free (path);
432
433                 path = g_strdup_printf ("/redirect/async/req/%d-%s-%d", n,
434                                         tests[n].requests[0].method,
435                                         tests[n].requests[0].status_code);
436                 g_test_add_data_func (path, &tests[n], do_async_req_api_test);
437                 g_free (path);
438
439                 path = g_strdup_printf ("/redirect/sync/msg/%d-%s-%d", n,
440                                         tests[n].requests[0].method,
441                                         tests[n].requests[0].status_code);
442                 g_test_add_data_func (path, &tests[n], do_sync_msg_api_test);
443                 g_free (path);
444
445                 path = g_strdup_printf ("/redirect/sync/req/%d-%s-%d", n,
446                                         tests[n].requests[0].method,
447                                         tests[n].requests[0].status_code);
448                 g_test_add_data_func (path, &tests[n], do_sync_req_api_test);
449                 g_free (path);
450         }
451
452         ret = g_test_run ();
453
454         g_main_loop_unref (loop);
455         soup_uri_free (base_uri);
456         soup_test_server_quit_unref (server);
457         g_free (server2_uri);
458         soup_test_server_quit_unref (server2);
459
460         soup_test_session_abort_unref (async_session);
461         soup_test_session_abort_unref (sync_session);
462
463         test_cleanup ();
464         return ret;
465 }