soup-message-io: keep cancellable alive for duration of io_run_until
[platform/upstream/libsoup.git] / tests / misc-test.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright 2007-2012 Red Hat, Inc.
4  */
5
6 #include <string.h>
7
8 #define LIBSOUP_USE_UNSTABLE_REQUEST_API
9 #include <libsoup/soup.h>
10 #include <libsoup/soup-requester.h>
11
12 #include "test-utils.h"
13
14 SoupServer *server, *ssl_server;
15 SoupURI *base_uri, *ssl_base_uri;
16
17 static gboolean
18 auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
19                const char *username, const char *password, gpointer data)
20 {
21         return !strcmp (username, "user") && !strcmp (password, "password");
22 }
23
24 static gboolean
25 timeout_finish_message (gpointer msg)
26 {
27         SoupServer *server = g_object_get_data (G_OBJECT (msg), "server");
28
29         soup_server_unpause_message (server, msg);
30         return FALSE;
31 }
32
33 static void
34 server_callback (SoupServer *server, SoupMessage *msg,
35                  const char *path, GHashTable *query,
36                  SoupClientContext *context, gpointer data)
37 {
38         SoupURI *uri = soup_message_get_uri (msg);
39         const char *server_protocol = data;
40
41         soup_message_headers_append (msg->response_headers,
42                                      "X-Handled-By", "server_callback");
43
44         if (!strcmp (path, "*")) {
45                 debug_printf (1, "    default server_callback got request for '*'!\n");
46                 errors++;
47                 soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
48                 return;
49         }
50
51         if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_POST) {
52                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
53                 return;
54         }
55
56         if (!strcmp (path, "/redirect")) {
57                 soup_message_set_redirect (msg, SOUP_STATUS_FOUND, "/");
58                 return;
59         }
60
61         if (!strcmp (path, "/alias-redirect")) {
62                 SoupURI *redirect_uri;
63                 char *redirect_string;
64                 const char *redirect_protocol;
65
66                 redirect_protocol = soup_message_headers_get_one (msg->request_headers, "X-Redirect-Protocol");
67
68                 redirect_uri = soup_uri_copy (uri);
69                 soup_uri_set_scheme (redirect_uri, "foo");
70                 if (!g_strcmp0 (redirect_protocol, "https"))
71                         soup_uri_set_port (redirect_uri, ssl_base_uri->port);
72                 else
73                         soup_uri_set_port (redirect_uri, base_uri->port);
74                 soup_uri_set_path (redirect_uri, "/alias-redirected");
75                 redirect_string = soup_uri_to_string (redirect_uri, FALSE);
76
77                 soup_message_set_redirect (msg, SOUP_STATUS_FOUND, redirect_string);
78                 g_free (redirect_string);
79                 soup_uri_free (redirect_uri);
80                 return;
81         } else if (!strcmp (path, "/alias-redirected")) {
82                 soup_message_set_status (msg, SOUP_STATUS_OK);
83                 soup_message_headers_append (msg->response_headers,
84                                              "X-Redirected-Protocol",
85                                              server_protocol);
86                 return;
87         }
88
89         if (!strcmp (path, "/slow")) {
90                 soup_server_pause_message (server, msg);
91                 g_object_set_data (G_OBJECT (msg), "server", server);
92                 soup_add_timeout (soup_server_get_async_context (server),
93                                   1000, timeout_finish_message, msg);
94         }
95
96         soup_message_set_status (msg, SOUP_STATUS_OK);
97         if (!strcmp (uri->host, "foo")) {
98                 soup_message_set_response (msg, "text/plain",
99                                            SOUP_MEMORY_STATIC, "foo-index", 9);
100                 return;
101         } else {
102                 soup_message_set_response (msg, "text/plain",
103                                            SOUP_MEMORY_STATIC, "index", 5);
104                 return;
105         }
106 }
107
108 static void
109 server_star_callback (SoupServer *server, SoupMessage *msg,
110                       const char *path, GHashTable *query,
111                       SoupClientContext *context, gpointer data)
112 {
113         soup_message_headers_append (msg->response_headers,
114                                      "X-Handled-By", "star_callback");
115
116         if (strcmp (path, "*") != 0) {
117                 debug_printf (1, "    server_star_callback got request for '%s'!\n", path);
118                 errors++;
119                 soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
120                 return;
121         }
122
123         if (msg->method != SOUP_METHOD_OPTIONS) {
124                 soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
125                 return;
126         }
127
128         soup_message_set_status (msg, SOUP_STATUS_OK);
129 }
130
131 /* Host header handling: client must be able to override the default
132  * value, server must be able to recognize different Host values.
133  * #539803.
134  */
135 static void
136 do_host_test (void)
137 {
138         SoupSession *session;
139         SoupMessage *one, *two;
140
141         debug_printf (1, "Host handling\n");
142
143         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
144
145         one = soup_message_new_from_uri ("GET", base_uri);
146         two = soup_message_new_from_uri ("GET", base_uri);
147         soup_message_headers_replace (two->request_headers, "Host", "foo");
148
149         soup_session_send_message (session, one);
150         soup_session_send_message (session, two);
151
152         soup_test_session_abort_unref (session);
153
154         if (!SOUP_STATUS_IS_SUCCESSFUL (one->status_code)) {
155                 debug_printf (1, "  Message 1 failed: %d %s\n",
156                               one->status_code, one->reason_phrase);
157                 errors++;
158         } else if (strcmp (one->response_body->data, "index") != 0) {
159                 debug_printf (1, "  Unexpected response to message 1: '%s'\n",
160                               one->response_body->data);
161                 errors++;
162         }
163         g_object_unref (one);
164
165         if (!SOUP_STATUS_IS_SUCCESSFUL (two->status_code)) {
166                 debug_printf (1, "  Message 2 failed: %d %s\n",
167                               two->status_code, two->reason_phrase);
168                 errors++;
169         } else if (strcmp (two->response_body->data, "foo-index") != 0) {
170                 debug_printf (1, "  Unexpected response to message 2: '%s'\n",
171                               two->response_body->data);
172                 errors++;
173         }
174         g_object_unref (two);
175 }
176
177 /* Dropping the application's ref on the session from a callback
178  * should not cause the session to be freed at an incorrect time.
179  * (This test will crash if it fails.) #533473
180  */
181 static void
182 cu_one_completed (SoupSession *session, SoupMessage *msg, gpointer loop)
183 {
184         debug_printf (2, "  Message 1 completed\n");
185         if (msg->status_code != SOUP_STATUS_CANT_CONNECT) {
186                 debug_printf (1, "  Unexpected status on Message 1: %d %s\n",
187                               msg->status_code, msg->reason_phrase);
188                 errors++;
189         }
190         g_object_unref (session);
191 }
192
193 static gboolean
194 cu_idle_quit (gpointer loop)
195 {
196         g_main_loop_quit (loop);
197         return FALSE;
198 }
199
200 static void
201 cu_two_completed (SoupSession *session, SoupMessage *msg, gpointer loop)
202 {
203         debug_printf (2, "  Message 2 completed\n");
204         if (msg->status_code != SOUP_STATUS_CANT_CONNECT) {
205                 debug_printf (1, "  Unexpected status on Message 2: %d %s\n",
206                               msg->status_code, msg->reason_phrase);
207                 errors++;
208         }
209         g_idle_add (cu_idle_quit, loop); 
210 }
211
212 static void
213 do_callback_unref_test (void)
214 {
215         SoupServer *bad_server;
216         SoupAddress *addr;
217         SoupSession *session;
218         SoupMessage *one, *two;
219         GMainLoop *loop;
220         char *bad_uri;
221
222         debug_printf (1, "\nCallback unref handling (msg api)\n");
223
224         /* Get a guaranteed-bad URI */
225         addr = soup_address_new ("127.0.0.1", SOUP_ADDRESS_ANY_PORT);
226         soup_address_resolve_sync (addr, NULL);
227         bad_server = soup_server_new (SOUP_SERVER_INTERFACE, addr,
228                                       NULL);
229         g_object_unref (addr);
230
231         bad_uri = g_strdup_printf ("http://127.0.0.1:%u/",
232                                    soup_server_get_port (bad_server));
233         g_object_unref (bad_server);
234
235         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
236         g_object_add_weak_pointer (G_OBJECT (session), (gpointer *)&session);
237
238         loop = g_main_loop_new (NULL, TRUE);
239
240         one = soup_message_new ("GET", bad_uri);
241         g_object_add_weak_pointer (G_OBJECT (one), (gpointer *)&one);
242         two = soup_message_new ("GET", bad_uri);
243         g_object_add_weak_pointer (G_OBJECT (two), (gpointer *)&two);
244         g_free (bad_uri);
245
246         soup_session_queue_message (session, one, cu_one_completed, loop);
247         soup_session_queue_message (session, two, cu_two_completed, loop);
248
249         g_main_loop_run (loop);
250         g_main_loop_unref (loop);
251
252         if (session) {
253                 g_object_remove_weak_pointer (G_OBJECT (session), (gpointer *)&session);
254                 debug_printf (1, "  Session not destroyed?\n");
255                 errors++;
256                 g_object_unref (session);
257         }
258         if (one) {
259                 g_object_remove_weak_pointer (G_OBJECT (one), (gpointer *)&one);
260                 debug_printf (1, "  Message 1 not destroyed?\n");
261                 errors++;
262                 g_object_unref (one);
263         }
264         if (two) {
265                 g_object_remove_weak_pointer (G_OBJECT (two), (gpointer *)&two);
266                 debug_printf (1, "  Message 2 not destroyed?\n");
267                 errors++;
268                 g_object_unref (two);
269         }
270
271         /* Otherwise, if we haven't crashed, we're ok. */
272 }
273
274 static void
275 cur_one_completed (GObject *source, GAsyncResult *result, gpointer session)
276 {
277         SoupRequest *one = SOUP_REQUEST (source);
278         GError *error = NULL;
279
280         debug_printf (2, "  Request 1 completed\n");
281         if (soup_request_send_finish (one, result, &error)) {
282                 debug_printf (1, "  Request 1 succeeded?\n");
283                 errors++;
284         } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_CONNECT)) {
285                 debug_printf (1, "  Unexpected error on Request 1: %s\n",
286                               error->message);
287                 errors++;
288         }
289         g_clear_error (&error);
290
291         g_object_unref (session);
292 }
293
294 static gboolean
295 cur_idle_quit (gpointer loop)
296 {
297         g_main_loop_quit (loop);
298         return FALSE;
299 }
300
301 static void
302 cur_two_completed (GObject *source, GAsyncResult *result, gpointer loop)
303 {
304         SoupRequest *two = SOUP_REQUEST (source);
305         GError *error = NULL;
306
307         debug_printf (2, "  Request 2 completed\n");
308         if (soup_request_send_finish (two, result, &error)) {
309                 debug_printf (1, "  Request 2 succeeded?\n");
310                 errors++;
311         } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_CONNECT)) {
312                 debug_printf (1, "  Unexpected error on Request 2: %s\n",
313                               error->message);
314                 errors++;
315         }
316         g_clear_error (&error);
317
318         g_idle_add (cur_idle_quit, loop); 
319 }
320
321 static void
322 do_callback_unref_req_test (void)
323 {
324         SoupServer *bad_server;
325         SoupAddress *addr;
326         SoupSession *session;
327         SoupRequester *requester;
328         SoupRequest *one, *two;
329         GMainLoop *loop;
330         char *bad_uri;
331
332         debug_printf (1, "\nCallback unref handling (request api)\n");
333
334         /* Get a guaranteed-bad URI */
335         addr = soup_address_new ("127.0.0.1", SOUP_ADDRESS_ANY_PORT);
336         soup_address_resolve_sync (addr, NULL);
337         bad_server = soup_server_new (SOUP_SERVER_INTERFACE, addr,
338                                       NULL);
339         g_object_unref (addr);
340
341         bad_uri = g_strdup_printf ("http://127.0.0.1:%u/",
342                                    soup_server_get_port (bad_server));
343         g_object_unref (bad_server);
344
345         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
346                                          SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
347                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
348                                          NULL);
349         g_object_add_weak_pointer (G_OBJECT (session), (gpointer *)&session);
350
351         requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
352
353         loop = g_main_loop_new (NULL, TRUE);
354
355         one = soup_requester_request (requester, bad_uri, NULL);
356         g_object_add_weak_pointer (G_OBJECT (one), (gpointer *)&one);
357         two = soup_requester_request (requester, bad_uri, NULL);
358         g_object_add_weak_pointer (G_OBJECT (two), (gpointer *)&two);
359         g_free (bad_uri);
360
361         soup_request_send_async (one, NULL, cur_one_completed, session);
362         g_object_unref (one);
363         soup_request_send_async (two, NULL, cur_two_completed, loop);
364         g_object_unref (two);
365
366         g_main_loop_run (loop);
367         g_main_loop_unref (loop);
368
369         if (session) {
370                 g_object_remove_weak_pointer (G_OBJECT (session), (gpointer *)&session);
371                 debug_printf (1, "  Session not destroyed?\n");
372                 errors++;
373                 g_object_unref (session);
374         }
375         if (one) {
376                 g_object_remove_weak_pointer (G_OBJECT (one), (gpointer *)&one);
377                 debug_printf (1, "  Request 1 not destroyed?\n");
378                 errors++;
379                 g_object_unref (one);
380         }
381         if (two) {
382                 g_object_remove_weak_pointer (G_OBJECT (two), (gpointer *)&two);
383                 debug_printf (1, "  Request 2 not destroyed?\n");
384                 errors++;
385                 g_object_unref (two);
386         }
387
388         /* Otherwise, if we haven't crashed, we're ok. */
389 }
390
391 /* SoupSession should clean up all signal handlers on a message after
392  * it is finished, allowing the message to be reused if desired.
393  * #559054
394  */
395 static void
396 ensure_no_signal_handlers (SoupMessage *msg, guint *signal_ids, guint n_signal_ids)
397 {
398         int i;
399
400         for (i = 0; i < n_signal_ids; i++) {
401                 if (g_signal_handler_find (msg, G_SIGNAL_MATCH_ID, signal_ids[i],
402                                            0, NULL, NULL, NULL)) {
403                         debug_printf (1, "    Message has handler for '%s'\n",
404                                       g_signal_name (signal_ids[i]));
405                         errors++;
406                 }
407         }
408 }
409
410 static void
411 reuse_test_authenticate (SoupSession *session, SoupMessage *msg,
412                          SoupAuth *auth, gboolean retrying)
413 {
414         /* Get it wrong the first time, then succeed */
415         if (!retrying)
416                 soup_auth_authenticate (auth, "user", "wrong password");
417         else
418                 soup_auth_authenticate (auth, "user", "password");
419 }
420
421 static void
422 do_msg_reuse_test (void)
423 {
424         SoupSession *session;
425         SoupMessage *msg;
426         SoupURI *uri;
427         guint *signal_ids, n_signal_ids;
428
429         debug_printf (1, "\nSoupMessage reuse\n");
430
431         signal_ids = g_signal_list_ids (SOUP_TYPE_MESSAGE, &n_signal_ids);
432
433         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
434         g_signal_connect (session, "authenticate",
435                           G_CALLBACK (reuse_test_authenticate), NULL);
436
437         debug_printf (1, "  First message\n");
438         msg = soup_message_new_from_uri ("GET", base_uri);
439         soup_session_send_message (session, msg);
440         ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
441
442         debug_printf (1, "  Redirect message\n");
443         uri = soup_uri_new_with_base (base_uri, "/redirect");
444         soup_message_set_uri (msg, uri);
445         soup_uri_free (uri);
446         soup_session_send_message (session, msg);
447         if (!soup_uri_equal (soup_message_get_uri (msg), base_uri)) {
448                 debug_printf (1, "    Message did not get redirected!\n");
449                 errors++;
450         }
451         ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
452
453         debug_printf (1, "  Auth message\n");
454         uri = soup_uri_new_with_base (base_uri, "/auth");
455         soup_message_set_uri (msg, uri);
456         soup_uri_free (uri);
457         soup_session_send_message (session, msg);
458         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
459                 debug_printf (1, "    Message did not get authenticated!\n");
460                 errors++;
461         }
462         ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
463
464         /* One last try to make sure the auth stuff got cleaned up */
465         debug_printf (1, "  Last message\n");
466         soup_message_set_uri (msg, base_uri);
467         soup_session_send_message (session, msg);
468         ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
469
470         soup_test_session_abort_unref (session);
471         g_object_unref (msg);
472         g_free (signal_ids);
473 }
474
475 /* Server handlers for "*" work but are separate from handlers for
476  * all other URIs. #590751
477  */
478 static void
479 do_star_test (void)
480 {
481         SoupSession *session;
482         SoupMessage *msg;
483         SoupURI *star_uri;
484         const char *handled_by;
485
486         debug_printf (1, "\nOPTIONS *\n");
487
488         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
489         star_uri = soup_uri_copy (base_uri);
490         soup_uri_set_path (star_uri, "*");
491
492         debug_printf (1, "  Testing with no handler\n");
493         msg = soup_message_new_from_uri ("OPTIONS", star_uri);
494         soup_session_send_message (session, msg);
495
496         if (msg->status_code != SOUP_STATUS_NOT_FOUND) {
497                 debug_printf (1, "    Unexpected response: %d %s\n",
498                               msg->status_code, msg->reason_phrase);
499                 errors++;
500         }
501         handled_by = soup_message_headers_get_one (msg->response_headers,
502                                                    "X-Handled-By");
503         if (handled_by) {
504                 /* Should have been rejected by SoupServer directly */
505                 debug_printf (1, "    Message reached handler '%s'\n",
506                               handled_by);
507                 errors++;
508         }
509         g_object_unref (msg);
510
511         soup_server_add_handler (server, "*", server_star_callback, NULL, NULL);
512
513         debug_printf (1, "  Testing with handler\n");
514         msg = soup_message_new_from_uri ("OPTIONS", star_uri);
515         soup_session_send_message (session, msg);
516
517         if (msg->status_code != SOUP_STATUS_OK) {
518                 debug_printf (1, "    Unexpected response: %d %s\n",
519                               msg->status_code, msg->reason_phrase);
520                 errors++;
521         }
522         handled_by = soup_message_headers_get_one (msg->response_headers,
523                                                    "X-Handled-By");
524         if (!handled_by) {
525                 debug_printf (1, "    Message did not reach handler!\n");
526                 errors++;
527         } else if (strcmp (handled_by, "star_callback") != 0) {
528                 debug_printf (1, "    Message reached incorrect handler '%s'\n",
529                               handled_by);
530                 errors++;
531         }
532         g_object_unref (msg);
533
534         soup_test_session_abort_unref (session);
535         soup_uri_free (star_uri);
536 }
537
538 /* Handle unexpectedly-early aborts. #596074, #618641 */
539 static void
540 ea_msg_completed_one (SoupSession *session, SoupMessage *msg, gpointer loop)
541 {
542         debug_printf (2, "  Message 1 completed\n");
543         if (msg->status_code != SOUP_STATUS_CANCELLED) {
544                 debug_printf (1, "  Unexpected status on Message 1: %d %s\n",
545                               msg->status_code, msg->reason_phrase);
546                 errors++;
547         }
548         g_main_loop_quit (loop);
549 }
550
551 static gboolean
552 ea_abort_session (gpointer session)
553 {
554         soup_session_abort (session);
555         return FALSE;
556 }
557
558 static void
559 ea_connection_state_changed (GObject *conn, GParamSpec *pspec, gpointer session)
560 {
561         SoupConnectionState state;
562
563         g_object_get (conn, "state", &state, NULL);
564         if (state == SOUP_CONNECTION_CONNECTING) {
565                 g_idle_add_full (G_PRIORITY_HIGH,
566                                  ea_abort_session,
567                                  session, NULL);
568                 g_signal_handlers_disconnect_by_func (conn, ea_connection_state_changed, session);
569         }
570 }               
571
572 static void
573 ea_connection_created (SoupSession *session, GObject *conn, gpointer user_data)
574 {
575         g_signal_connect (conn, "notify::state",
576                           G_CALLBACK (ea_connection_state_changed), session);
577         g_signal_handlers_disconnect_by_func (session, ea_connection_created, user_data);
578 }
579
580 static void
581 ea_request_started (SoupSession *session, SoupMessage *msg, SoupSocket *socket, gpointer user_data)
582 {
583         soup_session_cancel_message (session, msg, SOUP_STATUS_CANCELLED);
584 }
585
586 static void
587 do_early_abort_test (void)
588 {
589         SoupSession *session;
590         SoupMessage *msg;
591         GMainContext *context;
592         GMainLoop *loop;
593
594         debug_printf (1, "\nAbort with pending connection (msg api)\n");
595
596         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
597         msg = soup_message_new_from_uri ("GET", base_uri);
598
599         context = g_main_context_default ();
600         loop = g_main_loop_new (context, TRUE);
601         soup_session_queue_message (session, msg, ea_msg_completed_one, loop);
602         g_main_context_iteration (context, FALSE);
603
604         soup_session_abort (session);
605         while (g_main_context_pending (context))
606                 g_main_context_iteration (context, FALSE);
607         g_main_loop_unref (loop);
608         soup_test_session_abort_unref (session);
609
610         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
611         msg = soup_message_new_from_uri ("GET", base_uri);
612
613         g_signal_connect (session, "connection-created",
614                           G_CALLBACK (ea_connection_created), NULL);
615         soup_session_send_message (session, msg);
616         debug_printf (2, "  Message 2 completed\n");
617
618         if (msg->status_code != SOUP_STATUS_CANCELLED) {
619                 debug_printf (1, "    Unexpected response: %d %s\n",
620                               msg->status_code, msg->reason_phrase);
621                 errors++;
622         }
623         g_object_unref (msg);
624
625         while (g_main_context_pending (context))
626                 g_main_context_iteration (context, FALSE);
627
628         soup_test_session_abort_unref (session);
629
630         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
631         msg = soup_message_new_from_uri ("GET", base_uri);
632
633         g_signal_connect (session, "request-started",
634                           G_CALLBACK (ea_request_started), NULL);
635         soup_session_send_message (session, msg);
636         debug_printf (2, "  Message 3 completed\n");
637
638         if (msg->status_code != SOUP_STATUS_CANCELLED) {
639                 debug_printf (1, "    Unexpected response: %d %s\n",
640                               msg->status_code, msg->reason_phrase);
641                 errors++;
642         }
643         g_object_unref (msg);
644
645         while (g_main_context_pending (context))
646                 g_main_context_iteration (context, FALSE);
647
648         soup_test_session_abort_unref (session);
649 }
650
651 static void
652 ear_one_completed (GObject *source, GAsyncResult *result, gpointer user_data)
653 {
654         GError *error = NULL;
655
656         debug_printf (2, "  Request 1 completed\n");
657         if (soup_request_send_finish (SOUP_REQUEST (source), result, &error)) {
658                 debug_printf (1, "  Request 1 succeeded?\n");
659                 errors++;
660         } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED)) {
661                 debug_printf (1, "  Unexpected error on Request 1: %s\n",
662                               error->message);
663                 errors++;
664         }
665         g_clear_error (&error);
666 }
667
668 static void
669 ear_two_completed (GObject *source, GAsyncResult *result, gpointer loop)
670 {
671         GError *error = NULL;
672
673         debug_printf (2, "  Request 2 completed\n");
674         if (soup_request_send_finish (SOUP_REQUEST (source), result, &error)) {
675                 debug_printf (1, "  Request 2 succeeded?\n");
676                 errors++;
677         } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED)) {
678                 debug_printf (1, "  Unexpected error on Request 2: %s\n",
679                               error->message);
680                 errors++;
681         }
682         g_clear_error (&error);
683
684         g_main_loop_quit (loop);
685 }
686
687 static void
688 ear_three_completed (GObject *source, GAsyncResult *result, gpointer loop)
689 {
690         GError *error = NULL;
691
692         debug_printf (2, "  Request 3 completed\n");
693         if (soup_request_send_finish (SOUP_REQUEST (source), result, &error)) {
694                 debug_printf (1, "  Request 3 succeeded?\n");
695                 errors++;
696         } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
697                 debug_printf (1, "  Unexpected error on Request 3: %s\n",
698                               error->message);
699                 errors++;
700         }
701         g_clear_error (&error);
702
703         g_main_loop_quit (loop);
704 }
705
706 static void
707 ear_request_started (SoupSession *session, SoupMessage *msg,
708                      SoupSocket *socket, gpointer cancellable)
709 {
710         g_cancellable_cancel (cancellable);
711 }
712
713 static void
714 do_early_abort_req_test (void)
715 {
716         SoupSession *session;
717         SoupRequester *requester;
718         SoupRequest *req;
719         GMainContext *context;
720         GMainLoop *loop;
721         GCancellable *cancellable;
722
723         debug_printf (1, "\nAbort with pending connection (request api)\n");
724
725         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
726                                          SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
727                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
728                                          NULL);
729         requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
730         req = soup_requester_request_uri (requester, base_uri, NULL);
731
732         context = g_main_context_default ();
733         loop = g_main_loop_new (context, TRUE);
734         soup_request_send_async (req, NULL, ear_one_completed, NULL);
735         g_object_unref (req);
736         g_main_context_iteration (context, FALSE);
737
738         soup_session_abort (session);
739         while (g_main_context_pending (context))
740                 g_main_context_iteration (context, FALSE);
741         soup_test_session_abort_unref (session);
742
743         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
744                                          SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
745                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
746                                          NULL);
747         requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
748         req = soup_requester_request_uri (requester, base_uri, NULL);
749
750         g_signal_connect (session, "connection-created",
751                           G_CALLBACK (ea_connection_created), NULL);
752         soup_request_send_async (req, NULL, ear_two_completed, loop);
753         g_main_loop_run (loop);
754         g_object_unref (req);
755
756         while (g_main_context_pending (context))
757                 g_main_context_iteration (context, FALSE);
758
759         soup_test_session_abort_unref (session);
760
761         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
762                                          SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
763                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
764                                          NULL);
765         requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
766         req = soup_requester_request_uri (requester, base_uri, NULL);
767
768         cancellable = g_cancellable_new ();
769         g_signal_connect (session, "request-started",
770                           G_CALLBACK (ear_request_started), cancellable);
771         soup_request_send_async (req, cancellable, ear_three_completed, loop);
772         g_main_loop_run (loop);
773         g_object_unref (req);
774         g_object_unref (cancellable);
775
776         while (g_main_context_pending (context))
777                 g_main_context_iteration (context, FALSE);
778
779         soup_test_session_abort_unref (session);
780         g_main_loop_unref (loop);
781 }
782
783 static void
784 do_one_accept_language_test (const char *language, const char *expected_header)
785 {
786         SoupSession *session;
787         SoupMessage *msg;
788         const char *val;
789
790         debug_printf (1, "  LANGUAGE=%s\n", language);
791         g_setenv ("LANGUAGE", language, TRUE);
792         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC,
793                                          SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE,
794                                          NULL);
795         msg = soup_message_new_from_uri ("GET", base_uri);
796         soup_session_send_message (session, msg);
797         soup_test_session_abort_unref (session);
798
799         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
800                 debug_printf (1, "    Message failed? %d %s\n",
801                               msg->status_code, msg->reason_phrase);
802                 errors++;
803         }
804         val = soup_message_headers_get_list (msg->request_headers,
805                                              "Accept-Language");
806         if (!val) {
807                 debug_printf (1, "    No Accept-Language set!\n");
808                 errors++;
809         } else if (strcmp (val, expected_header) != 0) {
810                 debug_printf (1, "    Wrong Accept-Language: expected '%s', got '%s'\n",
811                               expected_header, val);
812                 errors++;
813         }
814
815         g_object_unref (msg);
816 }
817
818 static void
819 do_accept_language_test (void)
820 {
821         const char *orig_language;
822
823         debug_printf (1, "\nAutomatic Accept-Language processing\n");
824
825         orig_language = g_getenv ("LANGUAGE");
826         do_one_accept_language_test ("C", "en");
827         do_one_accept_language_test ("fr_FR", "fr-fr, fr;q=0.9");
828         do_one_accept_language_test ("fr_FR:de:en_US", "fr-fr, fr;q=0.9, de;q=0.8, en-us;q=0.7, en;q=0.6");
829
830         if (orig_language)
831                 g_setenv ("LANGUAGE", orig_language, TRUE);
832         else
833                 g_unsetenv ("LANGUAGE");
834 }
835
836 static gboolean
837 cancel_message_timeout (gpointer msg)
838 {
839         SoupSession *session = g_object_get_data (G_OBJECT (msg), "session");
840
841         soup_session_cancel_message (session, msg, SOUP_STATUS_CANCELLED);
842         g_object_unref (msg);
843         g_object_unref (session);
844         return FALSE;
845 }
846
847 static gpointer
848 cancel_message_thread (gpointer msg)
849 {
850         SoupSession *session = g_object_get_data (G_OBJECT (msg), "session");
851
852         g_usleep (100000); /* .1s */
853         soup_session_cancel_message (session, msg, SOUP_STATUS_CANCELLED);
854         g_object_unref (msg);
855         g_object_unref (session);
856         return NULL;
857 }
858
859 static void
860 set_done (SoupSession *session, SoupMessage *msg, gpointer user_data)
861 {
862         gboolean *done = user_data;
863
864         *done = TRUE;
865 }
866
867 static void
868 do_cancel_while_reading_test_for_session (SoupSession *session)
869 {
870         SoupMessage *msg;
871         GThread *thread = NULL;
872         SoupURI *uri;
873         gboolean done = FALSE;
874
875         uri = soup_uri_new_with_base (base_uri, "/slow");
876         msg = soup_message_new_from_uri ("GET", uri);
877         soup_uri_free (uri);
878
879         g_object_set_data (G_OBJECT (msg), "session", session);
880         g_object_ref (msg);
881         g_object_ref (session);
882         if (SOUP_IS_SESSION_ASYNC (session))
883                 g_timeout_add (100, cancel_message_timeout, msg);
884         else
885                 thread = g_thread_new ("cancel_message_thread", cancel_message_thread, msg);
886
887         /* We intentionally don't use soup_session_send_message() here,
888          * because it holds an extra ref on the SoupMessageQueueItem
889          * relative to soup_session_queue_message().
890          */
891         g_object_ref (msg);
892         soup_session_queue_message (session, msg, set_done, &done);
893         while (!done)
894                 g_main_context_iteration (NULL, TRUE);
895
896         if (msg->status_code != SOUP_STATUS_CANCELLED) {
897                 debug_printf (1, "      FAILED: %d %s (expected Cancelled)\n",
898                               msg->status_code, msg->reason_phrase);
899                 errors++;
900         }
901         g_object_unref (msg);
902
903         if (thread)
904                 g_thread_join (thread);
905 }
906
907 static void
908 do_cancel_while_reading_test (void)
909 {
910         SoupSession *session;
911
912         debug_printf (1, "\nCancelling message while reading response (msg api)\n");
913
914         debug_printf (1, "  Async session\n");
915         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
916         do_cancel_while_reading_test_for_session (session);
917         soup_test_session_abort_unref (session);
918
919         debug_printf (1, "  Sync session\n");
920         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
921         do_cancel_while_reading_test_for_session (session);
922         soup_test_session_abort_unref (session);
923 }
924
925 static gboolean
926 cancel_request_timeout (gpointer cancellable)
927 {
928         g_cancellable_cancel (cancellable);
929         return FALSE;
930 }
931
932 static gpointer
933 cancel_request_thread (gpointer cancellable)
934 {
935         g_usleep (100000); /* .1s */
936         g_cancellable_cancel (cancellable);
937         return NULL;
938 }
939
940 static void
941 cancel_request_finished (GObject *source, GAsyncResult *result, gpointer loop)
942 {
943         GError *error = NULL;
944
945         if (soup_request_send_finish (SOUP_REQUEST (source), result, &error)) {
946                 debug_printf (1, "  Request succeeded?\n");
947                 errors++;
948         } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
949                 debug_printf (1, "  Unexpected error: %s\n",
950                               error->message);
951                 errors++;
952         }
953         g_clear_error (&error);
954
955         g_main_loop_quit (loop);
956 }
957
958 static void
959 do_cancel_while_reading_req_test_for_session (SoupRequester *requester)
960 {
961         SoupRequest *req;
962         SoupURI *uri;
963         GCancellable *cancellable;
964
965         uri = soup_uri_new_with_base (base_uri, "/slow");
966         req = soup_requester_request_uri (requester, uri, NULL);
967         soup_uri_free (uri);
968
969         cancellable = g_cancellable_new ();
970
971         if (SOUP_IS_SESSION_ASYNC (soup_request_get_session (req))) {
972                 GMainLoop *loop;
973
974                 loop = g_main_loop_new (NULL, FALSE);
975                 g_timeout_add (100, cancel_request_timeout, cancellable);
976                 soup_request_send_async (req, cancellable,
977                                          cancel_request_finished, loop);
978                 g_main_loop_run (loop);
979                 g_main_loop_unref (loop);
980         } else {
981                 GThread *thread;
982                 GError *error = NULL;
983
984                 thread = g_thread_new ("cancel_request_thread", cancel_request_thread, cancellable);
985                 soup_request_send (req, cancellable, &error);
986                 if (!error) {
987                         debug_printf (1, "  Request succeeded?\n");
988                         errors++;
989                 } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
990                         debug_printf (1, "  Unexpected error: %s\n",
991                                       error->message);
992                         errors++;
993                 }
994                 g_thread_unref (thread);
995         }
996
997         g_object_unref (req);
998         g_object_unref (cancellable);
999 }
1000
1001 static void
1002 do_cancel_while_reading_req_test (void)
1003 {
1004         SoupSession *session;
1005         SoupRequester *requester;
1006
1007         debug_printf (1, "\nCancelling message while reading response (request api)\n");
1008
1009         debug_printf (1, "  Async session\n");
1010         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
1011                                          SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
1012                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
1013                                          NULL);
1014         requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
1015         do_cancel_while_reading_req_test_for_session (requester);
1016         soup_test_session_abort_unref (session);
1017
1018         debug_printf (1, "  Sync session\n");
1019         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC,
1020                                          SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER,
1021                                          NULL);
1022         requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
1023         do_cancel_while_reading_req_test_for_session (requester);
1024         soup_test_session_abort_unref (session);
1025 }
1026
1027 static void
1028 do_aliases_test_for_session (SoupSession *session,
1029                              const char *redirect_protocol)
1030 {
1031         SoupMessage *msg;
1032         SoupURI *uri;
1033         const char *redirected_protocol;
1034
1035         uri = soup_uri_new_with_base (base_uri, "/alias-redirect");
1036         msg = soup_message_new_from_uri ("GET", uri);
1037         if (redirect_protocol)
1038                 soup_message_headers_append (msg->request_headers, "X-Redirect-Protocol", redirect_protocol);
1039         soup_uri_free (uri);
1040         soup_session_send_message (session, msg);
1041
1042         redirected_protocol = soup_message_headers_get_one (msg->response_headers, "X-Redirected-Protocol");
1043
1044         if (g_strcmp0 (redirect_protocol, redirected_protocol)) {
1045                 debug_printf (1, "    redirect went to %s, should have gone to %s!\n",
1046                               redirected_protocol ? redirected_protocol : "(none)",
1047                               redirect_protocol ? redirect_protocol : "(none)");
1048                 errors++;
1049         } else if (redirect_protocol && !SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1050                 debug_printf (1, "    msg failed? (%d %s)\n",
1051                               msg->status_code, msg->reason_phrase);
1052                 errors++;
1053         } else if (!redirect_protocol && SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1054                 debug_printf (1, "    msg succeeded? (%d %s)\n",
1055                               msg->status_code, msg->reason_phrase);
1056                 errors++;
1057         }
1058
1059         g_object_unref (msg);
1060 }
1061
1062 static void
1063 do_aliases_test (void)
1064 {
1065         SoupSession *session;
1066         char *aliases[] = { "foo", NULL };
1067
1068         debug_printf (1, "\nhttp-aliases / https-aliases\n");
1069
1070         debug_printf (1, "  Default behavior\n");
1071         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1072         do_aliases_test_for_session (session, "http");
1073         soup_test_session_abort_unref (session);
1074
1075         debug_printf (1, "  foo-means-https\n");
1076         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
1077                                          SOUP_SESSION_HTTPS_ALIASES, aliases,
1078                                          NULL);
1079         do_aliases_test_for_session (session, "https");
1080         soup_test_session_abort_unref (session);
1081
1082         debug_printf (1, "  foo-means-nothing\n");
1083         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
1084                                          SOUP_SESSION_HTTP_ALIASES, NULL,
1085                                          NULL);
1086         do_aliases_test_for_session (session, NULL);
1087         soup_test_session_abort_unref (session);
1088 }
1089
1090 static void
1091 do_dot_dot_test (void)
1092 {
1093         SoupSession *session;
1094         SoupMessage *msg;
1095         SoupURI *uri;
1096
1097         debug_printf (1, "\n'..' smuggling test\n");
1098
1099         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
1100
1101         uri = soup_uri_new_with_base (base_uri, "/..%2ftest");
1102         msg = soup_message_new_from_uri ("GET", uri);
1103         soup_uri_free (uri);
1104
1105         soup_session_send_message (session, msg);
1106
1107         if (msg->status_code != SOUP_STATUS_BAD_REQUEST) {
1108                 debug_printf (1, "      FAILED: %d %s (expected Bad Request)\n",
1109                               msg->status_code, msg->reason_phrase);
1110                 errors++;
1111         }
1112         g_object_unref (msg);
1113
1114         soup_test_session_abort_unref (session);
1115 }
1116
1117 static void
1118 do_ipv6_test (void)
1119 {
1120         SoupServer *ipv6_server;
1121         SoupURI *ipv6_uri;
1122         SoupAddress *ipv6_addr;
1123         SoupSession *session;
1124         SoupMessage *msg;
1125
1126         debug_printf (1, "\nIPv6 server test\n");
1127
1128         ipv6_addr = soup_address_new ("::1", SOUP_ADDRESS_ANY_PORT);
1129         soup_address_resolve_sync (ipv6_addr, NULL);
1130         ipv6_server = soup_server_new (SOUP_SERVER_INTERFACE, ipv6_addr,
1131                                        NULL);
1132         g_object_unref (ipv6_addr);
1133         soup_server_add_handler (ipv6_server, NULL, server_callback, NULL, NULL);
1134         soup_server_run_async (ipv6_server);
1135
1136         ipv6_uri = soup_uri_new ("http://[::1]/");
1137         soup_uri_set_port (ipv6_uri, soup_server_get_port (ipv6_server));
1138
1139         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1140
1141         debug_printf (1, "  HTTP/1.1\n");
1142         msg = soup_message_new_from_uri ("GET", ipv6_uri);
1143         soup_session_send_message (session, msg);
1144         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1145                 debug_printf (1, "    request failed: %d %s\n",
1146                               msg->status_code, msg->reason_phrase);
1147                 errors++;
1148         }
1149         g_object_unref (msg);
1150
1151         debug_printf (1, "  HTTP/1.0\n");
1152         msg = soup_message_new_from_uri ("GET", ipv6_uri);
1153         soup_message_set_http_version (msg, SOUP_HTTP_1_0);
1154         soup_session_send_message (session, msg);
1155         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1156                 debug_printf (1, "    request failed: %d %s\n",
1157                               msg->status_code, msg->reason_phrase);
1158                 errors++;
1159         }
1160         g_object_unref (msg);
1161
1162         soup_uri_free (ipv6_uri);
1163         soup_test_session_abort_unref (session);
1164         soup_test_server_quit_unref (ipv6_server);
1165 }
1166
1167 static void
1168 do_idle_on_dispose_test (void)
1169 {
1170         SoupSession *session;
1171         SoupMessage *msg;
1172         GMainContext *async_context;
1173
1174         debug_printf (1, "\nTesting SoupSessionAsync dispose behavior\n");
1175
1176         async_context = g_main_context_new ();
1177         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
1178                                          SOUP_SESSION_ASYNC_CONTEXT, async_context,
1179                                          NULL);
1180
1181         msg = soup_message_new_from_uri ("GET", base_uri);
1182         soup_session_send_message (session, msg);
1183         g_object_unref (msg);
1184
1185         while (g_main_context_iteration (async_context, FALSE))
1186                 ;
1187
1188         g_object_run_dispose (G_OBJECT (session));
1189
1190         if (g_main_context_iteration (async_context, FALSE)) {
1191                 debug_printf (1, "  idle was queued!\n");
1192                 errors++;
1193         }
1194
1195         g_object_unref (session);
1196         g_main_context_unref (async_context);
1197 }
1198
1199 static void
1200 do_pause_abort_test (void)
1201 {
1202         SoupSession *session;
1203         SoupMessage *msg;
1204         gpointer ptr;
1205
1206         debug_printf (1, "\nTesting paused messages don't get leaked on abort\n");
1207
1208         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1209
1210         msg = soup_message_new_from_uri ("GET", base_uri);
1211         soup_session_queue_message (session, msg, NULL, NULL);
1212         soup_session_pause_message (session, msg);
1213
1214         g_object_add_weak_pointer (G_OBJECT (msg), &ptr);
1215         soup_test_session_abort_unref (session);
1216
1217         if (ptr) {
1218                 debug_printf (1, "  msg was leaked\n");
1219                 errors++;
1220         }
1221 }
1222
1223 int
1224 main (int argc, char **argv)
1225 {
1226         SoupAuthDomain *auth_domain;
1227
1228         test_init (argc, argv, NULL);
1229
1230         server = soup_test_server_new (TRUE);
1231         soup_server_add_handler (server, NULL, server_callback, "http", NULL);
1232         base_uri = soup_uri_new ("http://127.0.0.1/");
1233         soup_uri_set_port (base_uri, soup_server_get_port (server));
1234
1235         auth_domain = soup_auth_domain_basic_new (
1236                 SOUP_AUTH_DOMAIN_REALM, "misc-test",
1237                 SOUP_AUTH_DOMAIN_ADD_PATH, "/auth",
1238                 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, auth_callback,
1239                 NULL);
1240         soup_server_add_auth_domain (server, auth_domain);
1241         g_object_unref (auth_domain);
1242
1243         ssl_server = soup_test_server_new_ssl (TRUE);
1244         soup_server_add_handler (ssl_server, NULL, server_callback, "https", NULL);
1245         ssl_base_uri = soup_uri_new ("https://127.0.0.1/");
1246         soup_uri_set_port (ssl_base_uri, soup_server_get_port (ssl_server));
1247
1248         do_host_test ();
1249         do_callback_unref_test ();
1250         do_callback_unref_req_test ();
1251         do_msg_reuse_test ();
1252         do_star_test ();
1253         do_early_abort_test ();
1254         do_early_abort_req_test ();
1255         do_accept_language_test ();
1256         do_cancel_while_reading_test ();
1257         do_cancel_while_reading_req_test ();
1258         do_aliases_test ();
1259         do_dot_dot_test ();
1260         do_ipv6_test ();
1261         do_idle_on_dispose_test ();
1262         do_pause_abort_test ();
1263
1264         soup_uri_free (base_uri);
1265         soup_uri_free (ssl_base_uri);
1266         soup_test_server_quit_unref (server);
1267         soup_test_server_quit_unref (ssl_server);
1268
1269         test_cleanup ();
1270         return errors != 0;
1271 }