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 #include <gio/gnetworking.h>
9
10 SoupServer *server;
11 SoupURI *base_uri;
12 GMutex server_mutex;
13
14 static void
forget_close(SoupMessage * msg,gpointer user_data)15 forget_close (SoupMessage *msg, gpointer user_data)
16 {
17 soup_message_headers_remove (msg->response_headers, "Connection");
18 }
19
20 static void
close_socket(SoupMessage * msg,gpointer user_data)21 close_socket (SoupMessage *msg, gpointer user_data)
22 {
23 SoupSocket *sock = user_data;
24 int sockfd;
25
26 /* Actually calling soup_socket_disconnect() here would cause
27 * us to leak memory, so just shutdown the socket instead.
28 */
29 sockfd = soup_socket_get_fd (sock);
30 #ifdef G_OS_WIN32
31 shutdown (sockfd, SD_SEND);
32 #else
33 shutdown (sockfd, SHUT_WR);
34 #endif
35
36 /* Then add the missing data to the message now, so SoupServer
37 * can clean up after itself properly.
38 */
39 soup_message_body_append (msg->response_body, SOUP_MEMORY_STATIC,
40 "foo", 3);
41 }
42
43 static void
timeout_socket(SoupSocket * sock,gpointer user_data)44 timeout_socket (SoupSocket *sock, gpointer user_data)
45 {
46 soup_socket_disconnect (sock);
47 }
48
49 static void
timeout_request_started(SoupServer * server,SoupMessage * msg,SoupClientContext * client,gpointer user_data)50 timeout_request_started (SoupServer *server, SoupMessage *msg,
51 SoupClientContext *client, gpointer user_data)
52 {
53 SoupSocket *sock;
54 GMainContext *context = g_main_context_get_thread_default ();
55 guint readable;
56
57 g_signal_handlers_disconnect_by_func (server, timeout_request_started, NULL);
58
59 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
60 sock = soup_client_context_get_socket (client);
61 G_GNUC_END_IGNORE_DEPRECATIONS;
62 readable = g_signal_connect (sock, "readable",
63 G_CALLBACK (timeout_socket), NULL);
64
65 g_mutex_unlock (&server_mutex);
66 while (soup_socket_is_connected (sock))
67 g_main_context_iteration (context, TRUE);
68 g_signal_handler_disconnect (sock, readable);
69 }
70
71 static void
setup_timeout_persistent(SoupServer * server,SoupSocket * sock)72 setup_timeout_persistent (SoupServer *server, SoupSocket *sock)
73 {
74 char buf[1];
75 gsize nread;
76
77 /* In order for the test to work correctly, we have to
78 * close the connection *after* the client side writes
79 * the request. To ensure that this happens reliably,
80 * regardless of thread scheduling, we:
81 *
82 * 1. Try to read off the socket now, knowing it will
83 * fail (since the client is waiting for us to
84 * return a response). This will cause it to
85 * emit "readable" later.
86 * 2. Wait for the server to finish this request and
87 * start reading the next one (and lock server_mutex
88 * to interlock with the client and ensure that it
89 * doesn't start writing its next request until
90 * that point).
91 * 3. Block until "readable" is emitted, meaning the
92 * client has written its request.
93 * 4. Close the socket.
94 */
95
96 soup_socket_read (sock, buf, 1, &nread, NULL, NULL);
97 g_mutex_lock (&server_mutex);
98 g_signal_connect (server, "request-started",
99 G_CALLBACK (timeout_request_started), NULL);
100 }
101
102 static void
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * context,gpointer data)103 server_callback (SoupServer *server, SoupMessage *msg,
104 const char *path, GHashTable *query,
105 SoupClientContext *context, gpointer data)
106 {
107 /* The way this gets used in the tests, we don't actually
108 * need to hold it through the whole function, so it's simpler
109 * to just release it right away.
110 */
111 g_mutex_lock (&server_mutex);
112 g_mutex_unlock (&server_mutex);
113
114 if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_POST) {
115 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
116 return;
117 }
118
119 if (g_str_has_prefix (path, "/content-length/")) {
120 gboolean too_long = strcmp (path, "/content-length/long") == 0;
121 gboolean no_close = strcmp (path, "/content-length/noclose") == 0;
122
123 soup_message_set_status (msg, SOUP_STATUS_OK);
124 soup_message_set_response (msg, "text/plain",
125 SOUP_MEMORY_STATIC, "foobar", 6);
126 if (too_long)
127 soup_message_headers_set_content_length (msg->response_headers, 9);
128 soup_message_headers_append (msg->response_headers,
129 "Connection", "close");
130
131 if (too_long) {
132 SoupSocket *sock;
133
134 /* soup-message-io will wait for us to add
135 * another chunk after the first, to fill out
136 * the declared Content-Length. Instead, we
137 * forcibly close the socket at that point.
138 */
139 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
140 sock = soup_client_context_get_socket (context);
141 G_GNUC_END_IGNORE_DEPRECATIONS;
142 g_signal_connect (msg, "wrote-chunk",
143 G_CALLBACK (close_socket), sock);
144 } else if (no_close) {
145 /* Remove the 'Connection: close' after writing
146 * the headers, so that when we check it after
147 * writing the body, we'll think we aren't
148 * supposed to close it.
149 */
150 g_signal_connect (msg, "wrote-headers",
151 G_CALLBACK (forget_close), NULL);
152 }
153 return;
154 }
155
156 if (!strcmp (path, "/timeout-persistent")) {
157 SoupSocket *sock;
158
159 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
160 sock = soup_client_context_get_socket (context);
161 G_GNUC_END_IGNORE_DEPRECATIONS;
162 setup_timeout_persistent (server, sock);
163 }
164
165 soup_message_set_status (msg, SOUP_STATUS_OK);
166 soup_message_set_response (msg, "text/plain",
167 SOUP_MEMORY_STATIC, "index", 5);
168 return;
169 }
170
171 static void
do_content_length_framing_test(void)172 do_content_length_framing_test (void)
173 {
174 SoupSession *session;
175 SoupMessage *msg;
176 SoupURI *request_uri;
177 goffset declared_length;
178
179 g_test_bug ("611481");
180
181 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
182
183 debug_printf (1, " Content-Length larger than message body length\n");
184 request_uri = soup_uri_new_with_base (base_uri, "/content-length/long");
185 msg = soup_message_new_from_uri ("GET", request_uri);
186 soup_session_send_message (session, msg);
187
188 soup_test_assert_message_status (msg, SOUP_STATUS_OK);
189
190 declared_length = soup_message_headers_get_content_length (msg->response_headers);
191 debug_printf (2, " Content-Length: %lu, body: %s\n",
192 (gulong)declared_length, msg->response_body->data);
193 g_assert_cmpint (msg->response_body->length, <, declared_length);
194
195 soup_uri_free (request_uri);
196 g_object_unref (msg);
197
198 debug_printf (1, " Server claims 'Connection: close' but doesn't\n");
199 request_uri = soup_uri_new_with_base (base_uri, "/content-length/noclose");
200 msg = soup_message_new_from_uri ("GET", request_uri);
201 soup_session_send_message (session, msg);
202
203 soup_test_assert_message_status (msg, SOUP_STATUS_OK);
204
205 declared_length = soup_message_headers_get_content_length (msg->response_headers);
206 g_assert_cmpint (msg->response_body->length, ==, declared_length);
207
208 soup_uri_free (request_uri);
209 g_object_unref (msg);
210
211 soup_test_session_abort_unref (session);
212 }
213
214 static void
request_started_socket_collector(SoupSession * session,SoupMessage * msg,SoupSocket * socket,gpointer user_data)215 request_started_socket_collector (SoupSession *session, SoupMessage *msg,
216 SoupSocket *socket, gpointer user_data)
217 {
218 SoupSocket **sockets = user_data;
219 int i;
220
221 debug_printf (2, " msg %p => socket %p\n", msg, socket);
222 for (i = 0; i < 4; i++) {
223 if (!sockets[i]) {
224 /* We ref the socket to make sure that even if
225 * it gets disconnected, it doesn't get freed,
226 * since our checks would get messed up if the
227 * slice allocator reused the same address for
228 * two consecutive sockets.
229 */
230 sockets[i] = g_object_ref (socket);
231 break;
232 }
233 }
234
235 soup_test_assert (i < 4, "socket queue overflowed");
236 }
237
238 static void
do_timeout_test_for_session(SoupSession * session)239 do_timeout_test_for_session (SoupSession *session)
240 {
241 SoupMessage *msg;
242 SoupSocket *sockets[4] = { NULL, NULL, NULL, NULL };
243 SoupURI *timeout_uri;
244 int i;
245
246 g_signal_connect (session, "request-started",
247 G_CALLBACK (request_started_socket_collector),
248 &sockets);
249
250 debug_printf (1, " First message\n");
251 timeout_uri = soup_uri_new_with_base (base_uri, "/timeout-persistent");
252 msg = soup_message_new_from_uri ("GET", timeout_uri);
253 soup_uri_free (timeout_uri);
254 soup_session_send_message (session, msg);
255 soup_test_assert_message_status (msg, SOUP_STATUS_OK);
256
257 if (sockets[1]) {
258 soup_test_assert (sockets[1] == NULL, "Message was retried");
259 sockets[1] = sockets[2] = sockets[3] = NULL;
260 }
261 g_object_unref (msg);
262
263 /* The server will grab server_mutex before returning the response,
264 * and release it when it's ready for us to send the second request.
265 */
266 g_mutex_lock (&server_mutex);
267 g_mutex_unlock (&server_mutex);
268
269 debug_printf (1, " Second message\n");
270 msg = soup_message_new_from_uri ("GET", base_uri);
271 soup_session_send_message (session, msg);
272 soup_test_assert_message_status (msg, SOUP_STATUS_OK);
273
274 soup_test_assert (sockets[1] == sockets[0],
275 "Message was not retried on existing connection");
276 soup_test_assert (sockets[2] != NULL,
277 "Message was not retried after disconnect");
278 soup_test_assert (sockets[2] != sockets[1],
279 "Message was retried on closed connection");
280 soup_test_assert (sockets[3] == NULL,
281 "Message was retried again");
282 g_object_unref (msg);
283
284 for (i = 0; sockets[i]; i++)
285 g_object_unref (sockets[i]);
286 }
287
288 static void
do_timeout_req_test_for_session(SoupSession * session)289 do_timeout_req_test_for_session (SoupSession *session)
290 {
291 SoupRequest *req;
292 SoupMessage *msg;
293 GInputStream *stream;
294 SoupSocket *sockets[4] = { NULL, NULL, NULL, NULL };
295 SoupURI *timeout_uri;
296 GError *error = NULL;
297 int i;
298
299 g_signal_connect (session, "request-started",
300 G_CALLBACK (request_started_socket_collector),
301 &sockets);
302
303 debug_printf (1, " First request\n");
304 timeout_uri = soup_uri_new_with_base (base_uri, "/timeout-persistent");
305 req = soup_session_request_uri (session, timeout_uri, NULL);
306 soup_uri_free (timeout_uri);
307
308 stream = soup_test_request_send (req, NULL, 0, &error);
309 if (error) {
310 g_assert_no_error (error);
311 g_clear_error (&error);
312 } else {
313 soup_test_request_read_all (req, stream, NULL, &error);
314 if (error) {
315 g_assert_no_error (error);
316 g_clear_error (&error);
317 }
318
319 soup_test_request_close_stream (req, stream, NULL, &error);
320 if (error) {
321 g_assert_no_error (error);
322 g_clear_error (&error);
323 }
324 g_object_unref (stream);
325 }
326
327 if (sockets[1]) {
328 soup_test_assert (sockets[1] == NULL, "Message was retried");
329 sockets[1] = sockets[2] = sockets[3] = NULL;
330 }
331 g_object_unref (req);
332
333 /* The server will grab server_mutex before returning the response,
334 * and release it when it's ready for us to send the second request.
335 */
336 g_mutex_lock (&server_mutex);
337 g_mutex_unlock (&server_mutex);
338
339 debug_printf (1, " Second request\n");
340 req = soup_session_request_uri (session, base_uri, NULL);
341
342 stream = soup_test_request_send (req, NULL, 0, &error);
343 if (error) {
344 g_assert_no_error (error);
345 g_clear_error (&error);
346 } else {
347 soup_test_request_close_stream (req, stream, NULL, &error);
348 if (error) {
349 g_assert_no_error (error);
350 g_clear_error (&error);
351 }
352 g_object_unref (stream);
353 }
354
355 msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req));
356 soup_test_assert_message_status (msg, SOUP_STATUS_OK);
357
358 soup_test_assert (sockets[1] == sockets[0],
359 "Message was not retried on existing connection");
360 soup_test_assert (sockets[2] != NULL,
361 "Message was not retried after disconnect");
362 soup_test_assert (sockets[2] != sockets[1],
363 "Message was retried on closed connection");
364 soup_test_assert (sockets[3] == NULL,
365 "Message was retried again");
366 g_object_unref (msg);
367 g_object_unref (req);
368
369 for (i = 0; sockets[i]; i++)
370 g_object_unref (sockets[i]);
371 }
372
373 static void
do_persistent_connection_timeout_test(void)374 do_persistent_connection_timeout_test (void)
375 {
376 SoupSession *session;
377
378 g_test_bug ("631525");
379
380 debug_printf (1, " Async session, message API\n");
381 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
382 do_timeout_test_for_session (session);
383 soup_test_session_abort_unref (session);
384
385 debug_printf (1, " Async session, request API\n");
386 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
387 SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
388 NULL);
389 do_timeout_req_test_for_session (session);
390 soup_test_session_abort_unref (session);
391
392 debug_printf (1, " Sync session, message API\n");
393 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
394 do_timeout_test_for_session (session);
395 soup_test_session_abort_unref (session);
396
397 debug_printf (1, " Sync session, request API\n");
398 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
399 do_timeout_req_test_for_session (session);
400 soup_test_session_abort_unref (session);
401 }
402
403 static void
cancel_cancellable_handler(SoupSession * session,SoupMessage * msg,SoupSocket * socket,gpointer user_data)404 cancel_cancellable_handler (SoupSession *session, SoupMessage *msg,
405 SoupSocket *socket, gpointer user_data)
406 {
407 g_cancellable_cancel (user_data);
408 }
409
410 static void
do_persistent_connection_timeout_test_with_cancellation(void)411 do_persistent_connection_timeout_test_with_cancellation (void)
412 {
413 SoupSession *session;
414 SoupMessage *msg;
415 SoupSocket *sockets[4] = { NULL, NULL, NULL, NULL };
416 SoupURI *timeout_uri;
417 GCancellable *cancellable;
418 GInputStream *response;
419 int i;
420 char buf[8192];
421
422 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
423
424 g_signal_connect (session, "request-started",
425 G_CALLBACK (request_started_socket_collector),
426 &sockets);
427
428 debug_printf (1, " First message\n");
429 timeout_uri = soup_uri_new_with_base (base_uri, "/timeout-persistent");
430 msg = soup_message_new_from_uri ("GET", timeout_uri);
431 cancellable = g_cancellable_new ();
432 soup_uri_free (timeout_uri);
433 response = soup_session_send (session, msg, cancellable, NULL);
434 soup_test_assert_message_status (msg, SOUP_STATUS_OK);
435
436 if (sockets[1]) {
437 soup_test_assert (sockets[1] == NULL, "Message was retried");
438 sockets[1] = sockets[2] = sockets[3] = NULL;
439 }
440 g_object_unref (msg);
441
442 soup_test_assert (response, "No response received");
443
444 while (g_input_stream_read (response, buf, sizeof (buf), NULL, NULL))
445 debug_printf (1, "Reading response\n");
446
447 soup_test_assert (!g_cancellable_is_cancelled (cancellable),
448 "User-supplied cancellable was cancelled");
449
450 g_object_unref (response);
451
452 /* The server will grab server_mutex before returning the response,
453 * and release it when it's ready for us to send the second request.
454 */
455 g_mutex_lock (&server_mutex);
456 g_mutex_unlock (&server_mutex);
457
458 debug_printf (1, " Second message\n");
459 msg = soup_message_new_from_uri ("GET", base_uri);
460
461 /* Cancel the cancellable in the signal handler, and then check that it
462 * was not reset below */
463 g_signal_connect (session, "request-started",
464 G_CALLBACK (cancel_cancellable_handler),
465 cancellable);
466
467 response = soup_session_send (session, msg, cancellable, NULL);
468
469 soup_test_assert (response == NULL, "Unexpected response");
470
471 soup_test_assert_message_status (msg, SOUP_STATUS_NONE);
472
473 soup_test_assert (sockets[1] == sockets[0],
474 "Message was not retried on existing connection");
475 soup_test_assert (sockets[2] != sockets[1],
476 "Message was retried on closed connection");
477 soup_test_assert (sockets[3] == NULL,
478 "Message was retried again");
479 g_object_unref (msg);
480
481 /* cancellable should not have been reset, it should still be in the
482 * cancelled state */
483 soup_test_assert (g_cancellable_is_cancelled (cancellable),
484 "User-supplied cancellable was reset");
485
486 for (i = 0; sockets[i]; i++)
487 g_object_unref (sockets[i]);
488
489 g_object_unref (cancellable);
490
491 soup_test_session_abort_unref (session);
492 }
493
494 static GMainLoop *max_conns_loop;
495 static int msgs_done;
496 static guint quit_loop_timeout;
497 #define MAX_CONNS 2
498 #define TEST_CONNS (MAX_CONNS * 2) + 1
499
500 static gboolean
idle_start_server(gpointer data)501 idle_start_server (gpointer data)
502 {
503 g_mutex_unlock (&server_mutex);
504 return FALSE;
505 }
506
507 static gboolean
quit_loop(gpointer data)508 quit_loop (gpointer data)
509 {
510 quit_loop_timeout = 0;
511 g_main_loop_quit (max_conns_loop);
512 return FALSE;
513 }
514
515 static void
max_conns_request_started(SoupSession * session,SoupMessage * msg,SoupSocket * socket,gpointer user_data)516 max_conns_request_started (SoupSession *session, SoupMessage *msg,
517 SoupSocket *socket, gpointer user_data)
518 {
519 if (++msgs_done >= MAX_CONNS) {
520 if (quit_loop_timeout)
521 g_source_remove (quit_loop_timeout);
522 quit_loop_timeout = g_timeout_add (100, quit_loop, NULL);
523 }
524 }
525
526 static void
max_conns_message_complete(SoupSession * session,SoupMessage * msg,gpointer user_data)527 max_conns_message_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
528 {
529 if (++msgs_done == TEST_CONNS)
530 g_main_loop_quit (max_conns_loop);
531 }
532
533 static void
do_max_conns_test_for_session(SoupSession * session)534 do_max_conns_test_for_session (SoupSession *session)
535 {
536 SoupMessage *msgs[TEST_CONNS + 1];
537 SoupMessageFlags flags;
538 int i;
539
540 max_conns_loop = g_main_loop_new (NULL, TRUE);
541
542 g_mutex_lock (&server_mutex);
543
544 g_signal_connect (session, "request-started",
545 G_CALLBACK (max_conns_request_started), NULL);
546 msgs_done = 0;
547 for (i = 0; i < TEST_CONNS - 1; i++) {
548 msgs[i] = soup_message_new_from_uri ("GET", base_uri);
549 g_object_ref (msgs[i]);
550 soup_session_queue_message (session, msgs[i],
551 max_conns_message_complete, NULL);
552 }
553
554 g_main_loop_run (max_conns_loop);
555 g_assert_cmpint (msgs_done, ==, MAX_CONNS);
556
557 if (quit_loop_timeout)
558 g_source_remove (quit_loop_timeout);
559 quit_loop_timeout = g_timeout_add (1000, quit_loop, NULL);
560
561 /* Message with SOUP_MESSAGE_IGNORE_CONNECTION_LIMITS should start */
562 msgs[i] = soup_message_new_from_uri ("GET", base_uri);
563 flags = soup_message_get_flags (msgs[i]);
564 soup_message_set_flags (msgs[i], flags | SOUP_MESSAGE_IGNORE_CONNECTION_LIMITS);
565 g_object_ref (msgs[i]);
566 soup_session_queue_message (session, msgs[i],
567 max_conns_message_complete, NULL);
568
569 g_main_loop_run (max_conns_loop);
570 g_assert_cmpint (msgs_done, ==, MAX_CONNS + 1);
571 g_signal_handlers_disconnect_by_func (session, max_conns_request_started, NULL);
572
573 msgs_done = 0;
574 g_idle_add (idle_start_server, NULL);
575 if (quit_loop_timeout)
576 g_source_remove (quit_loop_timeout);
577 quit_loop_timeout = g_timeout_add (1000, quit_loop, NULL);
578 g_main_loop_run (max_conns_loop);
579
580 for (i = 0; i < TEST_CONNS; i++)
581 soup_test_assert_message_status (msgs[i], SOUP_STATUS_OK);
582
583 if (msgs_done != TEST_CONNS) {
584 /* Clean up so we don't get a spurious "Leaked
585 * session" error.
586 */
587 for (i = 0; i < TEST_CONNS; i++)
588 soup_session_cancel_message (session, msgs[i], SOUP_STATUS_CANCELLED);
589 g_main_loop_run (max_conns_loop);
590 }
591
592 g_main_loop_unref (max_conns_loop);
593 if (quit_loop_timeout) {
594 g_source_remove (quit_loop_timeout);
595 quit_loop_timeout = 0;
596 }
597
598 for (i = 0; i < TEST_CONNS; i++)
599 g_object_unref (msgs[i]);
600 }
601
602 static void
do_max_conns_test(void)603 do_max_conns_test (void)
604 {
605 SoupSession *session;
606
607 g_test_bug ("634422");
608
609 debug_printf (1, " Async session\n");
610 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
611 SOUP_SESSION_MAX_CONNS, MAX_CONNS,
612 NULL);
613 do_max_conns_test_for_session (session);
614 soup_test_session_abort_unref (session);
615
616 debug_printf (1, " Sync session\n");
617 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC,
618 SOUP_SESSION_MAX_CONNS, MAX_CONNS,
619 NULL);
620 do_max_conns_test_for_session (session);
621 soup_test_session_abort_unref (session);
622 }
623
624 static void
np_request_started(SoupSession * session,SoupMessage * msg,SoupSocket * socket,gpointer user_data)625 np_request_started (SoupSession *session, SoupMessage *msg,
626 SoupSocket *socket, gpointer user_data)
627 {
628 SoupSocket **save_socket = user_data;
629
630 *save_socket = g_object_ref (socket);
631 }
632
633 static void
np_request_unqueued(SoupSession * session,SoupMessage * msg,gpointer user_data)634 np_request_unqueued (SoupSession *session, SoupMessage *msg,
635 gpointer user_data)
636 {
637 SoupSocket *socket = *(SoupSocket **)user_data;
638
639 g_assert_false (soup_socket_is_connected (socket));
640 }
641
642 static void
np_request_finished(SoupSession * session,SoupMessage * msg,gpointer user_data)643 np_request_finished (SoupSession *session, SoupMessage *msg,
644 gpointer user_data)
645 {
646 GMainLoop *loop = user_data;
647
648 g_main_loop_quit (loop);
649 }
650
651 static void
do_non_persistent_test_for_session(SoupSession * session)652 do_non_persistent_test_for_session (SoupSession *session)
653 {
654 SoupMessage *msg;
655 SoupSocket *socket = NULL;
656 GMainLoop *loop;
657
658 loop = g_main_loop_new (NULL, FALSE);
659
660 g_signal_connect (session, "request-started",
661 G_CALLBACK (np_request_started),
662 &socket);
663 g_signal_connect (session, "request-unqueued",
664 G_CALLBACK (np_request_unqueued),
665 &socket);
666
667 msg = soup_message_new_from_uri ("GET", base_uri);
668 soup_message_headers_append (msg->request_headers, "Connection", "close");
669 g_object_ref (msg);
670 soup_session_queue_message (session, msg,
671 np_request_finished, loop);
672 g_main_loop_run (loop);
673 g_main_loop_unref (loop);
674
675 soup_test_assert_message_status (msg, SOUP_STATUS_OK);
676
677 g_object_unref (msg);
678 g_object_unref (socket);
679 }
680
681 static void
do_non_persistent_connection_test(void)682 do_non_persistent_connection_test (void)
683 {
684 SoupSession *session;
685
686 g_test_bug ("578990");
687
688 debug_printf (1, " Async session\n");
689 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
690 do_non_persistent_test_for_session (session);
691 soup_test_session_abort_unref (session);
692
693 debug_printf (1, " Sync session\n");
694 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
695 do_non_persistent_test_for_session (session);
696 soup_test_session_abort_unref (session);
697 }
698
699 static void
do_non_idempotent_test_for_session(SoupSession * session)700 do_non_idempotent_test_for_session (SoupSession *session)
701 {
702 SoupMessage *msg;
703 SoupSocket *sockets[4] = { NULL, NULL, NULL, NULL };
704 int i;
705
706 g_signal_connect (session, "request-started",
707 G_CALLBACK (request_started_socket_collector),
708 &sockets);
709
710 debug_printf (2, " GET\n");
711 msg = soup_message_new_from_uri ("GET", base_uri);
712 soup_session_send_message (session, msg);
713 soup_test_assert_message_status (msg, SOUP_STATUS_OK);
714 if (sockets[1]) {
715 soup_test_assert (sockets[1] == NULL, "Message was retried");
716 sockets[1] = sockets[2] = sockets[3] = NULL;
717 }
718 g_object_unref (msg);
719
720 debug_printf (2, " POST\n");
721 msg = soup_message_new_from_uri ("POST", base_uri);
722 soup_session_send_message (session, msg);
723 soup_test_assert_message_status (msg, SOUP_STATUS_OK);
724 soup_test_assert (sockets[1] != sockets[0],
725 "Message was sent on existing connection");
726 soup_test_assert (sockets[2] == NULL,
727 "Too many connections used");
728
729 g_object_unref (msg);
730
731 for (i = 0; sockets[i]; i++)
732 g_object_unref (sockets[i]);
733 }
734
735 static void
do_non_idempotent_connection_test(void)736 do_non_idempotent_connection_test (void)
737 {
738 SoupSession *session;
739
740 debug_printf (1, " Async session\n");
741 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
742 do_non_idempotent_test_for_session (session);
743 soup_test_session_abort_unref (session);
744
745 debug_printf (1, " Sync session\n");
746 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
747 do_non_idempotent_test_for_session (session);
748 soup_test_session_abort_unref (session);
749 }
750
751 #define HTTP_SERVER "http://127.0.0.1:47524"
752 #define HTTPS_SERVER "https://127.0.0.1:47525"
753 #define HTTP_PROXY "http://127.0.0.1:47526"
754
755 static SoupConnectionState state_transitions[] = {
756 /* NEW -> */ SOUP_CONNECTION_CONNECTING,
757 /* CONNECTING -> */ SOUP_CONNECTION_IN_USE,
758 /* IDLE -> */ SOUP_CONNECTION_DISCONNECTED,
759 /* IN_USE -> */ SOUP_CONNECTION_IDLE,
760
761 /* REMOTE_DISCONNECTED */ -1,
762 /* DISCONNECTED */ -1,
763 };
764
765 static const char *state_names[] = {
766 "NEW", "CONNECTING", "IDLE", "IN_USE",
767 "REMOTE_DISCONNECTED", "DISCONNECTED"
768 };
769
770 static void
connection_state_changed(GObject * object,GParamSpec * param,gpointer user_data)771 connection_state_changed (GObject *object, GParamSpec *param,
772 gpointer user_data)
773 {
774 SoupConnectionState *state = user_data;
775 SoupConnectionState new_state;
776
777 g_object_get (object, "state", &new_state, NULL);
778 debug_printf (2, " %s -> %s\n",
779 state_names[*state], state_names[new_state]);
780 soup_test_assert (state_transitions[*state] == new_state,
781 "Unexpected transition: %s -> %s\n",
782 state_names[*state], state_names[new_state]);
783 *state = new_state;
784 }
785
786 static void
connection_created(SoupSession * session,GObject * conn,gpointer user_data)787 connection_created (SoupSession *session, GObject *conn,
788 gpointer user_data)
789 {
790 SoupConnectionState *state = user_data;
791
792 g_object_get (conn, "state", state, NULL);
793 g_assert_cmpint (*state, ==, SOUP_CONNECTION_NEW);
794
795 g_signal_connect (conn, "notify::state",
796 G_CALLBACK (connection_state_changed),
797 state);
798 }
799
800 static void
do_one_connection_state_test(SoupSession * session,const char * uri)801 do_one_connection_state_test (SoupSession *session, const char *uri)
802 {
803 SoupMessage *msg;
804
805 msg = soup_message_new ("GET", uri);
806 soup_session_send_message (session, msg);
807 soup_test_assert_message_status (msg, SOUP_STATUS_OK);
808 g_object_unref (msg);
809 soup_session_abort (session);
810 }
811
812 static void
do_connection_state_test_for_session(SoupSession * session)813 do_connection_state_test_for_session (SoupSession *session)
814 {
815 SoupConnectionState state;
816 SoupURI *proxy_uri;
817
818 g_signal_connect (session, "connection-created",
819 G_CALLBACK (connection_created),
820 &state);
821
822 debug_printf (1, " http\n");
823 do_one_connection_state_test (session, HTTP_SERVER);
824
825 if (tls_available) {
826 debug_printf (1, " https\n");
827 do_one_connection_state_test (session, HTTPS_SERVER);
828 } else
829 debug_printf (1, " https -- SKIPPING\n");
830
831 proxy_uri = soup_uri_new (HTTP_PROXY);
832 g_object_set (G_OBJECT (session),
833 SOUP_SESSION_PROXY_URI, proxy_uri,
834 NULL);
835 soup_uri_free (proxy_uri);
836
837 debug_printf (1, " http with proxy\n");
838 do_one_connection_state_test (session, HTTP_SERVER);
839
840 if (tls_available) {
841 debug_printf (1, " https with proxy\n");
842 do_one_connection_state_test (session, HTTPS_SERVER);
843 } else
844 debug_printf (1, " https with proxy -- SKIPPING\n");
845 }
846
847 static void
do_connection_state_test(void)848 do_connection_state_test (void)
849 {
850 SoupSession *session;
851
852 SOUP_TEST_SKIP_IF_NO_APACHE;
853
854 debug_printf (1, " Async session\n");
855 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
856 do_connection_state_test_for_session (session);
857 soup_test_session_abort_unref (session);
858
859 debug_printf (1, " Sync session\n");
860 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
861 do_connection_state_test_for_session (session);
862 soup_test_session_abort_unref (session);
863 }
864
865
866 static const char *event_names[] = {
867 "RESOLVING", "RESOLVED", "CONNECTING", "CONNECTED",
868 "PROXY_NEGOTIATING", "PROXY_NEGOTIATED",
869 "TLS_HANDSHAKING", "TLS_HANDSHAKED", "COMPLETE"
870 };
871
872 static const char event_abbrevs[] = {
873 'r', 'R', 'c', 'C', 'p', 'P', 't', 'T', 'x', '\0'
874 };
875
876 static const char *
event_name_from_abbrev(char abbrev)877 event_name_from_abbrev (char abbrev)
878 {
879 int evt;
880
881 for (evt = 0; event_abbrevs[evt]; evt++) {
882 if (event_abbrevs[evt] == abbrev)
883 return event_names[evt];
884 }
885 return "???";
886 }
887
888 static void
network_event(SoupMessage * msg,GSocketClientEvent event,GIOStream * connection,gpointer user_data)889 network_event (SoupMessage *msg, GSocketClientEvent event,
890 GIOStream *connection, gpointer user_data)
891 {
892 const char **events = user_data;
893
894 debug_printf (2, " %s\n", event_names[event]);
895 soup_test_assert (**events == event_abbrevs[event],
896 "Unexpected event: %s (expected %s)",
897 event_names[event],
898 event_name_from_abbrev (**events));
899
900 if (**events == event_abbrevs[event]) {
901 if (event == G_SOCKET_CLIENT_RESOLVING ||
902 event == G_SOCKET_CLIENT_RESOLVED) {
903 soup_test_assert (connection == NULL,
904 "Unexpectedly got connection (%s) with '%s' event",
905 G_OBJECT_TYPE_NAME (connection),
906 event_names[event]);
907 } else if (event < G_SOCKET_CLIENT_TLS_HANDSHAKING) {
908 soup_test_assert (G_IS_SOCKET_CONNECTION (connection),
909 "Unexpectedly got %s with '%s' event",
910 G_OBJECT_TYPE_NAME (connection),
911 event_names[event]);
912 } else if (event == G_SOCKET_CLIENT_TLS_HANDSHAKING ||
913 event == G_SOCKET_CLIENT_TLS_HANDSHAKED) {
914 soup_test_assert (G_IS_TLS_CLIENT_CONNECTION (connection),
915 "Unexpectedly got %s with '%s' event",
916 G_OBJECT_TYPE_NAME (connection),
917 event_names[event]);
918 } else if (event == G_SOCKET_CLIENT_COMPLETE) {
919 /* See if the previous expected event was TLS_HANDSHAKED */
920 if ((*events)[-1] == 'T') {
921 soup_test_assert (G_IS_TLS_CLIENT_CONNECTION (connection),
922 "Unexpectedly got %s with '%s' event",
923 G_OBJECT_TYPE_NAME (connection),
924 event_names[event]);
925 } else {
926 soup_test_assert (G_IS_SOCKET_CONNECTION (connection),
927 "Unexpectedly got %s with '%s' event",
928 G_OBJECT_TYPE_NAME (connection),
929 event_names[event]);
930 }
931 }
932 }
933
934 *events = *events + 1;
935 }
936
937 static void
do_one_connection_event_test(SoupSession * session,const char * uri,const char * events)938 do_one_connection_event_test (SoupSession *session, const char *uri,
939 const char *events)
940 {
941 SoupMessage *msg;
942
943 msg = soup_message_new ("GET", uri);
944 g_signal_connect (msg, "network-event",
945 G_CALLBACK (network_event),
946 &events);
947 soup_session_send_message (session, msg);
948 soup_test_assert_message_status (msg, SOUP_STATUS_OK);
949 while (*events) {
950 soup_test_assert (!*events,
951 "Expected %s",
952 event_name_from_abbrev (*events));
953 events++;
954 }
955
956 g_object_unref (msg);
957 soup_session_abort (session);
958 }
959
960 static void
do_connection_event_test_for_session(SoupSession * session)961 do_connection_event_test_for_session (SoupSession *session)
962 {
963 SoupURI *proxy_uri;
964
965 debug_printf (1, " http\n");
966 do_one_connection_event_test (session, HTTP_SERVER, "rRcCx");
967
968 if (tls_available) {
969 debug_printf (1, " https\n");
970 do_one_connection_event_test (session, HTTPS_SERVER, "rRcCtTx");
971 } else
972 debug_printf (1, " https -- SKIPPING\n");
973
974 proxy_uri = soup_uri_new (HTTP_PROXY);
975 g_object_set (G_OBJECT (session),
976 SOUP_SESSION_PROXY_URI, proxy_uri,
977 NULL);
978 soup_uri_free (proxy_uri);
979
980 debug_printf (1, " http with proxy\n");
981 do_one_connection_event_test (session, HTTP_SERVER, "rRcCx");
982
983 if (tls_available) {
984 debug_printf (1, " https with proxy\n");
985 do_one_connection_event_test (session, HTTPS_SERVER, "rRcCpPtTx");
986 } else
987 debug_printf (1, " https with proxy -- SKIPPING\n");
988 }
989
990 static void
do_connection_event_test(void)991 do_connection_event_test (void)
992 {
993 SoupSession *session;
994
995 SOUP_TEST_SKIP_IF_NO_APACHE;
996
997 debug_printf (1, " Async session\n");
998 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
999 do_connection_event_test_for_session (session);
1000 soup_test_session_abort_unref (session);
1001
1002 debug_printf (1, " Sync session\n");
1003 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
1004 do_connection_event_test_for_session (session);
1005 soup_test_session_abort_unref (session);
1006 }
1007
1008 typedef struct {
1009 GMainLoop *loop;
1010 GIOStream *stream;
1011 GError *error;
1012 const char *events;
1013 } ConnectTestData;
1014
1015 static void
connect_progress(SoupSession * session,GSocketClientEvent event,GIOStream * connection,ConnectTestData * data)1016 connect_progress (SoupSession *session, GSocketClientEvent event, GIOStream *connection, ConnectTestData *data)
1017 {
1018 soup_test_assert (*data->events == event_abbrevs[event],
1019 "Unexpected event: %s (expected %s)",
1020 event_names[event],
1021 event_name_from_abbrev (*data->events));
1022 data->events = data->events + 1;
1023 }
1024
1025 static void
connect_finished(SoupSession * session,GAsyncResult * result,ConnectTestData * data)1026 connect_finished (SoupSession *session, GAsyncResult *result, ConnectTestData *data)
1027 {
1028 data->stream = soup_session_connect_finish (session, result, &data->error);
1029 g_main_loop_quit (data->loop);
1030 }
1031
1032 static void
do_one_connection_connect_test(SoupSession * session,SoupURI * uri,const char * response,const char * events)1033 do_one_connection_connect_test (SoupSession *session, SoupURI *uri, const char *response, const char *events)
1034 {
1035 ConnectTestData data = { NULL, NULL, NULL, events };
1036 static const char *request = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
1037 gsize bytes = 0;
1038 char buffer[128];
1039
1040 data.loop = g_main_loop_new (NULL, FALSE);
1041 soup_session_connect_async (session, uri, NULL,
1042 (SoupSessionConnectProgressCallback)connect_progress,
1043 (GAsyncReadyCallback)connect_finished,
1044 &data);
1045 g_main_loop_run (data.loop);
1046
1047 g_assert (G_IS_IO_STREAM (data.stream));
1048 g_assert_no_error (data.error);
1049 g_assert (g_output_stream_write_all (g_io_stream_get_output_stream (data.stream),
1050 request, strlen (request), &bytes, NULL, NULL));
1051 g_assert (g_input_stream_read_all (g_io_stream_get_input_stream (data.stream),
1052 buffer, sizeof (buffer), &bytes, NULL, NULL));
1053 buffer[strlen (response)] = '\0';
1054 g_assert_cmpstr (buffer, ==, response);
1055
1056 while (*data.events) {
1057 soup_test_assert (!*data.events,
1058 "Expected %s",
1059 event_name_from_abbrev (*data.events));
1060 data.events++;
1061 }
1062
1063 g_object_unref (data.stream);
1064 g_main_loop_unref (data.loop);
1065 }
1066
1067 static void
do_one_connection_connect_fail_test(SoupSession * session,SoupURI * uri,GQuark domain,gint code,const char * events)1068 do_one_connection_connect_fail_test (SoupSession *session, SoupURI *uri, GQuark domain, gint code, const char *events)
1069 {
1070 ConnectTestData data = { NULL, NULL, NULL, events };
1071
1072 data.loop = g_main_loop_new (NULL, FALSE);
1073 soup_session_connect_async (session, uri, NULL,
1074 (SoupSessionConnectProgressCallback)connect_progress,
1075 (GAsyncReadyCallback)connect_finished,
1076 &data);
1077 g_main_loop_run (data.loop);
1078 g_main_loop_unref (data.loop);
1079
1080 g_assert (!data.stream);
1081 g_assert_error (data.error, domain, code);
1082 g_clear_error (&data.error);
1083
1084 while (*data.events) {
1085 soup_test_assert (!*data.events,
1086 "Expected %s",
1087 event_name_from_abbrev (*data.events));
1088 data.events++;
1089 }
1090 }
1091
1092 static void
do_connection_connect_test(void)1093 do_connection_connect_test (void)
1094 {
1095 SoupSession *session;
1096 SoupURI *http_uri;
1097 SoupURI *https_uri = NULL;
1098 SoupURI *ws_uri;
1099 SoupURI *wss_uri = NULL;
1100 SoupURI *file_uri;
1101 SoupURI *wrong_http_uri;
1102 SoupURI *proxy_uri;
1103
1104 SOUP_TEST_SKIP_IF_NO_APACHE;
1105
1106 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
1107 SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
1108 NULL);
1109
1110 debug_printf (1, " http\n");
1111 http_uri = soup_uri_new (HTTP_SERVER);
1112 do_one_connection_connect_test (session, http_uri,
1113 "HTTP/1.1 200 OK", "rRcCx");
1114
1115 if (tls_available) {
1116 debug_printf (1, " https\n");
1117 https_uri = soup_uri_new (HTTPS_SERVER);
1118 do_one_connection_connect_test (session, https_uri,
1119 "HTTP/1.1 200 OK", "rRcCtTx");
1120 } else
1121 debug_printf (1, " https -- SKIPPING\n");
1122
1123 debug_printf (1, " ws\n");
1124 ws_uri = soup_uri_new (HTTP_SERVER);
1125 ws_uri->scheme = SOUP_URI_SCHEME_WS;
1126 do_one_connection_connect_test (session, ws_uri,
1127 "HTTP/1.1 200 OK", "rRcCx");
1128
1129 if (tls_available) {
1130 debug_printf (1, " wss\n");
1131 wss_uri = soup_uri_new (HTTPS_SERVER);
1132 do_one_connection_connect_test (session, wss_uri,
1133 "HTTP/1.1 200 OK", "rRcCtTx");
1134 } else
1135 debug_printf (1, " wss -- SKIPPING\n");
1136
1137 debug_printf (1, " file\n");
1138 file_uri = soup_uri_new ("file:///foo/bar");
1139 do_one_connection_connect_fail_test (session, file_uri,
1140 G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND,
1141 "r");
1142
1143 debug_printf (1, " wrong http (invalid port)\n");
1144 wrong_http_uri = soup_uri_new (HTTP_SERVER);
1145 wrong_http_uri->port = 1234;
1146 do_one_connection_connect_fail_test (session, wrong_http_uri,
1147 G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED,
1148 "rRcr"); /* FIXME: why r again? GLib bug? */
1149
1150 proxy_uri = soup_uri_new (HTTP_PROXY);
1151 g_object_set (G_OBJECT (session),
1152 SOUP_SESSION_PROXY_URI, proxy_uri,
1153 NULL);
1154
1155 debug_printf (1, " http with proxy\n");
1156 do_one_connection_connect_test (session, http_uri,
1157 "HTTP/1.1 403 Forbidden", "rRcCx");
1158
1159 if (tls_available) {
1160 debug_printf (1, " https with proxy\n");
1161 do_one_connection_connect_test (session, https_uri,
1162 "HTTP/1.1 200 OK", "rRcCpPtTx");
1163 } else
1164 debug_printf (1, " https with proxy -- SKIPPING\n");
1165
1166 debug_printf (1, " ws with proxy\n");
1167 do_one_connection_connect_test (session, ws_uri,
1168 "HTTP/1.1 403 Forbidden", "rRcCx");
1169
1170 if (tls_available) {
1171 debug_printf (1, " wss with proxy\n");
1172 do_one_connection_connect_test (session, wss_uri,
1173 "HTTP/1.1 200 OK", "rRcCpPtTx");
1174 } else
1175 debug_printf (1, " wss with proxy -- SKIPPING\n");
1176
1177 soup_uri_free (http_uri);
1178 if (https_uri)
1179 soup_uri_free (https_uri);
1180 soup_uri_free (ws_uri);
1181 if (wss_uri)
1182 soup_uri_free (wss_uri);
1183 soup_uri_free (file_uri);
1184 soup_uri_free (wrong_http_uri);
1185 soup_uri_free (proxy_uri);
1186
1187 soup_test_session_abort_unref (session);
1188 }
1189
1190 int
main(int argc,char ** argv)1191 main (int argc, char **argv)
1192 {
1193 int ret;
1194
1195 test_init (argc, argv, NULL);
1196 apache_init ();
1197
1198 server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
1199 soup_server_add_handler (server, NULL, server_callback, "http", NULL);
1200 base_uri = soup_test_server_get_uri (server, "http", NULL);
1201
1202 g_test_add_func ("/connection/content-length-framing", do_content_length_framing_test);
1203 g_test_add_func ("/connection/persistent-connection-timeout", do_persistent_connection_timeout_test);
1204 g_test_add_func ("/connection/persistent-connection-timeout-with-cancellable",
1205 do_persistent_connection_timeout_test_with_cancellation);
1206 g_test_add_func ("/connection/max-conns", do_max_conns_test);
1207 g_test_add_func ("/connection/non-persistent", do_non_persistent_connection_test);
1208 g_test_add_func ("/connection/non-idempotent", do_non_idempotent_connection_test);
1209 g_test_add_func ("/connection/state", do_connection_state_test);
1210 g_test_add_func ("/connection/event", do_connection_event_test);
1211 g_test_add_func ("/connection/connect", do_connection_connect_test);
1212
1213 ret = g_test_run ();
1214
1215 soup_uri_free (base_uri);
1216 soup_test_server_quit_unref (server);
1217
1218 test_cleanup ();
1219 return ret;
1220 }
1221