tests: add cache-test
[platform/upstream/libsoup.git] / tests / connection-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 "test-utils.h"
7
8 SoupServer *server;
9 SoupURI *base_uri;
10 GMutex server_mutex;
11
12 static void
13 forget_close (SoupMessage *msg, gpointer user_data)
14 {
15         soup_message_headers_remove (msg->response_headers, "Connection");
16 }
17
18 static void
19 close_socket (SoupMessage *msg, gpointer user_data)
20 {
21         SoupSocket *sock = user_data;
22         int sockfd;
23
24         /* Actually calling soup_socket_disconnect() here would cause
25          * us to leak memory, so just shutdown the socket instead.
26          */
27         sockfd = soup_socket_get_fd (sock);
28 #ifdef G_OS_WIN32
29         shutdown (sockfd, SD_SEND);
30 #else
31         shutdown (sockfd, SHUT_WR);
32 #endif
33
34         /* Then add the missing data to the message now, so SoupServer
35          * can clean up after itself properly.
36          */
37         soup_message_body_append (msg->response_body, SOUP_MEMORY_STATIC,
38                                   "foo", 3);
39 }
40
41 static void
42 timeout_socket (SoupSocket *sock, gpointer user_data)
43 {
44         soup_socket_disconnect (sock);
45 }
46
47 static void
48 timeout_request_started (SoupServer *server, SoupMessage *msg,
49                          SoupClientContext *client, gpointer user_data)
50 {
51         SoupSocket *sock;
52         GMainContext *context = soup_server_get_async_context (server);
53         guint readable;
54
55         sock = soup_client_context_get_socket (client);
56         readable = g_signal_connect (sock, "readable",
57                                     G_CALLBACK (timeout_socket), NULL);
58         while (soup_socket_is_connected (sock))
59                 g_main_context_iteration (context, TRUE);
60         g_signal_handler_disconnect (sock, readable);
61         g_signal_handlers_disconnect_by_func (server, timeout_request_started, NULL);
62 }
63
64 static void
65 setup_timeout_persistent (SoupServer *server, SoupSocket *sock)
66 {
67         char buf[1];
68         gsize nread;
69
70         /* In order for the test to work correctly, we have to
71          * close the connection *after* the client side writes
72          * the request. To ensure that this happens reliably,
73          * regardless of thread scheduling, we:
74          *
75          *   1. Try to read off the socket now, knowing it will
76          *      fail (since the client is waiting for us to
77          *      return a response). This will cause it to
78          *      emit "readable" later.
79          *   2. Connect to the server's request-started signal.
80          *   3. Run an inner main loop from that signal handler
81          *      until the socket emits "readable". (If we don't
82          *      do this then it's possible the client's next
83          *      request would be ready before we returned to
84          *      the main loop, and so the signal would never be
85          *      emitted.)
86          *   4. Close the socket.
87          */
88
89         soup_socket_read (sock, buf, 1, &nread, NULL, NULL);
90         g_signal_connect (server, "request-started",
91                           G_CALLBACK (timeout_request_started), NULL);
92 }
93
94 static void
95 server_callback (SoupServer *server, SoupMessage *msg,
96                  const char *path, GHashTable *query,
97                  SoupClientContext *context, gpointer data)
98 {
99         /* The way this gets used in the tests, we don't actually
100          * need to hold it through the whole function, so it's simpler
101          * to just release it right away.
102          */
103         g_mutex_lock (&server_mutex);
104         g_mutex_unlock (&server_mutex);
105
106         if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_POST) {
107                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
108                 return;
109         }
110
111         if (g_str_has_prefix (path, "/content-length/")) {
112                 gboolean too_long = strcmp (path, "/content-length/long") == 0;
113                 gboolean no_close = strcmp (path, "/content-length/noclose") == 0;
114
115                 soup_message_set_status (msg, SOUP_STATUS_OK);
116                 soup_message_set_response (msg, "text/plain",
117                                            SOUP_MEMORY_STATIC, "foobar", 6);
118                 if (too_long)
119                         soup_message_headers_set_content_length (msg->response_headers, 9);
120                 soup_message_headers_append (msg->response_headers,
121                                              "Connection", "close");
122
123                 if (too_long) {
124                         SoupSocket *sock;
125
126                         /* soup-message-io will wait for us to add
127                          * another chunk after the first, to fill out
128                          * the declared Content-Length. Instead, we
129                          * forcibly close the socket at that point.
130                          */
131                         sock = soup_client_context_get_socket (context);
132                         g_signal_connect (msg, "wrote-chunk",
133                                           G_CALLBACK (close_socket), sock);
134                 } else if (no_close) {
135                         /* Remove the 'Connection: close' after writing
136                          * the headers, so that when we check it after
137                          * writing the body, we'll think we aren't
138                          * supposed to close it.
139                          */
140                         g_signal_connect (msg, "wrote-headers",
141                                           G_CALLBACK (forget_close), NULL);
142                 }
143                 return;
144         }
145
146         if (!strcmp (path, "/timeout-persistent")) {
147                 SoupSocket *sock;
148
149                 sock = soup_client_context_get_socket (context);
150                 setup_timeout_persistent (server, sock);
151         }
152
153         soup_message_set_status (msg, SOUP_STATUS_OK);
154         soup_message_set_response (msg, "text/plain",
155                                    SOUP_MEMORY_STATIC, "index", 5);
156         return;
157 }
158
159 static void
160 do_content_length_framing_test (void)
161 {
162         SoupSession *session;
163         SoupMessage *msg;
164         SoupURI *request_uri;
165         goffset declared_length;
166
167         debug_printf (1, "\nInvalid Content-Length framing tests\n");
168
169         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
170
171         debug_printf (1, "  Content-Length larger than message body length\n");
172         request_uri = soup_uri_new_with_base (base_uri, "/content-length/long");
173         msg = soup_message_new_from_uri ("GET", request_uri);
174         soup_session_send_message (session, msg);
175         if (msg->status_code != SOUP_STATUS_OK) {
176                 debug_printf (1, "    Unexpected response: %d %s\n",
177                               msg->status_code, msg->reason_phrase);
178                 errors++;
179         } else {
180                 declared_length = soup_message_headers_get_content_length (msg->response_headers);
181                 debug_printf (2, "    Content-Length: %lu, body: %s\n",
182                               (gulong)declared_length, msg->response_body->data);
183                 if (msg->response_body->length >= declared_length) {
184                         debug_printf (1, "    Body length %lu >= declared length %lu\n",
185                                       (gulong)msg->response_body->length,
186                                       (gulong)declared_length);
187                         errors++;
188                 }
189         }
190         soup_uri_free (request_uri);
191         g_object_unref (msg);
192
193         debug_printf (1, "  Server claims 'Connection: close' but doesn't\n");
194         request_uri = soup_uri_new_with_base (base_uri, "/content-length/noclose");
195         msg = soup_message_new_from_uri ("GET", request_uri);
196         soup_session_send_message (session, msg);
197         if (msg->status_code != SOUP_STATUS_OK) {
198                 debug_printf (1, "    Unexpected response: %d %s\n",
199                               msg->status_code, msg->reason_phrase);
200                 errors++;
201         } else {
202                 declared_length = soup_message_headers_get_content_length (msg->response_headers);
203                 debug_printf (2, "    Content-Length: %lu, body: %s\n",
204                               (gulong)declared_length, msg->response_body->data);
205                 if (msg->response_body->length != declared_length) {
206                         debug_printf (1, "    Body length %lu != declared length %lu\n",
207                                       (gulong)msg->response_body->length,
208                                       (gulong)declared_length);
209                         errors++;
210                 }
211         }
212         soup_uri_free (request_uri);
213         g_object_unref (msg);
214
215         soup_test_session_abort_unref (session);
216 }
217
218 static void
219 request_started_socket_collector (SoupSession *session, SoupMessage *msg,
220                                   SoupSocket *socket, gpointer user_data)
221 {
222         SoupSocket **sockets = user_data;
223         int i;
224
225         debug_printf (2, "      msg %p => socket %p\n", msg, socket);
226         for (i = 0; i < 4; i++) {
227                 if (!sockets[i]) {
228                         /* We ref the socket to make sure that even if
229                          * it gets disconnected, it doesn't get freed,
230                          * since our checks would get messed up if the
231                          * slice allocator reused the same address for
232                          * two consecutive sockets.
233                          */
234                         sockets[i] = g_object_ref (socket);
235                         return;
236                 }
237         }
238
239         debug_printf (1, "      socket queue overflowed!\n");
240         errors++;
241 }
242
243 static void
244 do_timeout_test_for_session (SoupSession *session)
245 {
246         SoupMessage *msg;
247         SoupSocket *sockets[4] = { NULL, NULL, NULL, NULL };
248         SoupURI *timeout_uri;
249         int i;
250
251         g_signal_connect (session, "request-started",
252                           G_CALLBACK (request_started_socket_collector),
253                           &sockets);
254
255         debug_printf (1, "    First message\n");
256         timeout_uri = soup_uri_new_with_base (base_uri, "/timeout-persistent");
257         msg = soup_message_new_from_uri ("GET", timeout_uri);
258         soup_uri_free (timeout_uri);
259         soup_session_send_message (session, msg);
260         if (msg->status_code != SOUP_STATUS_OK) {
261                 debug_printf (1, "      Unexpected response: %d %s\n",
262                               msg->status_code, msg->reason_phrase);
263                 errors++;
264         }
265         if (sockets[1]) {
266                 debug_printf (1, "      Message was retried??\n");
267                 errors++;
268                 sockets[1] = sockets[2] = sockets[3] = NULL;
269         }
270         g_object_unref (msg);
271
272         debug_printf (1, "    Second message\n");
273         msg = soup_message_new_from_uri ("GET", base_uri);
274         soup_session_send_message (session, msg);
275         if (msg->status_code != SOUP_STATUS_OK) {
276                 debug_printf (1, "      Unexpected response: %d %s\n",
277                               msg->status_code, msg->reason_phrase);
278                 errors++;
279         }
280         if (sockets[1] != sockets[0]) {
281                 debug_printf (1, "      Message was not retried on existing connection\n");
282                 errors++;
283         } else if (!sockets[2]) {
284                 debug_printf (1, "      Message was not retried after disconnect\n");
285                 errors++;
286         } else if (sockets[2] == sockets[1]) {
287                 debug_printf (1, "      Message was retried on closed connection??\n");
288                 errors++;
289         } else if (sockets[3]) {
290                 debug_printf (1, "      Message was retried again??\n");
291                 errors++;
292         }
293         g_object_unref (msg);
294
295         for (i = 0; sockets[i]; i++)
296                 g_object_unref (sockets[i]);
297 }
298
299 static void
300 do_timeout_req_test_for_session (SoupSession *session)
301 {
302         SoupRequest *req;
303         SoupRequestHTTP *http;
304         GInputStream *stream;
305         SoupSocket *sockets[4] = { NULL, NULL, NULL, NULL };
306         SoupURI *timeout_uri;
307         GError *error = NULL;
308         int i;
309
310         g_signal_connect (session, "request-started",
311                           G_CALLBACK (request_started_socket_collector),
312                           &sockets);
313
314         debug_printf (1, "    First request\n");
315         timeout_uri = soup_uri_new_with_base (base_uri, "/timeout-persistent");
316         req = soup_session_request_uri (session, timeout_uri, NULL);
317         soup_uri_free (timeout_uri);
318
319         stream = soup_test_request_send (req, NULL, &error);
320         if (!stream) {
321                 debug_printf (1, "      Unexpected error on send: %s\n",
322                               error->message);
323                 errors++;
324                 g_clear_error (&error);
325         } else {
326                 soup_test_request_close_stream (req, stream, NULL, &error);
327                 if (error) {
328                         debug_printf (1, "  Unexpected error on close: %s\n",
329                                       error->message);
330                         errors++;
331                         g_clear_error (&error);
332                 }
333                 g_object_unref (stream);
334         }
335
336         if (sockets[1]) {
337                 debug_printf (1, "      Message was retried??\n");
338                 errors++;
339                 sockets[1] = sockets[2] = sockets[3] = NULL;
340         }
341         g_object_unref (req);
342
343         debug_printf (1, "    Second request\n");
344         req = soup_session_request_uri (session, base_uri, NULL);
345
346         stream = soup_test_request_send (req, NULL, &error);
347         if (!stream) {
348                 debug_printf (1, "      Unexpected error on send: %s\n",
349                               error->message);
350                 errors++;
351                 g_clear_error (&error);
352         } else {
353                 soup_test_request_close_stream (req, stream, NULL, &error);
354                 if (error) {
355                         debug_printf (1, "  Unexpected error on close: %s\n",
356                                       error->message);
357                         errors++;
358                         g_clear_error (&error);
359                 }
360                 g_object_unref (stream);
361         }
362
363         http = SOUP_REQUEST_HTTP (req);
364         if (http->status_code != SOUP_STATUS_OK) {
365                 debug_printf (1, "      Unexpected response: %d %s\n",
366                               http->status_code, http->reason_phrase);
367                 errors++;
368         }
369         if (sockets[1] != sockets[0]) {
370                 debug_printf (1, "      Message was not retried on existing connection\n");
371                 errors++;
372         } else if (!sockets[2]) {
373                 debug_printf (1, "      Message was not retried after disconnect\n");
374                 errors++;
375         } else if (sockets[2] == sockets[1]) {
376                 debug_printf (1, "      Message was retried on closed connection??\n");
377                 errors++;
378         } else if (sockets[3]) {
379                 debug_printf (1, "      Message was retried again??\n");
380                 errors++;
381         }
382         g_object_unref (req);
383
384         for (i = 0; sockets[i]; i++)
385                 g_object_unref (sockets[i]);
386 }
387
388 static void
389 do_persistent_connection_timeout_test (void)
390 {
391         SoupSession *session;
392
393         debug_printf (1, "\nUnexpected timing out of persistent connections\n");
394
395         debug_printf (1, "  Async session, message API\n");
396         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
397         do_timeout_test_for_session (session);
398         soup_test_session_abort_unref (session);
399
400         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
401                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
402                                          NULL);
403         do_timeout_req_test_for_session (session);
404         soup_test_session_abort_unref (session);
405
406         debug_printf (1, "  Sync session, message API\n");
407         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
408         do_timeout_test_for_session (session);
409         soup_test_session_abort_unref (session);
410
411         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
412         do_timeout_req_test_for_session (session);
413         soup_test_session_abort_unref (session);
414 }
415
416 static GMainLoop *max_conns_loop;
417 static int msgs_done;
418 static guint quit_loop_timeout;
419 #define MAX_CONNS 2
420 #define TEST_CONNS (MAX_CONNS * 2)
421
422 static gboolean
423 idle_start_server (gpointer data)
424 {
425         g_mutex_unlock (&server_mutex);
426         return FALSE;
427 }
428
429 static gboolean
430 quit_loop (gpointer data)
431 {
432         quit_loop_timeout = 0;
433         g_main_loop_quit (max_conns_loop);
434         return FALSE;
435 }
436
437 static void
438 max_conns_request_started (SoupSession *session, SoupMessage *msg,
439                            SoupSocket *socket, gpointer user_data)
440 {
441         if (++msgs_done == MAX_CONNS) {
442                 if (quit_loop_timeout)
443                         g_source_remove (quit_loop_timeout);
444                 quit_loop_timeout = g_timeout_add (100, quit_loop, NULL);
445         }
446 }
447
448 static void
449 max_conns_message_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
450 {
451         if (++msgs_done == TEST_CONNS)
452                 g_main_loop_quit (max_conns_loop);
453 }
454
455 static void
456 do_max_conns_test_for_session (SoupSession *session)
457 {
458         SoupMessage *msgs[TEST_CONNS];
459         int i;
460
461         max_conns_loop = g_main_loop_new (NULL, TRUE);
462
463         g_mutex_lock (&server_mutex);
464
465         g_signal_connect (session, "request-started",
466                           G_CALLBACK (max_conns_request_started), NULL);
467         msgs_done = 0;
468         for (i = 0; i < TEST_CONNS; i++) {
469                 msgs[i] = soup_message_new_from_uri ("GET", base_uri);
470                 g_object_ref (msgs[i]);
471                 soup_session_queue_message (session, msgs[i],
472                                             max_conns_message_complete, NULL);
473         }
474
475         g_main_loop_run (max_conns_loop);
476         if (msgs_done != MAX_CONNS) {
477                 debug_printf (1, "  Queued %d connections out of max %d?",
478                               msgs_done, MAX_CONNS);
479                 errors++;
480         }
481         g_signal_handlers_disconnect_by_func (session, max_conns_request_started, NULL);
482
483         msgs_done = 0;
484         g_idle_add (idle_start_server, NULL);
485         quit_loop_timeout = g_timeout_add (1000, quit_loop, NULL);
486         g_main_loop_run (max_conns_loop);
487
488         for (i = 0; i < TEST_CONNS; i++) {
489                 if (!SOUP_STATUS_IS_SUCCESSFUL (msgs[i]->status_code)) {
490                         debug_printf (1, "    Message %d failed? %d %s\n",
491                                       i, msgs[i]->status_code,
492                                       msgs[i]->reason_phrase ? msgs[i]->reason_phrase : "-");
493                         errors++;
494                 }
495         }
496
497         if (msgs_done != TEST_CONNS) {
498                 /* Clean up so we don't get a spurious "Leaked
499                  * session" error.
500                  */
501                 for (i = 0; i < TEST_CONNS; i++)
502                         soup_session_cancel_message (session, msgs[i], SOUP_STATUS_CANCELLED);
503                 g_main_loop_run (max_conns_loop);
504         }
505
506         g_main_loop_unref (max_conns_loop);
507         if (quit_loop_timeout)
508                 g_source_remove (quit_loop_timeout);
509
510         for (i = 0; i < TEST_CONNS; i++)
511                 g_object_unref (msgs[i]);
512 }
513
514 static void
515 do_max_conns_test (void)
516 {
517         SoupSession *session;
518
519         debug_printf (1, "\nExceeding max-conns\n");
520
521         debug_printf (1, "  Async session\n");
522         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
523                                          SOUP_SESSION_MAX_CONNS, MAX_CONNS,
524                                          NULL);
525         do_max_conns_test_for_session (session);
526         soup_test_session_abort_unref (session);
527
528         debug_printf (1, "  Sync session\n");
529         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC,
530                                          SOUP_SESSION_MAX_CONNS, MAX_CONNS,
531                                          NULL);
532         do_max_conns_test_for_session (session);
533         soup_test_session_abort_unref (session);
534 }
535
536 static void
537 np_request_started (SoupSession *session, SoupMessage *msg,
538                     SoupSocket *socket, gpointer user_data)
539 {
540         SoupSocket **save_socket = user_data;
541
542         *save_socket = g_object_ref (socket);
543 }
544
545 static void
546 np_request_unqueued (SoupSession *session, SoupMessage *msg,
547                      gpointer user_data)
548 {
549         SoupSocket *socket = *(SoupSocket **)user_data;
550
551         if (soup_socket_is_connected (socket)) {
552                 debug_printf (1, "    socket is still connected\n");
553                 errors++;
554         }
555 }
556
557 static void
558 np_request_finished (SoupSession *session, SoupMessage *msg,
559                      gpointer user_data)
560 {
561         GMainLoop *loop = user_data;
562
563         g_main_loop_quit (loop);
564 }
565
566 static void
567 do_non_persistent_test_for_session (SoupSession *session)
568 {
569         SoupMessage *msg;
570         SoupSocket *socket = NULL;
571         GMainLoop *loop;
572
573         loop = g_main_loop_new (NULL, FALSE);
574
575         g_signal_connect (session, "request-started",
576                           G_CALLBACK (np_request_started),
577                           &socket);
578         g_signal_connect (session, "request-unqueued",
579                           G_CALLBACK (np_request_unqueued),
580                           &socket);
581
582         msg = soup_message_new_from_uri ("GET", base_uri);
583         soup_message_headers_append (msg->request_headers, "Connection", "close");
584         g_object_ref (msg);
585         soup_session_queue_message (session, msg,
586                                     np_request_finished, loop);
587         g_main_loop_run (loop);
588         g_main_loop_unref (loop);
589
590         if (msg->status_code != SOUP_STATUS_OK) {
591                 debug_printf (1, "      Unexpected response: %d %s\n",
592                               msg->status_code, msg->reason_phrase);
593                 errors++;
594         }
595         g_object_unref (msg);
596         g_object_unref (socket);
597 }
598
599 static void
600 do_non_persistent_connection_test (void)
601 {
602         SoupSession *session;
603
604         debug_printf (1, "\nNon-persistent connections are closed immediately\n");
605
606         debug_printf (1, "  Async session\n");
607         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
608         do_non_persistent_test_for_session (session);
609         soup_test_session_abort_unref (session);
610
611         debug_printf (1, "  Sync session\n");
612         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
613         do_non_persistent_test_for_session (session);
614         soup_test_session_abort_unref (session);
615 }
616
617 static void
618 do_non_idempotent_test_for_session (SoupSession *session)
619 {
620         SoupMessage *msg;
621         SoupSocket *sockets[4] = { NULL, NULL, NULL, NULL };
622         int i;
623
624         g_signal_connect (session, "request-started",
625                           G_CALLBACK (request_started_socket_collector),
626                           &sockets);
627
628         debug_printf (2, "    GET\n");
629         msg = soup_message_new_from_uri ("GET", base_uri);
630         soup_session_send_message (session, msg);
631         if (msg->status_code != SOUP_STATUS_OK) {
632                 debug_printf (1, "      Unexpected response: %d %s\n",
633                               msg->status_code, msg->reason_phrase);
634                 errors++;
635         }
636         if (sockets[1]) {
637                 debug_printf (1, "      Message was retried??\n");
638                 errors++;
639                 sockets[1] = sockets[2] = sockets[3] = NULL;
640         }
641         g_object_unref (msg);
642
643         debug_printf (2, "    POST\n");
644         msg = soup_message_new_from_uri ("POST", base_uri);
645         soup_session_send_message (session, msg);
646         if (msg->status_code != SOUP_STATUS_OK) {
647                 debug_printf (1, "      Unexpected response: %d %s\n",
648                               msg->status_code, msg->reason_phrase);
649                 errors++;
650         }
651         if (sockets[1] == sockets[0]) {
652                 debug_printf (1, "      Message was sent on existing connection!\n");
653                 errors++;
654         }
655         if (sockets[2]) {
656                 debug_printf (1, "      Too many connections used...\n");
657                 errors++;
658         }
659         g_object_unref (msg);
660
661         for (i = 0; sockets[i]; i++)
662                 g_object_unref (sockets[i]);
663 }
664
665 static void
666 do_non_idempotent_connection_test (void)
667 {
668         SoupSession *session;
669
670         debug_printf (1, "\nNon-idempotent methods are always sent on new connections\n");
671
672         debug_printf (1, "  Async session\n");
673         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
674         do_non_idempotent_test_for_session (session);
675         soup_test_session_abort_unref (session);
676
677         debug_printf (1, "  Sync session\n");
678         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
679         do_non_idempotent_test_for_session (session);
680         soup_test_session_abort_unref (session);
681 }
682
683 #ifdef HAVE_APACHE
684
685 #define HTTP_SERVER  "http://127.0.0.1:47524"
686 #define HTTPS_SERVER "https://127.0.0.1:47525"
687 #define HTTP_PROXY   "http://127.0.0.1:47526"
688
689 static SoupConnectionState state_transitions[] = {
690         /* NEW -> */        SOUP_CONNECTION_CONNECTING,
691         /* CONNECTING -> */ SOUP_CONNECTION_IN_USE,
692         /* IDLE -> */       SOUP_CONNECTION_DISCONNECTED,
693         /* IN_USE -> */     SOUP_CONNECTION_IDLE,
694
695         /* REMOTE_DISCONNECTED */ -1,
696         /* DISCONNECTED */        -1,
697 };
698
699 static const char *state_names[] = {
700         "NEW", "CONNECTING", "IDLE", "IN_USE",
701         "REMOTE_DISCONNECTED", "DISCONNECTED"
702 };
703
704 static void
705 connection_state_changed (GObject *object, GParamSpec *param,
706                           gpointer user_data)
707 {
708         SoupConnectionState *state = user_data;
709         SoupConnectionState new_state;
710
711         g_object_get (object, "state", &new_state, NULL);
712         if (state_transitions[*state] != new_state) {
713                 debug_printf (1, "      Unexpected transition: %s -> %s\n",
714                               state_names[*state], state_names[new_state]);
715                 errors++;
716         } else {
717                 debug_printf (2, "      %s -> %s\n",
718                               state_names[*state], state_names[new_state]);
719         }
720
721         *state = new_state;
722 }
723
724 static void
725 connection_created (SoupSession *session, GObject *conn,
726                     gpointer user_data)
727 {
728         SoupConnectionState *state = user_data;
729
730         g_object_get (conn, "state", state, NULL);
731         if (*state != SOUP_CONNECTION_NEW) {
732                 debug_printf (1, "      Unexpected initial state: %d\n",
733                               *state);
734                 errors++;
735         }
736
737         g_signal_connect (conn, "notify::state",
738                           G_CALLBACK (connection_state_changed),
739                           state);
740 }
741
742 static void
743 do_one_connection_state_test (SoupSession *session, const char *uri)
744 {
745         SoupMessage *msg;
746
747         msg = soup_message_new ("GET", uri);
748         soup_session_send_message (session, msg);
749         if (msg->status_code != SOUP_STATUS_OK) {
750                 debug_printf (1, "      Unexpected response: %d %s\n",
751                               msg->status_code, msg->reason_phrase);
752                 errors++;
753         }
754         g_object_unref (msg);
755         soup_session_abort (session);
756 }
757
758 static void
759 do_connection_state_test_for_session (SoupSession *session)
760 {
761         SoupConnectionState state;
762         SoupURI *proxy_uri;
763
764         g_signal_connect (session, "connection-created",
765                           G_CALLBACK (connection_created),
766                           &state);
767
768         debug_printf (1, "    http\n");
769         do_one_connection_state_test (session, HTTP_SERVER);
770
771         debug_printf (1, "    https\n");
772         do_one_connection_state_test (session, HTTPS_SERVER);
773
774         proxy_uri = soup_uri_new (HTTP_PROXY);
775         g_object_set (G_OBJECT (session),
776                       SOUP_SESSION_PROXY_URI, proxy_uri,
777                       NULL);
778         soup_uri_free (proxy_uri);
779
780         debug_printf (1, "    http with proxy\n");
781         do_one_connection_state_test (session, HTTP_SERVER);
782
783         debug_printf (1, "    https with proxy\n");
784         do_one_connection_state_test (session, HTTPS_SERVER);
785 }
786
787 static void
788 do_connection_state_test (void)
789 {
790         SoupSession *session;
791
792         debug_printf (1, "\nConnection states\n");
793
794         debug_printf (1, "  Async session\n");
795         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
796         do_connection_state_test_for_session (session);
797         soup_test_session_abort_unref (session);
798
799         debug_printf (1, "  Sync session\n");
800         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
801         do_connection_state_test_for_session (session);
802         soup_test_session_abort_unref (session);
803 }
804
805
806 static const char *event_names[] = {
807         "RESOLVING", "RESOLVED", "CONNECTING", "CONNECTED",
808         "PROXY_NEGOTIATING", "PROXY_NEGOTIATED",
809         "TLS_HANDSHAKING", "TLS_HANDSHAKED", "COMPLETE"
810 };
811
812 static const char event_abbrevs[] = {
813         'r', 'R', 'c', 'C', 'p', 'P', 't', 'T', 'x', '\0'
814 };
815
816 static const char *
817 event_name_from_abbrev (char abbrev)
818 {
819         int evt;
820
821         for (evt = 0; event_abbrevs[evt]; evt++) {
822                 if (event_abbrevs[evt] == abbrev)
823                         return event_names[evt];
824         }
825         return "???";
826 }
827
828 static void
829 network_event (SoupMessage *msg, GSocketClientEvent event,
830                GIOStream *connection, gpointer user_data)
831 {
832         const char **events = user_data;
833
834         if (!**events) {
835                 debug_printf (1, "      Unexpected event: %s\n",
836                               event_names[event]);
837                 errors++;
838         } else {
839                 if (**events == event_abbrevs[event])
840                         debug_printf (2, "      %s\n", event_names[event]);
841                 else {
842                         debug_printf (1, "      Unexpected event: %s (expected %s)\n",
843                                       event_names[event],
844                                       event_name_from_abbrev (**events));
845                         errors++;
846                 }
847                 *events = *events + 1;
848         }
849 }
850
851 static void
852 do_one_connection_event_test (SoupSession *session, const char *uri,
853                               const char *events)
854 {
855         SoupMessage *msg;
856
857         msg = soup_message_new ("GET", uri);
858         g_signal_connect (msg, "network-event",
859                           G_CALLBACK (network_event),
860                           &events);
861         soup_session_send_message (session, msg);
862         if (msg->status_code != SOUP_STATUS_OK) {
863                 debug_printf (1, "      Unexpected response: %d %s\n",
864                               msg->status_code, msg->reason_phrase);
865                 errors++;
866         } else {
867                 while (*events) {
868                         debug_printf (1, "      Expected %s\n",
869                                       event_name_from_abbrev (*events));
870                         events++;
871                         errors++;
872                 }
873         }
874         g_object_unref (msg);
875         soup_session_abort (session);
876 }
877
878 static void
879 do_connection_event_test_for_session (SoupSession *session)
880 {
881         SoupURI *proxy_uri;
882
883         debug_printf (1, "    http\n");
884         do_one_connection_event_test (session, HTTP_SERVER, "rRcCx");
885
886         debug_printf (1, "    https\n");
887         do_one_connection_event_test (session, HTTPS_SERVER, "rRcCtTx");
888
889         proxy_uri = soup_uri_new (HTTP_PROXY);
890         g_object_set (G_OBJECT (session),
891                       SOUP_SESSION_PROXY_URI, proxy_uri,
892                       NULL);
893         soup_uri_free (proxy_uri);
894
895         debug_printf (1, "    http with proxy\n");
896         do_one_connection_event_test (session, HTTP_SERVER, "rRcCx");
897
898         debug_printf (1, "    https with proxy\n");
899         do_one_connection_event_test (session, HTTPS_SERVER, "rRcCpPtTx");
900 }
901
902 static void
903 do_connection_event_test (void)
904 {
905         SoupSession *session;
906
907         debug_printf (1, "\nConnection events\n");
908
909         debug_printf (1, "  Async session\n");
910         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
911         do_connection_event_test_for_session (session);
912         soup_test_session_abort_unref (session);
913
914         debug_printf (1, "  Sync session\n");
915         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
916         do_connection_event_test_for_session (session);
917         soup_test_session_abort_unref (session);
918 }
919
920 #endif
921
922 int
923 main (int argc, char **argv)
924 {
925         test_init (argc, argv, NULL);
926 #ifdef HAVE_APACHE
927         apache_init ();
928 #endif
929
930         server = soup_test_server_new (TRUE);
931         soup_server_add_handler (server, NULL, server_callback, "http", NULL);
932         base_uri = soup_uri_new ("http://127.0.0.1/");
933         soup_uri_set_port (base_uri, soup_server_get_port (server));
934
935         do_content_length_framing_test ();
936         do_persistent_connection_timeout_test ();
937         do_max_conns_test ();
938         do_non_persistent_connection_test ();
939         do_non_idempotent_connection_test ();
940 #ifdef HAVE_APACHE
941         do_connection_state_test ();
942         do_connection_event_test ();
943 #endif
944
945         soup_uri_free (base_uri);
946         soup_test_server_quit_unref (server);
947
948         test_cleanup ();
949         return errors != 0;
950 }