Git init
[profile/ivi/libsoup2.4.git] / tests / misc-test.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Red Hat, Inc.
4  */
5
6 #include <ctype.h>
7 #include <fcntl.h>
8 #include <errno.h>
9 #include <signal.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15
16 #include <glib.h>
17 #include <libsoup/soup.h>
18
19 #include "test-utils.h"
20
21 SoupServer *server;
22 SoupURI *base_uri;
23 GMutex *server_mutex;
24
25 static gboolean
26 auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
27                const char *username, const char *password, gpointer data)
28 {
29         return !strcmp (username, "user") && !strcmp (password, "password");
30 }
31
32 static void
33 forget_close (SoupMessage *msg, gpointer user_data)
34 {
35         soup_message_headers_remove (msg->response_headers, "Connection");
36 }
37
38 static void
39 close_socket (SoupMessage *msg, gpointer user_data)
40 {
41         SoupSocket *sock = user_data;
42
43         soup_socket_disconnect (sock);
44 }
45
46 static void
47 timeout_socket (SoupSocket *sock, gpointer user_data)
48 {
49         soup_socket_disconnect (sock);
50 }
51
52 static void
53 timeout_request_started (SoupServer *server, SoupMessage *msg,
54                          SoupClientContext *client, gpointer user_data)
55 {
56         SoupSocket *sock;
57         GMainContext *context = soup_server_get_async_context (server);
58         guint readable;
59
60         sock = soup_client_context_get_socket (client);
61         readable = g_signal_connect (sock, "readable",
62                                     G_CALLBACK (timeout_socket), NULL);
63         while (soup_socket_is_connected (sock))
64                 g_main_context_iteration (context, TRUE);
65         g_signal_handler_disconnect (sock, readable);
66         g_signal_handlers_disconnect_by_func (server, timeout_request_started, NULL);
67 }
68
69 static void
70 setup_timeout_persistent (SoupServer *server, SoupSocket *sock)
71 {
72         char buf[1];
73         gsize nread;
74
75         /* In order for the test to work correctly, we have to
76          * close the connection *after* the client side writes
77          * the request. To ensure that this happens reliably,
78          * regardless of thread scheduling, we:
79          *
80          *   1. Try to read off the socket now, knowing it will
81          *      fail (since the client is waiting for us to
82          *      return a response). This will cause it to
83          *      emit "readable" later.
84          *   2. Connect to the server's request-started signal.
85          *   3. Run an inner main loop from that signal handler
86          *      until the socket emits "readable". (If we don't
87          *      do this then it's possible the client's next
88          *      request would be ready before we returned to
89          *      the main loop, and so the signal would never be
90          *      emitted.)
91          *   4. Close the socket.
92          */
93
94         soup_socket_read (sock, buf, 1, &nread, NULL, NULL);
95         g_signal_connect (server, "request-started",
96                           G_CALLBACK (timeout_request_started), NULL);
97 }
98
99 static gboolean
100 timeout_finish_message (gpointer msg)
101 {
102         SoupServer *server = g_object_get_data (G_OBJECT (msg), "server");
103
104         soup_server_unpause_message (server, msg);
105         return FALSE;
106 }
107
108 static void
109 server_callback (SoupServer *server, SoupMessage *msg,
110                  const char *path, GHashTable *query,
111                  SoupClientContext *context, gpointer data)
112 {
113         SoupURI *uri = soup_message_get_uri (msg);
114
115         /* The way this gets used in the tests, we don't actually
116          * need to hold it through the whole function, so it's simpler
117          * to just release it right away.
118          */
119         g_mutex_lock (server_mutex);
120         g_mutex_unlock (server_mutex);
121
122         soup_message_headers_append (msg->response_headers,
123                                      "X-Handled-By", "server_callback");
124
125         if (!strcmp (path, "*")) {
126                 debug_printf (1, "    default server_callback got request for '*'!\n");
127                 errors++;
128                 soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
129                 return;
130         }
131
132         if (msg->method != SOUP_METHOD_GET) {
133                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
134                 return;
135         }
136
137         if (!strcmp (path, "/redirect")) {
138                 soup_message_set_status (msg, SOUP_STATUS_FOUND);
139                 soup_message_headers_append (msg->response_headers,
140                                              /* Kids: don't try this at home!
141                                               * RFC2616 says to use an
142                                               * absolute URI!
143                                               */
144                                              "Location", "/");
145                 return;
146         }
147
148         if (g_str_has_prefix (path, "/content-length/")) {
149                 gboolean too_long = strcmp (path, "/content-length/long") == 0;
150                 gboolean no_close = strcmp (path, "/content-length/noclose") == 0;
151
152                 soup_message_set_status (msg, SOUP_STATUS_OK);
153                 soup_message_set_response (msg, "text/plain",
154                                            SOUP_MEMORY_STATIC, "foobar", 6);
155                 if (too_long)
156                         soup_message_headers_set_content_length (msg->response_headers, 9);
157                 soup_message_headers_append (msg->response_headers,
158                                              "Connection", "close");
159
160                 if (too_long) {
161                         SoupSocket *sock;
162
163                         /* soup-message-io will wait for us to add
164                          * another chunk after the first, to fill out
165                          * the declared Content-Length. Instead, we
166                          * forcibly close the socket at that point.
167                          */
168                         sock = soup_client_context_get_socket (context);
169                         g_signal_connect (msg, "wrote-chunk",
170                                           G_CALLBACK (close_socket), sock);
171                 } else if (no_close) {
172                         /* Remove the 'Connection: close' after writing
173                          * the headers, so that when we check it after
174                          * writing the body, we'll think we aren't
175                          * supposed to close it.
176                          */
177                         g_signal_connect (msg, "wrote-headers",
178                                           G_CALLBACK (forget_close), NULL);
179                 }
180                 return;
181         }
182
183         if (!strcmp (path, "/timeout-persistent")) {
184                 SoupSocket *sock;
185
186                 sock = soup_client_context_get_socket (context);
187                 setup_timeout_persistent (server, sock);
188         }
189
190         if (!strcmp (path, "/slow")) {
191                 soup_server_pause_message (server, msg);
192                 g_object_set_data (G_OBJECT (msg), "server", server);
193                 soup_add_timeout (soup_server_get_async_context (server),
194                                   1000, timeout_finish_message, msg);
195         }
196
197         soup_message_set_status (msg, SOUP_STATUS_OK);
198         if (!strcmp (uri->host, "foo")) {
199                 soup_message_set_response (msg, "text/plain",
200                                            SOUP_MEMORY_STATIC, "foo-index", 9);
201                 return;
202         } else {
203                 soup_message_set_response (msg, "text/plain",
204                                            SOUP_MEMORY_STATIC, "index", 5);
205                 return;
206         }
207 }
208
209 static void
210 server_star_callback (SoupServer *server, SoupMessage *msg,
211                       const char *path, GHashTable *query,
212                       SoupClientContext *context, gpointer data)
213 {
214         soup_message_headers_append (msg->response_headers,
215                                      "X-Handled-By", "star_callback");
216
217         if (strcmp (path, "*") != 0) {
218                 debug_printf (1, "    server_star_callback got request for '%s'!\n", path);
219                 errors++;
220                 soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
221                 return;
222         }
223
224         if (msg->method != SOUP_METHOD_OPTIONS) {
225                 soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
226                 return;
227         }
228
229         soup_message_set_status (msg, SOUP_STATUS_OK);
230 }
231
232 /* Host header handling: client must be able to override the default
233  * value, server must be able to recognize different Host values.
234  * #539803.
235  */
236 static void
237 do_host_test (void)
238 {
239         SoupSession *session;
240         SoupMessage *one, *two;
241
242         debug_printf (1, "Host handling\n");
243
244         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
245
246         one = soup_message_new_from_uri ("GET", base_uri);
247         two = soup_message_new_from_uri ("GET", base_uri);
248         soup_message_headers_replace (two->request_headers, "Host", "foo");
249
250         soup_session_send_message (session, one);
251         soup_session_send_message (session, two);
252
253         soup_test_session_abort_unref (session);
254
255         if (!SOUP_STATUS_IS_SUCCESSFUL (one->status_code)) {
256                 debug_printf (1, "  Message 1 failed: %d %s\n",
257                               one->status_code, one->reason_phrase);
258                 errors++;
259         } else if (strcmp (one->response_body->data, "index") != 0) {
260                 debug_printf (1, "  Unexpected response to message 1: '%s'\n",
261                               one->response_body->data);
262                 errors++;
263         }
264         g_object_unref (one);
265
266         if (!SOUP_STATUS_IS_SUCCESSFUL (two->status_code)) {
267                 debug_printf (1, "  Message 2 failed: %d %s\n",
268                               two->status_code, two->reason_phrase);
269                 errors++;
270         } else if (strcmp (two->response_body->data, "foo-index") != 0) {
271                 debug_printf (1, "  Unexpected response to message 2: '%s'\n",
272                               two->response_body->data);
273                 errors++;
274         }
275         g_object_unref (two);
276 }
277
278 /* Dropping the application's ref on the session from a callback
279  * should not cause the session to be freed at an incorrect time.
280  * (This test will crash if it fails.) #533473
281  */
282 static void
283 cu_one_completed (SoupSession *session, SoupMessage *msg, gpointer loop)
284 {
285         debug_printf (2, "  Message 1 completed\n");
286         if (msg->status_code != SOUP_STATUS_CANT_CONNECT) {
287                 debug_printf (1, "  Unexpected status on Message 1: %d %s\n",
288                               msg->status_code, msg->reason_phrase);
289                 errors++;
290         }
291         g_object_unref (session);
292 }
293
294 static gboolean
295 cu_idle_quit (gpointer loop)
296 {
297         g_main_loop_quit (loop);
298         return FALSE;
299 }
300
301 static void
302 cu_two_completed (SoupSession *session, SoupMessage *msg, gpointer loop)
303 {
304         debug_printf (2, "  Message 2 completed\n");
305         if (msg->status_code != SOUP_STATUS_CANT_CONNECT) {
306                 debug_printf (1, "  Unexpected status on Message 2: %d %s\n",
307                               msg->status_code, msg->reason_phrase);
308                 errors++;
309         }
310         g_idle_add (cu_idle_quit, loop); 
311 }
312
313 static void
314 do_callback_unref_test (void)
315 {
316         SoupServer *bad_server;
317         SoupAddress *addr;
318         SoupSession *session;
319         SoupMessage *one, *two;
320         GMainLoop *loop;
321         char *bad_uri;
322
323         debug_printf (1, "\nCallback unref handling\n");
324
325         /* Get a guaranteed-bad URI */
326         addr = soup_address_new ("127.0.0.1", SOUP_ADDRESS_ANY_PORT);
327         soup_address_resolve_sync (addr, NULL);
328         bad_server = soup_server_new (SOUP_SERVER_INTERFACE, addr,
329                                       NULL);
330         g_object_unref (addr);
331
332         bad_uri = g_strdup_printf ("http://127.0.0.1:%u/",
333                                    soup_server_get_port (bad_server));
334         g_object_unref (bad_server);
335
336         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
337         g_object_add_weak_pointer (G_OBJECT (session), (gpointer *)&session);
338
339         loop = g_main_loop_new (NULL, TRUE);
340
341         one = soup_message_new ("GET", bad_uri);
342         g_object_add_weak_pointer (G_OBJECT (one), (gpointer *)&one);
343         two = soup_message_new ("GET", bad_uri);
344         g_object_add_weak_pointer (G_OBJECT (two), (gpointer *)&two);
345         g_free (bad_uri);
346
347         soup_session_queue_message (session, one, cu_one_completed, loop);
348         soup_session_queue_message (session, two, cu_two_completed, loop);
349
350         g_main_loop_run (loop);
351         g_main_loop_unref (loop);
352
353         if (session) {
354                 g_object_remove_weak_pointer (G_OBJECT (session), (gpointer *)&session);
355                 debug_printf (1, "  Session not destroyed?\n");
356                 errors++;
357                 g_object_unref (session);
358         }
359         if (one) {
360                 g_object_remove_weak_pointer (G_OBJECT (one), (gpointer *)&one);
361                 debug_printf (1, "  Message 1 not destroyed?\n");
362                 errors++;
363                 g_object_unref (one);
364         }
365         if (two) {
366                 g_object_remove_weak_pointer (G_OBJECT (two), (gpointer *)&two);
367                 debug_printf (1, "  Message 2 not destroyed?\n");
368                 errors++;
369                 g_object_unref (two);
370         }
371
372         /* Otherwise, if we haven't crashed, we're ok. */
373 }
374
375 /* SoupSession should clean up all signal handlers on a message after
376  * it is finished, allowing the message to be reused if desired.
377  * #559054
378  */
379 static void
380 ensure_no_signal_handlers (SoupMessage *msg, guint *signal_ids, guint n_signal_ids)
381 {
382         int i;
383
384         for (i = 0; i < n_signal_ids; i++) {
385                 if (g_signal_handler_find (msg, G_SIGNAL_MATCH_ID, signal_ids[i],
386                                            0, NULL, NULL, NULL)) {
387                         debug_printf (1, "    Message has handler for '%s'\n",
388                                       g_signal_name (signal_ids[i]));
389                         errors++;
390                 }
391         }
392 }
393
394 static void
395 reuse_test_authenticate (SoupSession *session, SoupMessage *msg,
396                          SoupAuth *auth, gboolean retrying)
397 {
398         /* Get it wrong the first time, then succeed */
399         if (!retrying)
400                 soup_auth_authenticate (auth, "user", "wrong password");
401         else
402                 soup_auth_authenticate (auth, "user", "password");
403 }
404
405 static void
406 do_msg_reuse_test (void)
407 {
408         SoupSession *session;
409         SoupMessage *msg;
410         SoupURI *uri;
411         guint *signal_ids, n_signal_ids;
412
413         debug_printf (1, "\nSoupMessage reuse\n");
414
415         signal_ids = g_signal_list_ids (SOUP_TYPE_MESSAGE, &n_signal_ids);
416
417         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
418         g_signal_connect (session, "authenticate",
419                           G_CALLBACK (reuse_test_authenticate), NULL);
420
421         debug_printf (1, "  First message\n");
422         msg = soup_message_new_from_uri ("GET", base_uri);
423         soup_session_send_message (session, msg);
424         ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
425
426         debug_printf (1, "  Redirect message\n");
427         uri = soup_uri_new_with_base (base_uri, "/redirect");
428         soup_message_set_uri (msg, uri);
429         soup_uri_free (uri);
430         soup_session_send_message (session, msg);
431         if (!soup_uri_equal (soup_message_get_uri (msg), base_uri)) {
432                 debug_printf (1, "    Message did not get redirected!\n");
433                 errors++;
434         }
435         ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
436
437         debug_printf (1, "  Auth message\n");
438         uri = soup_uri_new_with_base (base_uri, "/auth");
439         soup_message_set_uri (msg, uri);
440         soup_uri_free (uri);
441         soup_session_send_message (session, msg);
442         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
443                 debug_printf (1, "    Message did not get authenticated!\n");
444                 errors++;
445         }
446         ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
447
448         /* One last try to make sure the auth stuff got cleaned up */
449         debug_printf (1, "  Last message\n");
450         soup_message_set_uri (msg, base_uri);
451         soup_session_send_message (session, msg);
452         ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
453
454         soup_test_session_abort_unref (session);
455         g_object_unref (msg);
456         g_free (signal_ids);
457 }
458
459 /* Server handlers for "*" work but are separate from handlers for
460  * all other URIs. #590751
461  */
462 static void
463 do_star_test (void)
464 {
465         SoupSession *session;
466         SoupMessage *msg;
467         SoupURI *star_uri;
468         const char *handled_by;
469
470         debug_printf (1, "\nOPTIONS *\n");
471
472         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
473         star_uri = soup_uri_copy (base_uri);
474         soup_uri_set_path (star_uri, "*");
475
476         debug_printf (1, "  Testing with no handler\n");
477         msg = soup_message_new_from_uri ("OPTIONS", star_uri);
478         soup_session_send_message (session, msg);
479
480         if (msg->status_code != SOUP_STATUS_NOT_FOUND) {
481                 debug_printf (1, "    Unexpected response: %d %s\n",
482                               msg->status_code, msg->reason_phrase);
483                 errors++;
484         }
485         handled_by = soup_message_headers_get_one (msg->response_headers,
486                                                    "X-Handled-By");
487         if (handled_by) {
488                 /* Should have been rejected by SoupServer directly */
489                 debug_printf (1, "    Message reached handler '%s'\n",
490                               handled_by);
491                 errors++;
492         }
493         g_object_unref (msg);
494
495         soup_server_add_handler (server, "*", server_star_callback, NULL, NULL);
496
497         debug_printf (1, "  Testing with handler\n");
498         msg = soup_message_new_from_uri ("OPTIONS", star_uri);
499         soup_session_send_message (session, msg);
500
501         if (msg->status_code != SOUP_STATUS_OK) {
502                 debug_printf (1, "    Unexpected response: %d %s\n",
503                               msg->status_code, msg->reason_phrase);
504                 errors++;
505         }
506         handled_by = soup_message_headers_get_one (msg->response_headers,
507                                                    "X-Handled-By");
508         if (!handled_by) {
509                 debug_printf (1, "    Message did not reach handler!\n");
510                 errors++;
511         } else if (strcmp (handled_by, "star_callback") != 0) {
512                 debug_printf (1, "    Message reached incorrect handler '%s'\n",
513                               handled_by);
514                 errors++;
515         }
516         g_object_unref (msg);
517
518         soup_test_session_abort_unref (session);
519         soup_uri_free (star_uri);
520 }
521
522 /* Handle unexpectedly-early aborts. #596074, #618641 */
523 static void
524 ea_msg_completed_one (SoupSession *session, SoupMessage *msg, gpointer loop)
525 {
526         debug_printf (2, "  Message 1 completed\n");
527         if (msg->status_code != SOUP_STATUS_CANCELLED) {
528                 debug_printf (1, "  Unexpected status on Message 1: %d %s\n",
529                               msg->status_code, msg->reason_phrase);
530                 errors++;
531         }
532         g_main_loop_quit (loop);
533 }
534
535 static gboolean
536 ea_abort_session (gpointer session)
537 {
538         soup_session_abort (session);
539         return FALSE;
540 }
541
542 static void
543 ea_connection_state_changed (GObject *conn, GParamSpec *pspec, gpointer session)
544 {
545         SoupConnectionState state;
546
547         g_object_get (conn, "state", &state, NULL);
548         if (state == SOUP_CONNECTION_CONNECTING) {
549                 g_idle_add_full (G_PRIORITY_HIGH,
550                                  ea_abort_session,
551                                  session, NULL);
552                 g_signal_handlers_disconnect_by_func (conn, ea_connection_state_changed, session);
553         }
554 }               
555
556 static void
557 ea_connection_created (SoupSession *session, GObject *conn, gpointer user_data)
558 {
559         g_signal_connect (conn, "notify::state",
560                           G_CALLBACK (ea_connection_state_changed), session);
561         g_signal_handlers_disconnect_by_func (session, ea_connection_created, user_data);
562 }
563
564 static void
565 do_early_abort_test (void)
566 {
567         SoupSession *session;
568         SoupMessage *msg;
569         GMainContext *context;
570         GMainLoop *loop;
571
572         debug_printf (1, "\nAbort with pending connection\n");
573
574         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
575         msg = soup_message_new_from_uri ("GET", base_uri);
576
577         context = g_main_context_default ();
578         loop = g_main_loop_new (context, TRUE);
579         soup_session_queue_message (session, msg, ea_msg_completed_one, loop);
580         g_main_context_iteration (context, FALSE);
581
582         soup_session_abort (session);
583         while (g_main_context_pending (context))
584                 g_main_context_iteration (context, FALSE);
585         g_main_loop_unref (loop);
586         soup_test_session_abort_unref (session);
587
588         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
589         msg = soup_message_new_from_uri ("GET", base_uri);
590
591         g_signal_connect (session, "connection-created",
592                           G_CALLBACK (ea_connection_created), NULL);
593         soup_session_send_message (session, msg);
594         debug_printf (2, "  Message 2 completed\n");
595
596         if (msg->status_code != SOUP_STATUS_CANCELLED) {
597                 debug_printf (1, "    Unexpected response: %d %s\n",
598                               msg->status_code, msg->reason_phrase);
599                 errors++;
600         }
601         g_object_unref (msg);
602
603         while (g_main_context_pending (context))
604                 g_main_context_iteration (context, FALSE);
605
606         soup_test_session_abort_unref (session);
607 }
608
609 static void
610 do_content_length_framing_test (void)
611 {
612         SoupSession *session;
613         SoupMessage *msg;
614         SoupURI *request_uri;
615         goffset declared_length;
616
617         debug_printf (1, "\nInvalid Content-Length framing tests\n");
618
619         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
620
621         debug_printf (1, "  Content-Length larger than message body length\n");
622         request_uri = soup_uri_new_with_base (base_uri, "/content-length/long");
623         msg = soup_message_new_from_uri ("GET", request_uri);
624         soup_session_send_message (session, msg);
625         if (msg->status_code != SOUP_STATUS_OK) {
626                 debug_printf (1, "    Unexpected response: %d %s\n",
627                               msg->status_code, msg->reason_phrase);
628                 errors++;
629         } else {
630                 declared_length = soup_message_headers_get_content_length (msg->response_headers);
631                 debug_printf (2, "    Content-Length: %lu, body: %s\n",
632                               (gulong)declared_length, msg->response_body->data);
633                 if (msg->response_body->length >= declared_length) {
634                         debug_printf (1, "    Body length %lu >= declared length %lu\n",
635                                       (gulong)msg->response_body->length,
636                                       (gulong)declared_length);
637                         errors++;
638                 }
639         }
640         soup_uri_free (request_uri);
641         g_object_unref (msg);
642
643         debug_printf (1, "  Server claims 'Connection: close' but doesn't\n");
644         request_uri = soup_uri_new_with_base (base_uri, "/content-length/noclose");
645         msg = soup_message_new_from_uri ("GET", request_uri);
646         soup_session_send_message (session, msg);
647         if (msg->status_code != SOUP_STATUS_OK) {
648                 debug_printf (1, "    Unexpected response: %d %s\n",
649                               msg->status_code, msg->reason_phrase);
650                 errors++;
651         } else {
652                 declared_length = soup_message_headers_get_content_length (msg->response_headers);
653                 debug_printf (2, "    Content-Length: %lu, body: %s\n",
654                               (gulong)declared_length, msg->response_body->data);
655                 if (msg->response_body->length != declared_length) {
656                         debug_printf (1, "    Body length %lu != declared length %lu\n",
657                                       (gulong)msg->response_body->length,
658                                       (gulong)declared_length);
659                         errors++;
660                 }
661         }
662         soup_uri_free (request_uri);
663         g_object_unref (msg);
664
665         soup_test_session_abort_unref (session);
666 }
667
668 static void
669 do_one_accept_language_test (const char *language, const char *expected_header)
670 {
671         SoupSession *session;
672         SoupMessage *msg;
673         const char *val;
674
675         debug_printf (1, "  LANGUAGE=%s\n", language);
676         g_setenv ("LANGUAGE", language, TRUE);
677         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC,
678                                          SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE,
679                                          NULL);
680         msg = soup_message_new_from_uri ("GET", base_uri);
681         soup_session_send_message (session, msg);
682         soup_test_session_abort_unref (session);
683
684         if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
685                 debug_printf (1, "    Message failed? %d %s\n",
686                               msg->status_code, msg->reason_phrase);
687                 errors++;
688         }
689         val = soup_message_headers_get_list (msg->request_headers,
690                                              "Accept-Language");
691         if (!val) {
692                 debug_printf (1, "    No Accept-Language set!\n");
693                 errors++;
694         } else if (strcmp (val, expected_header) != 0) {
695                 debug_printf (1, "    Wrong Accept-Language: expected '%s', got '%s'\n",
696                               expected_header, val);
697                 errors++;
698         }
699
700         g_object_unref (msg);
701 }
702
703 static void
704 do_accept_language_test (void)
705 {
706         const char *orig_language;
707
708         debug_printf (1, "\nAutomatic Accept-Language processing\n");
709
710         orig_language = g_getenv ("LANGUAGE");
711         do_one_accept_language_test ("C", "en");
712         do_one_accept_language_test ("fr_FR", "fr-fr, fr;q=0.9");
713         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");
714
715         if (orig_language)
716                 g_setenv ("LANGUAGE", orig_language, TRUE);
717         else
718                 g_unsetenv ("LANGUAGE");
719 }
720
721 static void
722 timeout_test_request_started (SoupSession *session, SoupMessage *msg,
723                               SoupSocket *socket, gpointer user_data)
724 {
725         SoupSocket **sockets = user_data;
726         int i;
727
728         debug_printf (2, "      msg %p => socket %p\n", msg, socket);
729         for (i = 0; i < 4; i++) {
730                 if (!sockets[i]) {
731                         /* We ref the socket to make sure that even if
732                          * it gets disconnected, it doesn't get freed,
733                          * since our checks would get messed up if the
734                          * slice allocator reused the same address for
735                          * two consecutive sockets.
736                          */
737                         sockets[i] = g_object_ref (socket);
738                         return;
739                 }
740         }
741
742         debug_printf (1, "      socket queue overflowed!\n");
743         errors++;
744         soup_session_cancel_message (session, msg, SOUP_STATUS_CANCELLED);
745 }
746
747 static void
748 do_timeout_test_for_session (SoupSession *session)
749 {
750         SoupMessage *msg;
751         SoupSocket *sockets[4] = { NULL, NULL, NULL, NULL };
752         SoupURI *timeout_uri;
753         int i;
754
755         g_signal_connect (session, "request-started",
756                           G_CALLBACK (timeout_test_request_started),
757                           &sockets);
758
759         debug_printf (1, "    First message\n");
760         timeout_uri = soup_uri_new_with_base (base_uri, "/timeout-persistent");
761         msg = soup_message_new_from_uri ("GET", timeout_uri);
762         soup_uri_free (timeout_uri);
763         soup_session_send_message (session, msg);
764         if (msg->status_code != SOUP_STATUS_OK) {
765                 debug_printf (1, "      Unexpected response: %d %s\n",
766                               msg->status_code, msg->reason_phrase);
767                 errors++;
768         }
769         if (sockets[1]) {
770                 debug_printf (1, "      Message was retried??\n");
771                 errors++;
772                 sockets[1] = sockets[2] = sockets[3] = NULL;
773         }
774         g_object_unref (msg);
775
776         debug_printf (1, "    Second message\n");
777         msg = soup_message_new_from_uri ("GET", base_uri);
778         soup_session_send_message (session, msg);
779         if (msg->status_code != SOUP_STATUS_OK) {
780                 debug_printf (1, "      Unexpected response: %d %s\n",
781                               msg->status_code, msg->reason_phrase);
782                 errors++;
783         }
784         if (sockets[1] != sockets[0]) {
785                 debug_printf (1, "      Message was not retried on existing connection\n");
786                 errors++;
787         } else if (!sockets[2]) {
788                 debug_printf (1, "      Message was not retried after disconnect\n");
789                 errors++;
790         } else if (sockets[2] == sockets[1]) {
791                 debug_printf (1, "      Message was retried on closed connection??\n");
792                 errors++;
793         } else if (sockets[3]) {
794                 debug_printf (1, "      Message was retried again??\n");
795                 errors++;
796         }
797         g_object_unref (msg);
798
799         for (i = 0; sockets[i]; i++)
800                 g_object_unref (sockets[i]);
801 }
802
803 static void
804 do_persistent_connection_timeout_test (void)
805 {
806         SoupSession *session;
807
808         debug_printf (1, "\nUnexpected timing out of persistent connections\n");
809
810         debug_printf (1, "  Async session\n");
811         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
812         do_timeout_test_for_session (session);
813         soup_test_session_abort_unref (session);
814
815         debug_printf (1, "  Sync session\n");
816         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
817         do_timeout_test_for_session (session);
818         soup_test_session_abort_unref (session);
819 }
820
821 static GMainLoop *max_conns_loop;
822 static int msgs_done;
823 #define MAX_CONNS 2
824 #define TEST_CONNS (MAX_CONNS * 2)
825
826 static gboolean
827 idle_start_server (gpointer data)
828 {
829         g_mutex_unlock (server_mutex);
830         return FALSE;
831 }
832
833 static gboolean
834 quit_loop (gpointer data)
835 {
836         g_main_loop_quit (max_conns_loop);
837         return FALSE;
838 }
839
840 static void
841 max_conns_request_started (SoupSession *session, SoupMessage *msg,
842                            SoupSocket *socket, gpointer user_data)
843 {
844         if (++msgs_done == MAX_CONNS)
845                 g_timeout_add (100, quit_loop, NULL);
846 }
847
848 static void
849 max_conns_message_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
850 {
851         if (++msgs_done == TEST_CONNS)
852                 g_main_loop_quit (max_conns_loop);
853 }
854
855 static void
856 do_max_conns_test_for_session (SoupSession *session)
857 {
858         SoupMessage *msgs[TEST_CONNS];
859         int i;
860         guint timeout_id;
861
862         max_conns_loop = g_main_loop_new (NULL, TRUE);
863
864         g_mutex_lock (server_mutex);
865
866         g_signal_connect (session, "request-started",
867                           G_CALLBACK (max_conns_request_started), NULL);
868         msgs_done = 0;
869         for (i = 0; i < TEST_CONNS; i++) {
870                 msgs[i] = soup_message_new_from_uri ("GET", base_uri);
871                 g_object_ref (msgs[i]);
872                 soup_session_queue_message (session, msgs[i],
873                                             max_conns_message_complete, NULL);
874         }
875
876         g_main_loop_run (max_conns_loop);
877         if (msgs_done != MAX_CONNS) {
878                 debug_printf (1, "  Queued %d connections out of max %d?",
879                               msgs_done, MAX_CONNS);
880                 errors++;
881         }
882         g_signal_handlers_disconnect_by_func (session, max_conns_request_started, NULL);
883
884         msgs_done = 0;
885         g_idle_add (idle_start_server, NULL);
886         timeout_id = g_timeout_add (1000, quit_loop, NULL);
887         g_main_loop_run (max_conns_loop);
888
889         for (i = 0; i < TEST_CONNS; i++) {
890                 if (!SOUP_STATUS_IS_SUCCESSFUL (msgs[i]->status_code)) {
891                         debug_printf (1, "    Message %d failed? %d %s\n",
892                                       i, msgs[i]->status_code,
893                                       msgs[i]->reason_phrase ? msgs[i]->reason_phrase : "-");
894                         errors++;
895                 }
896         }
897
898         if (msgs_done != TEST_CONNS) {
899                 /* Clean up so we don't get a spurious "Leaked
900                  * session" error.
901                  */
902                 for (i = 0; i < TEST_CONNS; i++)
903                         soup_session_cancel_message (session, msgs[i], SOUP_STATUS_CANCELLED);
904                 g_main_loop_run (max_conns_loop);
905                 g_source_remove (timeout_id);
906         }
907
908         g_main_loop_unref (max_conns_loop);
909
910         for (i = 0; i < TEST_CONNS; i++)
911                 g_object_unref (msgs[i]);
912 }
913
914 static void
915 do_max_conns_test (void)
916 {
917         SoupSession *session;
918
919         debug_printf (1, "\nExceeding max-conns\n");
920
921         debug_printf (1, "  Async session\n");
922         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
923                                          SOUP_SESSION_MAX_CONNS, MAX_CONNS,
924                                          NULL);
925         do_max_conns_test_for_session (session);
926         soup_test_session_abort_unref (session);
927
928         debug_printf (1, "  Sync session\n");
929         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC,
930                                          SOUP_SESSION_MAX_CONNS, MAX_CONNS,
931                                          NULL);
932         do_max_conns_test_for_session (session);
933         soup_test_session_abort_unref (session);
934 }
935
936 static gboolean
937 cancel_message_timeout (gpointer msg)
938 {
939         SoupSession *session = g_object_get_data (G_OBJECT (msg), "session");
940
941         soup_session_cancel_message (session, msg, SOUP_STATUS_CANCELLED);
942         g_object_unref (msg);
943         g_object_unref (session);
944         return FALSE;
945 }
946
947 static gpointer
948 cancel_message_thread (gpointer msg)
949 {
950         SoupSession *session = g_object_get_data (G_OBJECT (msg), "session");
951
952         g_usleep (100000); /* .1s */
953         soup_session_cancel_message (session, msg, SOUP_STATUS_CANCELLED);
954         g_object_unref (msg);
955         g_object_unref (session);
956         return NULL;
957 }
958
959 static void
960 do_cancel_while_reading_test_for_session (SoupSession *session)
961 {
962         SoupMessage *msg;
963         GThread *thread = NULL;
964         SoupURI *uri;
965
966         uri = soup_uri_new_with_base (base_uri, "/slow");
967         msg = soup_message_new_from_uri ("GET", uri);
968         soup_uri_free (uri);
969
970         g_object_set_data (G_OBJECT (msg), "session", session);
971         g_object_ref (msg);
972         g_object_ref (session);
973         if (SOUP_IS_SESSION_ASYNC (session))
974                 g_timeout_add (100, cancel_message_timeout, msg);
975         else
976                 thread = g_thread_create (cancel_message_thread, msg, TRUE, NULL);
977
978         soup_session_send_message (session, msg);
979
980         if (msg->status_code != SOUP_STATUS_CANCELLED) {
981                 debug_printf (1, "      FAILED: %d %s (expected Cancelled)\n",
982                               msg->status_code, msg->reason_phrase);
983                 errors++;
984         }
985         g_object_unref (msg);
986
987         if (thread)
988                 g_thread_join (thread);
989 }
990
991 static void
992 do_cancel_while_reading_test (void)
993 {
994         SoupSession *session;
995
996         debug_printf (1, "\nCancelling message while reading response\n");
997
998         debug_printf (1, "  Async session\n");
999         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
1000         do_cancel_while_reading_test_for_session (session);
1001         soup_test_session_abort_unref (session);
1002
1003         debug_printf (1, "  Sync session\n");
1004         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
1005         do_cancel_while_reading_test_for_session (session);
1006         soup_test_session_abort_unref (session);
1007 }
1008
1009 int
1010 main (int argc, char **argv)
1011 {
1012         SoupAuthDomain *auth_domain;
1013
1014         test_init (argc, argv, NULL);
1015
1016         server_mutex = g_mutex_new ();
1017
1018         server = soup_test_server_new (TRUE);
1019         soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
1020         base_uri = soup_uri_new ("http://127.0.0.1/");
1021         soup_uri_set_port (base_uri, soup_server_get_port (server));
1022
1023         auth_domain = soup_auth_domain_basic_new (
1024                 SOUP_AUTH_DOMAIN_REALM, "misc-test",
1025                 SOUP_AUTH_DOMAIN_ADD_PATH, "/auth",
1026                 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, auth_callback,
1027                 NULL);
1028         soup_server_add_auth_domain (server, auth_domain);
1029         g_object_unref (auth_domain);
1030
1031         do_host_test ();
1032         do_callback_unref_test ();
1033         do_msg_reuse_test ();
1034         do_star_test ();
1035         do_early_abort_test ();
1036         do_content_length_framing_test ();
1037         do_accept_language_test ();
1038         do_persistent_connection_timeout_test ();
1039         do_max_conns_test ();
1040         do_cancel_while_reading_test ();
1041
1042         soup_uri_free (base_uri);
1043         soup_test_server_quit_unref (server);
1044
1045         test_cleanup ();
1046         return errors != 0;
1047 }