f99d9c7768904178942601c180750d8c3ce6ee32
[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 "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 char *server2_uri;
18
19 typedef struct {
20         const char *method;
21         const char *path;
22         guint status_code;
23         gboolean repeat;
24 } TestRequest;
25
26 static struct {
27         TestRequest requests[3];
28         guint final_status;
29 } tests[] = {
30         /* A redirecty response to a GET or HEAD should cause a redirect */
31
32         { { { "GET", "/301", 301 },
33             { "GET", "/", 200 },
34             { NULL } }, 200 },
35         { { { "GET", "/302", 302 },
36             { "GET", "/", 200 },
37             { NULL } }, 200 },
38         { { { "GET", "/303", 303 },
39             { "GET", "/", 200 },
40             { NULL } }, 200 },
41         { { { "GET", "/307", 307 },
42             { "GET", "/", 200 },
43             { NULL } }, 200 },
44         { { { "HEAD", "/301", 301 },
45             { "HEAD", "/", 200 },
46             { NULL } }, 200 },
47         { { { "HEAD", "/302", 302 },
48             { "HEAD", "/", 200 },
49             { NULL } }, 200 },
50         /* 303 is a nonsensical response to HEAD, but some sites do
51          * it anyway. :-/
52          */
53         { { { "HEAD", "/303", 303 },
54             { "HEAD", "/", 200 },
55             { NULL } }, 200 },
56         { { { "HEAD", "/307", 307 },
57             { "HEAD", "/", 200 },
58             { NULL } }, 200 },
59
60         /* A non-redirecty response to a GET or HEAD should not */
61
62         { { { "GET", "/300", 300 },
63             { NULL } }, 300 },
64         { { { "GET", "/304", 304 },
65             { NULL } }, 304 },
66         { { { "GET", "/305", 305 },
67             { NULL } }, 305 },
68         { { { "GET", "/306", 306 },
69             { NULL } }, 306 },
70         { { { "GET", "/308", 308 },
71             { NULL } }, 308 },
72         { { { "HEAD", "/300", 300 },
73             { NULL } }, 300 },
74         { { { "HEAD", "/304", 304 },
75             { NULL } }, 304 },
76         { { { "HEAD", "/305", 305 },
77             { NULL } }, 305 },
78         { { { "HEAD", "/306", 306 },
79             { NULL } }, 306 },
80         { { { "HEAD", "/308", 308 },
81             { NULL } }, 308 },
82         
83         /* Test double-redirect */
84
85         { { { "GET", "/301/302", 301 },
86             { "GET", "/302", 302 },
87             { "GET", "/", 200 } }, 200 },
88         { { { "HEAD", "/301/302", 301 },
89             { "HEAD", "/302", 302 },
90             { "HEAD", "/", 200 } }, 200 },
91
92         /* POST should only automatically redirect on 301, 302 and 303 */
93
94         { { { "POST", "/301", 301 },
95             { "GET", "/", 200 },
96             { NULL } }, 200 },
97         { { { "POST", "/302", 302 },
98             { "GET", "/", 200 },
99             { NULL } }, 200 },
100         { { { "POST", "/303", 303 },
101             { "GET", "/", 200 },
102             { NULL } }, 200 },
103         { { { "POST", "/307", 307 },
104             { NULL } }, 307 },
105
106         /* Test behavior with recoverably-bad Location header */
107         { { { "GET", "/bad", 302 },
108             { "GET", "/bad%20with%20spaces", 200 },
109             { NULL } }, 200 },
110
111         /* Test behavior with irrecoverably-bad Location header */
112         { { { "GET", "/bad-no-host", 302 },
113             { NULL } }, SOUP_STATUS_MALFORMED },
114
115         /* Test infinite redirection */
116         { { { "GET", "/bad-recursive", 302, TRUE },
117             { NULL } }, SOUP_STATUS_TOO_MANY_REDIRECTS },
118
119         /* Test redirection to a different server */
120         { { { "GET", "/server2", 302 },
121             { "GET", "/on-server2", 200 },
122             { NULL } }, 200 },
123 };
124 static const int n_tests = G_N_ELEMENTS (tests);
125
126 static void
127 got_headers (SoupMessage *msg, gpointer user_data)
128 {
129         TestRequest **req = user_data;
130         const char *location;
131
132         debug_printf (2, "    -> %d %s\n", msg->status_code,
133                       msg->reason_phrase);
134         location = soup_message_headers_get_one (msg->response_headers,
135                                                  "Location");
136         if (location)
137                 debug_printf (2, "       Location: %s\n", location);
138
139         if (!(*req)->method)
140                 return;
141
142         if (msg->status_code != (*req)->status_code) {
143                 debug_printf (1, "    - Expected %d !\n",
144                               (*req)->status_code);
145                 errors++;
146         }
147 }
148
149 static void
150 restarted (SoupMessage *msg, gpointer user_data)
151 {
152         TestRequest **req = user_data;
153         SoupURI *uri = soup_message_get_uri (msg);
154
155         debug_printf (2, "    %s %s\n", msg->method, uri->path);
156
157         if ((*req)->method && !(*req)->repeat)
158                 (*req)++;
159
160         if (!(*req)->method) {
161                 debug_printf (1, "    - Expected to be done!\n");
162                 errors++;
163                 return;
164         }
165
166         if (strcmp (msg->method, (*req)->method) != 0) {
167                 debug_printf (1, "    - Expected %s !\n", (*req)->method);
168                 errors++;
169         }
170         if (strcmp (uri->path, (*req)->path) != 0) {
171                 debug_printf (1, "    - Expected %s !\n", (*req)->path);
172                 errors++;
173         }
174 }
175
176 static void
177 do_test (SoupSession *session, SoupURI *base_uri, int n)
178 {
179         SoupURI *uri;
180         SoupMessage *msg;
181         TestRequest *req;
182
183         debug_printf (1, "%2d. %s %s\n", n + 1,
184                       tests[n].requests[0].method,
185                       tests[n].requests[0].path);
186
187         uri = soup_uri_new_with_base (base_uri, tests[n].requests[0].path);
188         msg = soup_message_new_from_uri (tests[n].requests[0].method, uri);
189         soup_uri_free (uri);
190
191         if (msg->method == SOUP_METHOD_POST) {
192                 soup_message_set_request (msg, "text/plain",
193                                           SOUP_MEMORY_STATIC,
194                                           "post body",
195                                           strlen ("post body"));
196         }
197
198         req = &tests[n].requests[0];
199         g_signal_connect (msg, "got_headers",
200                           G_CALLBACK (got_headers), &req);
201         g_signal_connect (msg, "restarted",
202                           G_CALLBACK (restarted), &req);
203
204         soup_session_send_message (session, msg);
205
206         if (msg->status_code != tests[n].final_status) {
207                 debug_printf (1, "    - Expected final status of %d, got %d !\n",
208                               tests[n].final_status, msg->status_code);
209                 errors++;
210         }
211
212         g_object_unref (msg);
213         debug_printf (2, "\n");
214 }
215
216 static void
217 do_redirect_tests (SoupURI *base_uri)
218 {
219         SoupSession *session;
220         int n;
221
222         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
223         debug_printf (1, "Async session\n");
224         for (n = 0; n < n_tests; n++)
225                 do_test (session, base_uri, n);
226         soup_test_session_abort_unref (session);
227
228         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
229         debug_printf (1, "Sync session\n");
230         for (n = 0; n < n_tests; n++)
231                 do_test (session, base_uri, n);
232         soup_test_session_abort_unref (session);
233 }
234
235 static void
236 server_callback (SoupServer *server, SoupMessage *msg,
237                  const char *path, GHashTable *query,
238                  SoupClientContext *context, gpointer data)
239 {
240         char *remainder;
241         guint status_code;
242
243         if (g_str_has_prefix (path, "/bad")) {
244                 if (!strcmp (path, "/bad")) {
245                         soup_message_set_status (msg, SOUP_STATUS_FOUND);
246                         soup_message_headers_replace (msg->response_headers,
247                                                       "Location",
248                                                       "/bad with spaces");
249                 } else if (!strcmp (path, "/bad-recursive")) {
250                         soup_message_set_status (msg, SOUP_STATUS_FOUND);
251                         soup_message_headers_replace (msg->response_headers,
252                                                       "Location",
253                                                       "/bad-recursive");
254                 } else if (!strcmp (path, "/bad-no-host")) {
255                         soup_message_set_status (msg, SOUP_STATUS_FOUND);
256                         soup_message_headers_replace (msg->response_headers,
257                                                       "Location",
258                                                       "about:blank");
259                 } else if (!strcmp (path, "/bad with spaces"))
260                         soup_message_set_status (msg, SOUP_STATUS_OK);
261                 else
262                         soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
263                 return;
264         } else if (!strcmp (path, "/server2")) {
265                 soup_message_set_status (msg, SOUP_STATUS_FOUND);
266                 soup_message_headers_replace (msg->response_headers,
267                                               "Location",
268                                               server2_uri);
269                 return;
270         } else if (!strcmp (path, "/")) {
271                 if (msg->method != SOUP_METHOD_GET &&
272                     msg->method != SOUP_METHOD_HEAD) {
273                         soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
274                         return;
275                 }
276
277                 /* Make sure that redirecting a POST clears the body */
278                 if (msg->request_body->length) {
279                         soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
280                         return;
281                 }
282
283                 /* Make sure that a HTTP/1.0 redirect doesn't cause an
284                  * HTTP/1.0 re-request. (#521848)
285                  */
286                 if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
287                         soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
288                         return;
289                 }
290
291                 soup_message_set_status (msg, SOUP_STATUS_OK);
292
293                 /* FIXME: this is wrong, though it doesn't matter for
294                  * the purposes of this test, and to do the right
295                  * thing currently we'd have to set Content-Length by
296                  * hand.
297                  */
298                 if (msg->method != SOUP_METHOD_HEAD) {
299                         soup_message_set_response (msg, "text/plain",
300                                                    SOUP_MEMORY_STATIC,
301                                                    "OK\r\n", 4);
302                 }
303                 return;
304         }
305
306         status_code = strtoul (path + 1, &remainder, 10);
307         if (!SOUP_STATUS_IS_REDIRECTION (status_code) ||
308             (*remainder && *remainder != '/')) {
309                 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
310                 return;
311         }
312
313         /* See above comment re bug 521848. */
314         soup_message_set_http_version (msg, SOUP_HTTP_1_0);
315
316         soup_message_set_status (msg, status_code);
317         if (*remainder) {
318                 soup_message_headers_replace (msg->response_headers,
319                                               "Location", remainder);
320         } else {
321                 soup_message_headers_replace (msg->response_headers,
322                                               "Location", "/");
323         }
324 }
325
326 static void
327 server2_callback (SoupServer *server, SoupMessage *msg,
328                   const char *path, GHashTable *query,
329                   SoupClientContext *context, gpointer data)
330 {
331         soup_message_set_status (msg, SOUP_STATUS_OK);
332 }
333
334 static gboolean run_tests = TRUE;
335
336 static GOptionEntry no_test_entry[] = {
337         { "no-tests", 'n', G_OPTION_FLAG_REVERSE,
338           G_OPTION_ARG_NONE, &run_tests,
339           "Don't run tests, just run the test server", NULL },
340         { NULL }
341 };
342
343 int
344 main (int argc, char **argv)
345 {
346         GMainLoop *loop;
347         SoupServer *server, *server2;
348         guint port;
349         SoupURI *base_uri;
350
351         test_init (argc, argv, no_test_entry);
352
353         server = soup_test_server_new (TRUE);
354         soup_server_add_handler (server, NULL,
355                                  server_callback, NULL, NULL);
356         port = soup_server_get_port (server);
357
358         server2 = soup_test_server_new (TRUE);
359         soup_server_add_handler (server2, NULL,
360                                  server2_callback, NULL, NULL);
361         server2_uri = g_strdup_printf ("http://127.0.0.1:%d/on-server2",
362                                        soup_server_get_port (server2));
363
364         loop = g_main_loop_new (NULL, TRUE);
365
366         if (run_tests) {
367                 base_uri = soup_uri_new ("http://127.0.0.1");
368                 soup_uri_set_port (base_uri, port);
369                 do_redirect_tests (base_uri);
370                 soup_uri_free (base_uri);
371         } else {
372                 printf ("Listening on port %d\n", port);
373                 g_main_loop_run (loop);
374         }
375
376         g_main_loop_unref (loop);
377         g_free (server2_uri);
378         soup_test_server_quit_unref (server);
379         soup_test_server_quit_unref (server2);
380
381         if (run_tests)
382                 test_cleanup ();
383         return errors != 0;
384 }