1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2007 Red Hat, Inc.
17 #include <libsoup/soup.h>
19 #include "test-utils.h"
25 auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg,
26 const char *username, const char *password, gpointer data)
28 return !strcmp (username, "user") && !strcmp (password, "password");
32 forget_close (SoupMessage *msg, gpointer user_data)
34 soup_message_headers_remove (msg->response_headers, "Connection");
38 close_socket (SoupMessage *msg, gpointer user_data)
40 SoupSocket *sock = user_data;
42 soup_socket_disconnect (sock);
46 server_callback (SoupServer *server, SoupMessage *msg,
47 const char *path, GHashTable *query,
48 SoupClientContext *context, gpointer data)
50 SoupURI *uri = soup_message_get_uri (msg);
52 soup_message_headers_append (msg->response_headers,
53 "X-Handled-By", "server_callback");
55 if (!strcmp (path, "*")) {
56 debug_printf (1, " default server_callback got request for '*'!\n");
58 soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
62 if (msg->method != SOUP_METHOD_GET) {
63 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
67 if (!strcmp (path, "/redirect")) {
68 soup_message_set_status (msg, SOUP_STATUS_FOUND);
69 soup_message_headers_append (msg->response_headers,
70 /* Kids: don't try this at home!
71 * RFC2616 says to use an
78 if (g_str_has_prefix (path, "/content-length/")) {
79 gboolean too_long = strcmp (path, "/content-length/long") == 0;
80 gboolean no_close = strcmp (path, "/content-length/noclose") == 0;
82 soup_message_set_status (msg, SOUP_STATUS_OK);
83 soup_message_set_response (msg, "text/plain",
84 SOUP_MEMORY_STATIC, "foobar", 6);
86 soup_message_headers_set_content_length (msg->response_headers, 9);
87 soup_message_headers_append (msg->response_headers,
88 "Connection", "close");
93 /* soup-message-io will wait for us to add
94 * another chunk after the first, to fill out
95 * the declared Content-Length. Instead, we
96 * forcibly close the socket at that point.
98 sock = soup_client_context_get_socket (context);
99 g_signal_connect (msg, "wrote-chunk",
100 G_CALLBACK (close_socket), sock);
101 } else if (no_close) {
102 /* Remove the 'Connection: close' after writing
103 * the headers, so that when we check it after
104 * writing the body, we'll think we aren't
105 * supposed to close it.
107 g_signal_connect (msg, "wrote-headers",
108 G_CALLBACK (forget_close), NULL);
113 soup_message_set_status (msg, SOUP_STATUS_OK);
114 if (!strcmp (uri->host, "foo")) {
115 soup_message_set_response (msg, "text/plain",
116 SOUP_MEMORY_STATIC, "foo-index", 9);
119 soup_message_set_response (msg, "text/plain",
120 SOUP_MEMORY_STATIC, "index", 5);
126 server_star_callback (SoupServer *server, SoupMessage *msg,
127 const char *path, GHashTable *query,
128 SoupClientContext *context, gpointer data)
130 soup_message_headers_append (msg->response_headers,
131 "X-Handled-By", "star_callback");
133 if (strcmp (path, "*") != 0) {
134 debug_printf (1, " server_star_callback got request for '%s'!\n", path);
136 soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
140 if (msg->method != SOUP_METHOD_OPTIONS) {
141 soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
145 soup_message_set_status (msg, SOUP_STATUS_OK);
148 /* Host header handling: client must be able to override the default
149 * value, server must be able to recognize different Host values.
155 SoupSession *session;
156 SoupMessage *one, *two;
158 debug_printf (1, "Host handling\n");
160 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
162 one = soup_message_new_from_uri ("GET", base_uri);
163 two = soup_message_new_from_uri ("GET", base_uri);
164 soup_message_headers_replace (two->request_headers, "Host", "foo");
166 soup_session_send_message (session, one);
167 soup_session_send_message (session, two);
169 soup_test_session_abort_unref (session);
171 if (!SOUP_STATUS_IS_SUCCESSFUL (one->status_code)) {
172 debug_printf (1, " Message 1 failed: %d %s\n",
173 one->status_code, one->reason_phrase);
175 } else if (strcmp (one->response_body->data, "index") != 0) {
176 debug_printf (1, " Unexpected response to message 1: '%s'\n",
177 one->response_body->data);
180 g_object_unref (one);
182 if (!SOUP_STATUS_IS_SUCCESSFUL (two->status_code)) {
183 debug_printf (1, " Message 2 failed: %d %s\n",
184 two->status_code, two->reason_phrase);
186 } else if (strcmp (two->response_body->data, "foo-index") != 0) {
187 debug_printf (1, " Unexpected response to message 2: '%s'\n",
188 two->response_body->data);
191 g_object_unref (two);
194 /* Dropping the application's ref on the session from a callback
195 * should not cause the session to be freed at an incorrect time.
196 * (This test will crash if it fails.) #533473
199 cu_one_completed (SoupSession *session, SoupMessage *msg, gpointer loop)
201 debug_printf (2, " Message 1 completed\n");
202 if (msg->status_code != SOUP_STATUS_CANT_CONNECT) {
203 debug_printf (1, " Unexpected status on Message 1: %d %s\n",
204 msg->status_code, msg->reason_phrase);
207 g_object_unref (session);
211 cu_idle_quit (gpointer loop)
213 g_main_loop_quit (loop);
218 cu_two_completed (SoupSession *session, SoupMessage *msg, gpointer loop)
220 debug_printf (2, " Message 2 completed\n");
221 if (msg->status_code != SOUP_STATUS_CANT_CONNECT) {
222 debug_printf (1, " Unexpected status on Message 2: %d %s\n",
223 msg->status_code, msg->reason_phrase);
226 g_idle_add (cu_idle_quit, loop);
230 do_callback_unref_test (void)
232 SoupServer *bad_server;
234 SoupSession *session;
235 SoupMessage *one, *two;
239 debug_printf (1, "\nCallback unref handling\n");
241 /* Get a guaranteed-bad URI */
242 addr = soup_address_new ("127.0.0.1", SOUP_ADDRESS_ANY_PORT);
243 soup_address_resolve_sync (addr, NULL);
244 bad_server = soup_server_new (SOUP_SERVER_INTERFACE, addr,
246 g_object_unref (addr);
248 bad_uri = g_strdup_printf ("http://127.0.0.1:%u/",
249 soup_server_get_port (bad_server));
250 g_object_unref (bad_server);
252 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
253 g_object_add_weak_pointer (G_OBJECT (session), (gpointer *)&session);
255 loop = g_main_loop_new (NULL, TRUE);
257 one = soup_message_new ("GET", bad_uri);
258 g_object_add_weak_pointer (G_OBJECT (one), (gpointer *)&one);
259 two = soup_message_new ("GET", bad_uri);
260 g_object_add_weak_pointer (G_OBJECT (two), (gpointer *)&two);
263 soup_session_queue_message (session, one, cu_one_completed, loop);
264 soup_session_queue_message (session, two, cu_two_completed, loop);
266 g_main_loop_run (loop);
267 g_main_loop_unref (loop);
270 g_object_remove_weak_pointer (G_OBJECT (session), (gpointer *)&session);
271 debug_printf (1, " Session not destroyed?\n");
273 g_object_unref (session);
276 g_object_remove_weak_pointer (G_OBJECT (one), (gpointer *)&one);
277 debug_printf (1, " Message 1 not destroyed?\n");
279 g_object_unref (one);
282 g_object_remove_weak_pointer (G_OBJECT (two), (gpointer *)&two);
283 debug_printf (1, " Message 2 not destroyed?\n");
285 g_object_unref (two);
288 /* Otherwise, if we haven't crashed, we're ok. */
291 /* SoupSession should clean up all signal handlers on a message after
292 * it is finished, allowing the message to be reused if desired.
296 ensure_no_signal_handlers (SoupMessage *msg, guint *signal_ids, guint n_signal_ids)
300 for (i = 0; i < n_signal_ids; i++) {
301 if (g_signal_handler_find (msg, G_SIGNAL_MATCH_ID, signal_ids[i],
302 0, NULL, NULL, NULL)) {
303 debug_printf (1, " Message has handler for '%s'\n",
304 g_signal_name (signal_ids[i]));
311 reuse_test_authenticate (SoupSession *session, SoupMessage *msg,
312 SoupAuth *auth, gboolean retrying)
314 /* Get it wrong the first time, then succeed */
316 soup_auth_authenticate (auth, "user", "wrong password");
318 soup_auth_authenticate (auth, "user", "password");
322 do_msg_reuse_test (void)
324 SoupSession *session;
327 guint *signal_ids, n_signal_ids;
329 debug_printf (1, "\nSoupMessage reuse\n");
331 signal_ids = g_signal_list_ids (SOUP_TYPE_MESSAGE, &n_signal_ids);
333 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
334 g_signal_connect (session, "authenticate",
335 G_CALLBACK (reuse_test_authenticate), NULL);
337 debug_printf (1, " First message\n");
338 msg = soup_message_new_from_uri ("GET", base_uri);
339 soup_session_send_message (session, msg);
340 ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
342 debug_printf (1, " Redirect message\n");
343 uri = soup_uri_new_with_base (base_uri, "/redirect");
344 soup_message_set_uri (msg, uri);
346 soup_session_send_message (session, msg);
347 if (!soup_uri_equal (soup_message_get_uri (msg), base_uri)) {
348 debug_printf (1, " Message did not get redirected!\n");
351 ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
353 debug_printf (1, " Auth message\n");
354 uri = soup_uri_new_with_base (base_uri, "/auth");
355 soup_message_set_uri (msg, uri);
357 soup_session_send_message (session, msg);
358 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
359 debug_printf (1, " Message did not get authenticated!\n");
362 ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
364 /* One last try to make sure the auth stuff got cleaned up */
365 debug_printf (1, " Last message\n");
366 soup_message_set_uri (msg, base_uri);
367 soup_session_send_message (session, msg);
368 ensure_no_signal_handlers (msg, signal_ids, n_signal_ids);
370 soup_test_session_abort_unref (session);
371 g_object_unref (msg);
375 /* Server handlers for "*" work but are separate from handlers for
376 * all other URIs. #590751
381 SoupSession *session;
384 const char *handled_by;
386 debug_printf (1, "\nOPTIONS *\n");
388 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
389 star_uri = soup_uri_copy (base_uri);
390 soup_uri_set_path (star_uri, "*");
392 debug_printf (1, " Testing with no handler\n");
393 msg = soup_message_new_from_uri ("OPTIONS", star_uri);
394 soup_session_send_message (session, msg);
396 if (msg->status_code != SOUP_STATUS_NOT_FOUND) {
397 debug_printf (1, " Unexpected response: %d %s\n",
398 msg->status_code, msg->reason_phrase);
401 handled_by = soup_message_headers_get_one (msg->response_headers,
404 /* Should have been rejected by SoupServer directly */
405 debug_printf (1, " Message reached handler '%s'\n",
409 g_object_unref (msg);
411 soup_server_add_handler (server, "*", server_star_callback, NULL, NULL);
413 debug_printf (1, " Testing with handler\n");
414 msg = soup_message_new_from_uri ("OPTIONS", star_uri);
415 soup_session_send_message (session, msg);
417 if (msg->status_code != SOUP_STATUS_OK) {
418 debug_printf (1, " Unexpected response: %d %s\n",
419 msg->status_code, msg->reason_phrase);
422 handled_by = soup_message_headers_get_one (msg->response_headers,
425 debug_printf (1, " Message did not reach handler!\n");
427 } else if (strcmp (handled_by, "star_callback") != 0) {
428 debug_printf (1, " Message reached incorrect handler '%s'\n",
432 g_object_unref (msg);
434 soup_test_session_abort_unref (session);
435 soup_uri_free (star_uri);
438 /* Handle unexpectedly-early aborts. #596074, #618641 */
440 ea_msg_completed_one (SoupSession *session, SoupMessage *msg, gpointer loop)
442 debug_printf (2, " Message 1 completed\n");
443 if (msg->status_code != SOUP_STATUS_CANCELLED) {
444 debug_printf (1, " Unexpected status on Message 1: %d %s\n",
445 msg->status_code, msg->reason_phrase);
448 g_main_loop_quit (loop);
452 ea_abort_session (gpointer session)
454 soup_session_abort (session);
459 ea_connection_state_changed (GObject *conn, GParamSpec *pspec, gpointer session)
461 SoupConnectionState state;
463 g_object_get (conn, "state", &state, NULL);
464 if (state == SOUP_CONNECTION_CONNECTING) {
465 g_idle_add_full (G_PRIORITY_HIGH,
468 g_signal_handlers_disconnect_by_func (conn, ea_connection_state_changed, session);
473 ea_connection_created (SoupSession *session, GObject *conn, gpointer user_data)
475 g_signal_connect (conn, "notify::state",
476 G_CALLBACK (ea_connection_state_changed), session);
477 g_signal_handlers_disconnect_by_func (session, ea_connection_created, user_data);
481 do_early_abort_test (void)
483 SoupSession *session;
485 GMainContext *context;
488 debug_printf (1, "\nAbort with pending connection\n");
490 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
491 msg = soup_message_new_from_uri ("GET", base_uri);
493 context = g_main_context_default ();
494 loop = g_main_loop_new (context, TRUE);
495 soup_session_queue_message (session, msg, ea_msg_completed_one, loop);
496 g_main_context_iteration (context, FALSE);
498 soup_session_abort (session);
499 while (g_main_context_pending (context))
500 g_main_context_iteration (context, FALSE);
501 g_main_loop_unref (loop);
502 soup_test_session_abort_unref (session);
504 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
505 msg = soup_message_new_from_uri ("GET", base_uri);
507 g_signal_connect (session, "connection-created",
508 G_CALLBACK (ea_connection_created), NULL);
509 soup_session_send_message (session, msg);
510 debug_printf (2, " Message 2 completed\n");
512 if (msg->status_code != SOUP_STATUS_CANCELLED) {
513 debug_printf (1, " Unexpected response: %d %s\n",
514 msg->status_code, msg->reason_phrase);
517 g_object_unref (msg);
519 while (g_main_context_pending (context))
520 g_main_context_iteration (context, FALSE);
522 soup_test_session_abort_unref (session);
526 do_content_length_framing_test (void)
528 SoupSession *session;
530 SoupURI *request_uri;
531 goffset declared_length;
533 debug_printf (1, "\nInvalid Content-Length framing tests\n");
535 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
537 debug_printf (1, " Content-Length larger than message body length\n");
538 request_uri = soup_uri_new_with_base (base_uri, "/content-length/long");
539 msg = soup_message_new_from_uri ("GET", request_uri);
540 soup_session_send_message (session, msg);
541 if (msg->status_code != SOUP_STATUS_OK) {
542 debug_printf (1, " Unexpected response: %d %s\n",
543 msg->status_code, msg->reason_phrase);
546 declared_length = soup_message_headers_get_content_length (msg->response_headers);
547 debug_printf (2, " Content-Length: %lu, body: %s\n",
548 (gulong)declared_length, msg->response_body->data);
549 if (msg->response_body->length >= declared_length) {
550 debug_printf (1, " Body length %lu >= declared length %lu\n",
551 (gulong)msg->response_body->length,
552 (gulong)declared_length);
556 soup_uri_free (request_uri);
557 g_object_unref (msg);
559 debug_printf (1, " Server claims 'Connection: close' but doesn't\n");
560 request_uri = soup_uri_new_with_base (base_uri, "/content-length/noclose");
561 msg = soup_message_new_from_uri ("GET", request_uri);
562 soup_session_send_message (session, msg);
563 if (msg->status_code != SOUP_STATUS_OK) {
564 debug_printf (1, " Unexpected response: %d %s\n",
565 msg->status_code, msg->reason_phrase);
568 declared_length = soup_message_headers_get_content_length (msg->response_headers);
569 debug_printf (2, " Content-Length: %lu, body: %s\n",
570 (gulong)declared_length, msg->response_body->data);
571 if (msg->response_body->length != declared_length) {
572 debug_printf (1, " Body length %lu != declared length %lu\n",
573 (gulong)msg->response_body->length,
574 (gulong)declared_length);
578 soup_uri_free (request_uri);
579 g_object_unref (msg);
581 soup_test_session_abort_unref (session);
585 do_one_accept_language_test (const char *language, const char *expected_header)
587 SoupSession *session;
591 debug_printf (1, " LANGUAGE=%s\n", language);
592 g_setenv ("LANGUAGE", language, TRUE);
593 session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC,
594 SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE,
596 msg = soup_message_new_from_uri ("GET", base_uri);
597 soup_session_send_message (session, msg);
598 soup_test_session_abort_unref (session);
600 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
601 debug_printf (1, " Message failed? %d %s\n",
602 msg->status_code, msg->reason_phrase);
605 val = soup_message_headers_get_list (msg->request_headers,
608 debug_printf (1, " No Accept-Language set!\n");
610 } else if (strcmp (val, expected_header) != 0) {
611 debug_printf (1, " Wrong Accept-Language: expected '%s', got '%s'\n",
612 expected_header, val);
616 g_object_unref (msg);
620 do_accept_language_test (void)
622 const char *orig_language;
624 debug_printf (1, "\nAutomatic Accept-Language processing\n");
626 orig_language = g_getenv ("LANGUAGE");
627 do_one_accept_language_test ("C", "en");
628 do_one_accept_language_test ("fr_FR", "fr-fr, fr;q=0.9");
629 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");
632 g_setenv ("LANGUAGE", orig_language, TRUE);
634 g_unsetenv ("LANGUAGE");
638 main (int argc, char **argv)
640 SoupAuthDomain *auth_domain;
642 test_init (argc, argv, NULL);
644 server = soup_test_server_new (TRUE);
645 soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
646 base_uri = soup_uri_new ("http://127.0.0.1/");
647 soup_uri_set_port (base_uri, soup_server_get_port (server));
649 auth_domain = soup_auth_domain_basic_new (
650 SOUP_AUTH_DOMAIN_REALM, "misc-test",
651 SOUP_AUTH_DOMAIN_ADD_PATH, "/auth",
652 SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, auth_callback,
654 soup_server_add_auth_domain (server, auth_domain);
655 g_object_unref (auth_domain);
658 do_callback_unref_test ();
659 do_msg_reuse_test ();
661 do_early_abort_test ();
662 do_content_length_framing_test ();
663 do_accept_language_test ();
665 soup_uri_free (base_uri);
666 soup_test_server_quit_unref (server);