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