/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ /* * This file was originally part of Cockpit. * * Copyright (C) 2013 Red Hat, Inc. * * Cockpit is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * Cockpit is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Cockpit; If not, see . */ #include "test-utils.h" #include "soup-message-private.h" #include "soup-server-message-private.h" #include typedef struct { GSocket *listener; gushort port; SoupSession *session; SoupMessage *msg; SoupWebsocketConnection *client; GError *client_error; SoupServer *soup_server; SoupWebsocketConnection *server; gboolean no_server; GIOStream *raw_server; gboolean enable_extensions; gboolean disable_deflate_in_message; GList *initial_cookies; GMutex mutex; } Test; #define WAIT_UNTIL(cond) \ G_STMT_START \ while (!(cond)) g_main_context_iteration (NULL, TRUE); \ G_STMT_END /* LCOV_EXCL_START */ static void on_error_not_reached (SoupWebsocketConnection *ws, GError *error, gpointer user_data) { /* At this point we know this will fail, but is informative */ g_assert_no_error (error); } /* LCOV_EXCL_STOP */ static void on_error_copy (SoupWebsocketConnection *ws, GError *error, gpointer user_data) { GError **copy = user_data; g_assert (*copy == NULL); *copy = g_error_copy (error); } static void setup_listener (Test *test) { GSocketAddress *addr; GError *error = NULL; test->listener = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_TCP, &error); g_assert_no_error (error); addr = g_inet_socket_address_new_from_string ("127.0.0.1", 0); g_assert_no_error (error); g_socket_bind (test->listener, addr, TRUE, &error); g_assert_no_error (error); g_object_unref (addr); addr = g_socket_get_local_address (test->listener, &error); g_assert_no_error (error); test->port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr)); g_object_unref (addr); g_socket_listen (test->listener, &error); g_assert_no_error (error); } static void direct_connection_complete (GObject *object, GAsyncResult *result, gpointer user_data) { Test *test = user_data; GSocketConnection *conn; GUri *uri; GError *error = NULL; GList *extensions = NULL; conn = g_socket_client_connect_to_host_finish (G_SOCKET_CLIENT (object), result, &error); g_assert_no_error (error); uri = g_uri_parse ("http://127.0.0.1/", SOUP_HTTP_URI_FLAGS, NULL); if (test->enable_extensions) { SoupWebsocketExtension *extension; extension = g_object_new (SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE, NULL); g_assert_true (soup_websocket_extension_configure (extension, SOUP_WEBSOCKET_CONNECTION_CLIENT, NULL, NULL)); extensions = g_list_prepend (extensions, extension); } test->client = soup_websocket_connection_new (G_IO_STREAM (conn), uri, SOUP_WEBSOCKET_CONNECTION_CLIENT, NULL, NULL, extensions); g_uri_unref (uri); g_object_unref (conn); } static gboolean got_connection (GSocket *listener, GIOCondition cond, gpointer user_data) { Test *test = user_data; GSocket *sock; GSocketConnection *conn; GUri *uri; GList *extensions = NULL; GError *error = NULL; sock = g_socket_accept (listener, NULL, &error); g_assert_no_error (error); conn = g_socket_connection_factory_create_connection (sock); g_assert (conn != NULL); g_object_unref (sock); if (test->no_server) test->raw_server = G_IO_STREAM (conn); else { uri = g_uri_parse ("http://127.0.0.1/", SOUP_HTTP_URI_FLAGS, NULL); if (test->enable_extensions) { SoupWebsocketExtension *extension; extension = g_object_new (SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE, NULL); g_assert_true (soup_websocket_extension_configure (extension, SOUP_WEBSOCKET_CONNECTION_SERVER, NULL, NULL)); extensions = g_list_prepend (extensions, extension); } test->server = soup_websocket_connection_new (G_IO_STREAM (conn), uri, SOUP_WEBSOCKET_CONNECTION_SERVER, NULL, NULL, extensions); g_uri_unref (uri); g_object_unref (conn); } return FALSE; } static void setup_direct_connection (Test *test, gconstpointer data) { GSocketClient *client; GSource *listen_source; setup_listener (test); client = g_socket_client_new (); g_socket_client_connect_to_host_async (client, "127.0.0.1", test->port, NULL, direct_connection_complete, test); listen_source = g_socket_create_source (test->listener, G_IO_IN, NULL); g_source_set_callback (listen_source, (GSourceFunc) got_connection, test, NULL); g_source_attach (listen_source, NULL); while (test->client == NULL || (test->server == NULL && test->raw_server == NULL)) g_main_context_iteration (NULL, TRUE); g_source_destroy (listen_source); g_source_unref (listen_source); g_object_unref (client); } static void setup_direct_connection_with_extensions (Test *test, gconstpointer data) { test->enable_extensions = TRUE; setup_direct_connection (test, data); } static void setup_half_direct_connection (Test *test, gconstpointer data) { test->no_server = TRUE; setup_direct_connection (test, data); } static void setup_half_direct_connection_with_extensions (Test *test, gconstpointer data) { test->no_server = TRUE; setup_direct_connection_with_extensions (test, data); } static void teardown_direct_connection (Test *test, gconstpointer data) { g_clear_object (&test->listener); g_clear_object (&test->client); g_clear_object (&test->server); g_clear_object (&test->raw_server); } static void setup_soup_server (Test *test, const char *origin, const char **protocols, SoupServerWebsocketCallback callback, gpointer user_data) { GError *error = NULL; setup_listener (test); test->soup_server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD); if (!test->enable_extensions) soup_server_remove_websocket_extension (test->soup_server, SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE); soup_server_listen_socket (test->soup_server, test->listener, 0, &error); g_assert_no_error (error); soup_server_add_websocket_handler (test->soup_server, "/unix", origin, (char **) protocols, callback, user_data, NULL); } static void client_connect (Test *test, const char *origin, const char **protocols, GAsyncReadyCallback callback, gpointer user_data) { char *url; SoupCookieJar *jar; GList *l; test->session = soup_test_session_new (NULL); if (test->enable_extensions) soup_session_add_feature_by_type (test->session, SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER); jar = soup_cookie_jar_new (); soup_cookie_jar_set_accept_policy (jar, SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY); soup_session_add_feature (test->session, SOUP_SESSION_FEATURE (jar)); for (l = test->initial_cookies; l; l = g_list_next (l)) soup_cookie_jar_add_cookie (jar, (SoupCookie *)l->data); g_clear_pointer (&test->initial_cookies, g_list_free); g_object_unref (jar); url = g_strdup_printf ("ws://127.0.0.1:%u/unix", test->port); test->msg = soup_message_new ("GET", url); if (test->disable_deflate_in_message) soup_message_disable_feature (test->msg, SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE); g_free (url); soup_session_websocket_connect_async (test->session, test->msg, origin, (char **) protocols, G_PRIORITY_DEFAULT, NULL, callback, user_data); } static void got_server_connection (SoupServer *server, SoupServerMessage *msg, const char *path, SoupWebsocketConnection *connection, gpointer user_data) { Test *test = user_data; test->server = g_object_ref (connection); } static void got_client_connection (GObject *object, GAsyncResult *result, gpointer user_data) { SoupSession *session = SOUP_SESSION (object); Test *test = user_data; g_assert_true (soup_session_get_async_result_message (session, result) == test->msg); test->client = soup_session_websocket_connect_finish (session, result, &test->client_error); } static void setup_soup_connection (Test *test, gconstpointer data) { setup_soup_server (test, NULL, NULL, got_server_connection, test); client_connect (test, NULL, NULL, got_client_connection, test); WAIT_UNTIL (test->server != NULL); WAIT_UNTIL (test->client != NULL || test->client_error != NULL); g_assert_no_error (test->client_error); } static void setup_soup_connection_with_extensions (Test *test, gconstpointer data) { test->enable_extensions = TRUE; setup_soup_connection (test, data); } static void teardown_soup_connection (Test *test, gconstpointer data) { teardown_direct_connection (test, data); g_clear_object (&test->msg); g_clear_error (&test->client_error); g_clear_pointer (&test->session, soup_test_session_abort_unref); g_clear_pointer (&test->soup_server, soup_test_server_quit_unref); } static void on_text_message (SoupWebsocketConnection *ws, SoupWebsocketDataType type, GBytes *message, gpointer user_data) { GBytes **receive = user_data; g_assert_cmpint (type, ==, SOUP_WEBSOCKET_DATA_TEXT); g_assert (*receive == NULL); g_assert (message != NULL); *receive = g_bytes_ref (message); } static void on_binary_message (SoupWebsocketConnection *ws, SoupWebsocketDataType type, GBytes *message, gpointer user_data) { GBytes **receive = user_data; g_assert_cmpint (type, ==, SOUP_WEBSOCKET_DATA_BINARY); g_assert (*receive == NULL); g_assert (message != NULL); *receive = g_bytes_ref (message); } static void on_close_set_flag (SoupWebsocketConnection *ws, gpointer user_data) { gboolean *flag = user_data; g_assert (*flag == FALSE); *flag = TRUE; } static void test_handshake (Test *test, gconstpointer data) { g_assert_cmpint (soup_websocket_connection_get_state (test->client), ==, SOUP_WEBSOCKET_STATE_OPEN); if (test->enable_extensions) { GList *extensions = soup_websocket_connection_get_extensions (test->client); g_assert_nonnull (extensions); g_assert_cmpuint (g_list_length (extensions), ==, 1); g_assert (SOUP_IS_WEBSOCKET_EXTENSION_DEFLATE (extensions->data)); } else { g_assert_null (soup_websocket_connection_get_extensions (test->client)); } g_assert_cmpint (soup_websocket_connection_get_state (test->server), ==, SOUP_WEBSOCKET_STATE_OPEN); if (test->enable_extensions) { GList *extensions = soup_websocket_connection_get_extensions (test->server); g_assert_nonnull (extensions); g_assert_cmpuint (g_list_length (extensions), ==, 1); g_assert (SOUP_IS_WEBSOCKET_EXTENSION_DEFLATE (extensions->data)); } else { g_assert_null (soup_websocket_connection_get_extensions (test->server)); } } static void websocket_server_request_started (SoupServer *server, SoupServerMessage *msg, gpointer user_data) { soup_message_headers_append (soup_server_message_get_response_headers (msg), "Sec-WebSocket-Extensions", "x-foo"); } static void request_unqueued (SoupSession *session, SoupMessage *msg, gpointer data) { Test *test = data; if (test->msg == msg) g_clear_object (&test->msg); } static void test_handshake_unsupported_extension (Test *test, gconstpointer data) { char *url; setup_listener (test); test->soup_server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD); soup_server_listen_socket (test->soup_server, test->listener, 0, NULL); g_signal_connect (test->soup_server, "request-started", G_CALLBACK (websocket_server_request_started), NULL); soup_server_add_websocket_handler (test->soup_server, "/unix", NULL, NULL, got_server_connection, test, NULL); test->session = soup_test_session_new (NULL); g_signal_connect (test->session, "request-unqueued", G_CALLBACK (request_unqueued), test); url = g_strdup_printf ("ws://127.0.0.1:%u/unix", test->port); test->msg = soup_message_new ("GET", url); g_free (url); soup_session_websocket_connect_async (test->session, test->msg, NULL, NULL, G_PRIORITY_DEFAULT, NULL, got_client_connection, test); WAIT_UNTIL (test->server != NULL); WAIT_UNTIL (test->msg == NULL); g_assert_error (test->client_error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE); } #define TEST_STRING "this is a test" #define TEST_STRING_WITH_NULL "this is\0 a test" static void test_send_client_to_server (Test *test, gconstpointer data) { GBytes *sent; GBytes *received = NULL; const char *contents; gsize len; g_signal_connect (test->server, "message", G_CALLBACK (on_text_message), &received); soup_websocket_connection_send_text (test->client, TEST_STRING); WAIT_UNTIL (received != NULL); /* Received messages should be null terminated (outside of len) */ contents = g_bytes_get_data (received, &len); g_assert_cmpstr (contents, ==, TEST_STRING); g_assert_cmpint (len, ==, strlen (TEST_STRING)); g_clear_pointer (&received, g_bytes_unref); sent = g_bytes_new_static (TEST_STRING_WITH_NULL, sizeof (TEST_STRING_WITH_NULL)); soup_websocket_connection_send_message (test->client, SOUP_WEBSOCKET_DATA_TEXT, sent); WAIT_UNTIL (received != NULL); g_assert (g_bytes_equal (sent, received)); g_clear_pointer (&sent, g_bytes_unref); g_clear_pointer (&received, g_bytes_unref); } static void test_send_server_to_client (Test *test, gconstpointer data) { GBytes *sent; GBytes *received = NULL; const char *contents; gsize len; g_signal_connect (test->client, "message", G_CALLBACK (on_text_message), &received); soup_websocket_connection_send_text (test->server, TEST_STRING); WAIT_UNTIL (received != NULL); /* Received messages should be null terminated (outside of len) */ contents = g_bytes_get_data (received, &len); g_assert_cmpstr (contents, ==, TEST_STRING); g_assert_cmpint (len, ==, strlen (TEST_STRING)); g_clear_pointer (&received, g_bytes_unref); sent = g_bytes_new_static (TEST_STRING_WITH_NULL, sizeof (TEST_STRING_WITH_NULL)); soup_websocket_connection_send_message (test->server, SOUP_WEBSOCKET_DATA_TEXT, sent); WAIT_UNTIL (received != NULL); g_assert (g_bytes_equal (sent, received)); g_clear_pointer (&sent, g_bytes_unref); g_clear_pointer (&received, g_bytes_unref); } static void test_send_big_packets (Test *test, gconstpointer data) { GBytes *sent = NULL; GBytes *received = NULL; g_signal_connect (test->client, "message", G_CALLBACK (on_text_message), &received); sent = g_bytes_new_take (g_strnfill (400, '!'), 400); soup_websocket_connection_send_text (test->server, g_bytes_get_data (sent, NULL)); WAIT_UNTIL (received != NULL); g_assert (g_bytes_equal (sent, received)); g_bytes_unref (sent); g_bytes_unref (received); received = NULL; sent = g_bytes_new_take (g_strnfill (100 * 1000, '?'), 100 * 1000); soup_websocket_connection_send_text (test->server, g_bytes_get_data (sent, NULL)); WAIT_UNTIL (received != NULL); g_assert (g_bytes_equal (sent, received)); g_bytes_unref (sent); g_bytes_unref (received); received = NULL; soup_websocket_connection_set_max_incoming_payload_size (test->client, 1000 * 1000 + 1); g_assert (soup_websocket_connection_get_max_incoming_payload_size (test->client) == (1000 * 1000 + 1)); soup_websocket_connection_set_max_incoming_payload_size (test->server, 1000 * 1000 + 1); g_assert (soup_websocket_connection_get_max_incoming_payload_size (test->server) == (1000 * 1000 + 1)); sent = g_bytes_new_take (g_strnfill (1000 * 1000, '?'), 1000 * 1000); soup_websocket_connection_send_text (test->server, g_bytes_get_data (sent, NULL)); WAIT_UNTIL (received != NULL); g_assert (g_bytes_equal (sent, received)); g_bytes_unref (sent); g_bytes_unref (received); } static void test_send_empty_packets (Test *test, gconstpointer data) { GBytes *received = NULL; gulong id; id = g_signal_connect (test->client, "message", G_CALLBACK (on_text_message), &received); soup_websocket_connection_send_text (test->server, "\0"); WAIT_UNTIL (received != NULL); g_assert_nonnull (g_bytes_get_data (received, NULL)); g_assert_cmpuint (((char *) g_bytes_get_data (received, NULL))[0], ==, '\0'); g_assert_cmpuint (g_bytes_get_size (received), ==, 0); g_bytes_unref (received); received = NULL; g_signal_handler_disconnect (test->client, id); id = g_signal_connect (test->client, "message", G_CALLBACK (on_binary_message), &received); soup_websocket_connection_send_binary (test->server, NULL, 0); WAIT_UNTIL (received != NULL); /* We always include at least a null character */ g_assert_nonnull (g_bytes_get_data (received, NULL)); g_assert_cmpuint (((char *) g_bytes_get_data (received, NULL))[0], ==, '\0'); g_assert_cmpuint (g_bytes_get_size (received), ==, 0); g_bytes_unref (received); received = NULL; g_signal_handler_disconnect (test->client, id); } static void test_send_bad_data (Test *test, gconstpointer unused) { GError *error = NULL; GIOStream *io; gsize written; const char *frame; gboolean close_event = FALSE; g_signal_handlers_disconnect_by_func (test->server, on_error_not_reached, NULL); g_signal_connect (test->server, "error", G_CALLBACK (on_error_copy), &error); g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event); io = soup_websocket_connection_get_io_stream (test->client); /* Bad UTF-8 frame */ frame = "\x81\x84\x00\x00\x00\x00\xEE\xEE\xEE\xEE"; if (!g_output_stream_write_all (g_io_stream_get_output_stream (io), frame, 10, &written, NULL, NULL)) g_assert_not_reached (); g_assert_cmpuint (written, ==, 10); WAIT_UNTIL (error != NULL); g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_BAD_DATA); g_clear_error (&error); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); g_assert (close_event); g_assert_cmpuint (soup_websocket_connection_get_close_code (test->client), ==, SOUP_WEBSOCKET_CLOSE_BAD_DATA); } static const char *negotiate_client_protocols[] = { "bbb", "ccc", NULL }; static const char *negotiate_server_protocols[] = { "aaa", "bbb", "ccc", NULL }; static const char *negotiated_protocol = "bbb"; static void test_protocol_negotiate_direct (Test *test, gconstpointer unused) { SoupMessage *msg; SoupServerMessage *server_msg; SoupMessageHeaders *request_headers; SoupMessageHeaders *response_headers; SoupMessageHeadersIter iter; const char *name, *value; gboolean ok; const char *protocol; GError *error = NULL; msg = soup_message_new ("GET", "http://127.0.0.1"); soup_websocket_client_prepare_handshake (msg, NULL, (char **) negotiate_client_protocols, NULL); server_msg = g_object_new (SOUP_TYPE_SERVER_MESSAGE, NULL); soup_server_message_set_method (server_msg, soup_message_get_method (msg)); soup_server_message_set_uri (server_msg, soup_message_get_uri (msg)); request_headers = soup_server_message_get_request_headers (server_msg); soup_message_headers_iter_init (&iter, soup_message_get_request_headers (msg)); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (request_headers, name, value); ok = soup_websocket_server_check_handshake (server_msg, NULL, (char **) negotiate_server_protocols, NULL, &error); g_assert_no_error (error); g_assert_true (ok); ok = soup_websocket_server_process_handshake (server_msg, NULL, (char **) negotiate_server_protocols, NULL, NULL); g_assert_true (ok); soup_message_set_status (msg, soup_server_message_get_status (server_msg), NULL); response_headers = soup_server_message_get_response_headers (server_msg); soup_message_headers_iter_init (&iter, response_headers); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (soup_message_get_response_headers (msg), name, value); protocol = soup_message_headers_get_one (soup_message_get_response_headers (msg), "Sec-WebSocket-Protocol"); g_assert_cmpstr (protocol, ==, negotiated_protocol); ok = soup_websocket_client_verify_handshake (msg, NULL, NULL, &error); g_assert_no_error (error); g_assert_true (ok); g_object_unref (msg); g_object_unref (server_msg); } static void test_protocol_negotiate_soup (Test *test, gconstpointer unused) { setup_soup_server (test, NULL, negotiate_server_protocols, got_server_connection, test); client_connect (test, NULL, negotiate_client_protocols, got_client_connection, test); WAIT_UNTIL (test->server != NULL); WAIT_UNTIL (test->client != NULL || test->client_error != NULL); g_assert_no_error (test->client_error); g_assert_cmpstr (soup_websocket_connection_get_protocol (test->client), ==, negotiated_protocol); g_assert_cmpstr (soup_websocket_connection_get_protocol (test->server), ==, negotiated_protocol); } static const char *mismatch_client_protocols[] = { "ddd", NULL }; static const char *mismatch_server_protocols[] = { "aaa", "bbb", "ccc", NULL }; static void test_protocol_mismatch_direct (Test *test, gconstpointer unused) { SoupMessage *msg; SoupServerMessage *server_msg; SoupMessageHeaders *request_headers; SoupMessageHeaders *response_headers; SoupMessageHeadersIter iter; const char *name, *value; gboolean ok; const char *protocol; GError *error = NULL; msg = soup_message_new ("GET", "http://127.0.0.1"); soup_websocket_client_prepare_handshake (msg, NULL, (char **) mismatch_client_protocols, NULL); server_msg = g_object_new (SOUP_TYPE_SERVER_MESSAGE, NULL); soup_server_message_set_method (server_msg, soup_message_get_method (msg)); soup_server_message_set_uri (server_msg, soup_message_get_uri (msg)); request_headers = soup_server_message_get_request_headers (server_msg); soup_message_headers_iter_init (&iter, soup_message_get_request_headers (msg)); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (request_headers, name, value); ok = soup_websocket_server_check_handshake (server_msg, NULL, (char **) mismatch_server_protocols, NULL, &error); g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE); g_clear_error (&error); g_assert_false (ok); ok = soup_websocket_server_process_handshake (server_msg, NULL, (char **) mismatch_server_protocols, NULL, NULL); g_assert_false (ok); soup_message_set_status (msg, soup_server_message_get_status (server_msg), NULL); soup_test_assert_message_status (msg, SOUP_STATUS_BAD_REQUEST); response_headers = soup_server_message_get_response_headers (server_msg); soup_message_headers_iter_init (&iter, response_headers); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (soup_message_get_response_headers (msg), name, value); protocol = soup_message_headers_get_one (soup_message_get_response_headers (msg), "Sec-WebSocket-Protocol"); g_assert_cmpstr (protocol, ==, NULL); ok = soup_websocket_client_verify_handshake (msg, NULL, NULL, &error); g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE); g_clear_error (&error); g_assert_false (ok); g_object_unref (msg); g_object_unref (server_msg); } static void test_protocol_mismatch_soup (Test *test, gconstpointer unused) { setup_soup_server (test, NULL, mismatch_server_protocols, got_server_connection, test); client_connect (test, NULL, mismatch_client_protocols, got_client_connection, test); WAIT_UNTIL (test->client_error != NULL); g_assert_error (test->client_error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET); } static const char *all_protocols[] = { "aaa", "bbb", "ccc", NULL }; static void test_protocol_server_any_direct (Test *test, gconstpointer unused) { SoupMessage *msg; SoupServerMessage *server_msg; SoupMessageHeaders *request_headers; SoupMessageHeaders *response_headers; SoupMessageHeadersIter iter; const char *name, *value; gboolean ok; const char *protocol; GError *error = NULL; msg = soup_message_new ("GET", "http://127.0.0.1"); soup_websocket_client_prepare_handshake (msg, NULL, (char **) all_protocols, NULL); server_msg = g_object_new (SOUP_TYPE_SERVER_MESSAGE, NULL); soup_server_message_set_method (server_msg, soup_message_get_method (msg)); soup_server_message_set_uri (server_msg, soup_message_get_uri (msg)); request_headers = soup_server_message_get_request_headers (server_msg); soup_message_headers_iter_init (&iter, soup_message_get_request_headers (msg)); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (request_headers, name, value); ok = soup_websocket_server_check_handshake (server_msg, NULL, NULL, NULL, &error); g_assert_no_error (error); g_assert_true (ok); ok = soup_websocket_server_process_handshake (server_msg, NULL, NULL, NULL, NULL); g_assert_true (ok); soup_message_set_status (msg, soup_server_message_get_status (server_msg), NULL); response_headers = soup_server_message_get_response_headers (server_msg); soup_message_headers_iter_init (&iter, response_headers); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (soup_message_get_response_headers (msg), name, value); protocol = soup_message_headers_get_one (soup_message_get_response_headers (msg), "Sec-WebSocket-Protocol"); g_assert_cmpstr (protocol, ==, NULL); ok = soup_websocket_client_verify_handshake (msg, NULL, NULL, &error); g_assert_no_error (error); g_assert_true (ok); g_object_unref (msg); g_object_unref (server_msg); } static void test_protocol_server_any_soup (Test *test, gconstpointer unused) { setup_soup_server (test, NULL, NULL, got_server_connection, test); client_connect (test, NULL, all_protocols, got_client_connection, test); WAIT_UNTIL (test->server != NULL); WAIT_UNTIL (test->client != NULL || test->client_error != NULL); g_assert_no_error (test->client_error); g_assert_cmpstr (soup_websocket_connection_get_protocol (test->client), ==, NULL); g_assert_cmpstr (soup_websocket_connection_get_protocol (test->server), ==, NULL); g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_response_headers (test->msg), "Sec-WebSocket-Protocol"), ==, NULL); } static void test_protocol_client_any_direct (Test *test, gconstpointer unused) { SoupMessage *msg; SoupServerMessage *server_msg; SoupMessageHeaders *request_headers; SoupMessageHeaders *response_headers; SoupMessageHeadersIter iter; const char *name, *value; gboolean ok; const char *protocol; GError *error = NULL; msg = soup_message_new ("GET", "http://127.0.0.1"); soup_websocket_client_prepare_handshake (msg, NULL, NULL, NULL); server_msg = g_object_new (SOUP_TYPE_SERVER_MESSAGE, NULL); soup_server_message_set_method (server_msg, soup_message_get_method (msg)); soup_server_message_set_uri (server_msg, soup_message_get_uri (msg)); request_headers = soup_server_message_get_request_headers (server_msg); soup_message_headers_iter_init (&iter, soup_message_get_request_headers (msg)); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (request_headers, name, value); ok = soup_websocket_server_check_handshake (server_msg, NULL, (char **) all_protocols, NULL, &error); g_assert_no_error (error); g_assert_true (ok); ok = soup_websocket_server_process_handshake (server_msg, NULL, (char **) all_protocols, NULL, NULL); g_assert_true (ok); soup_message_set_status (msg, soup_server_message_get_status (server_msg), NULL); response_headers = soup_server_message_get_response_headers (server_msg); soup_message_headers_iter_init (&iter, response_headers); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (soup_message_get_response_headers (msg), name, value); protocol = soup_message_headers_get_one (soup_message_get_response_headers (msg), "Sec-WebSocket-Protocol"); g_assert_cmpstr (protocol, ==, NULL); ok = soup_websocket_client_verify_handshake (msg, NULL, NULL, &error); g_assert_no_error (error); g_assert_true (ok); g_object_unref (msg); g_object_unref (server_msg); } static void test_protocol_client_any_soup (Test *test, gconstpointer unused) { setup_soup_server (test, NULL, all_protocols, got_server_connection, test); client_connect (test, NULL, NULL, got_client_connection, test); WAIT_UNTIL (test->server != NULL); WAIT_UNTIL (test->client != NULL || test->client_error != NULL); g_assert_no_error (test->client_error); g_assert_cmpstr (soup_websocket_connection_get_protocol (test->client), ==, NULL); g_assert_cmpstr (soup_websocket_connection_get_protocol (test->server), ==, NULL); g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_response_headers (test->msg), "Sec-WebSocket-Protocol"), ==, NULL); } static const char *invalid_protocols[] = { "", NULL }; static void test_soup_websocket_client_prepare_handshake_ignores_invalid_protocols (Test *test, gconstpointer unused) { SoupMessage *msg; const char *protocol; msg = soup_message_new ("GET", "http://127.0.0.1"); soup_websocket_client_prepare_handshake (msg, NULL, (char **) invalid_protocols, NULL); protocol = soup_message_headers_get_one (soup_message_get_request_headers (msg), "Sec-WebSocket-Protocol"); g_assert_cmpstr (protocol, ==, NULL); } static void test_protocol_client_invalid_direct (Test *test, gconstpointer unused) { SoupMessage *msg; SoupServerMessage *server_msg; SoupMessageHeaders *request_headers; SoupMessageHeadersIter iter; const char *name, *value; gboolean ok; GError *error = NULL; msg = soup_message_new ("GET", "http://127.0.0.1"); soup_websocket_client_prepare_handshake (msg, NULL, NULL, NULL); soup_message_headers_append (soup_message_get_request_headers (msg), "Sec-WebSocket-Protocol", ""); server_msg = g_object_new (SOUP_TYPE_SERVER_MESSAGE, NULL); soup_server_message_set_method (server_msg, soup_message_get_method (msg)); soup_server_message_set_uri (server_msg, soup_message_get_uri (msg)); request_headers = soup_server_message_get_request_headers (server_msg); soup_message_headers_iter_init (&iter, soup_message_get_request_headers (msg)); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (request_headers, name, value); ok = soup_websocket_server_check_handshake (server_msg, NULL, (char **) all_protocols, NULL, &error); g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE); g_assert_false (ok); ok = soup_websocket_server_process_handshake (server_msg, NULL, (char **) all_protocols, NULL, NULL); g_assert_false (ok); g_object_unref (msg); g_object_unref (server_msg); } typedef enum { CLOSE_TEST_FLAG_SERVER = 1 << 0, CLOSE_TEST_FLAG_CLIENT = 1 << 1 } CloseTestFlags; static const struct { gushort code; const char *reason; gushort expected_sender_code; const char *expected_sender_reason; gushort expected_receiver_code; const char *expected_receiver_reason; CloseTestFlags flags; } close_clean_tests[] = { { SOUP_WEBSOCKET_CLOSE_NORMAL, "NORMAL", SOUP_WEBSOCKET_CLOSE_NORMAL, "NORMAL", SOUP_WEBSOCKET_CLOSE_NORMAL, "NORMAL", CLOSE_TEST_FLAG_SERVER | CLOSE_TEST_FLAG_CLIENT }, { SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "GOING_AWAY", SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "GOING_AWAY", SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "GOING_AWAY", CLOSE_TEST_FLAG_SERVER | CLOSE_TEST_FLAG_CLIENT }, { SOUP_WEBSOCKET_CLOSE_NORMAL, NULL, SOUP_WEBSOCKET_CLOSE_NORMAL, NULL, SOUP_WEBSOCKET_CLOSE_NORMAL, NULL, CLOSE_TEST_FLAG_SERVER | CLOSE_TEST_FLAG_CLIENT }, { SOUP_WEBSOCKET_CLOSE_NO_STATUS, NULL, SOUP_WEBSOCKET_CLOSE_NORMAL, NULL, SOUP_WEBSOCKET_CLOSE_NO_STATUS, NULL, CLOSE_TEST_FLAG_SERVER | CLOSE_TEST_FLAG_CLIENT }, { 2999, NULL, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR, NULL, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR, NULL, CLOSE_TEST_FLAG_CLIENT }, { 2999, NULL, 0, NULL, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR, NULL, CLOSE_TEST_FLAG_SERVER }, { 5000, NULL, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR, NULL, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR, NULL, CLOSE_TEST_FLAG_CLIENT }, { 5000, NULL, 0, NULL, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR, NULL, CLOSE_TEST_FLAG_SERVER }, }; static void do_close_clean_client (Test *test, gushort code, const char *reason, gushort expected_sender_code, const char *expected_sender_reason, gushort expected_receiver_code, const char *expected_receiver_reason) { gboolean close_event_client = FALSE; gboolean close_event_server = FALSE; g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event_client); g_signal_connect (test->server, "closed", G_CALLBACK (on_close_set_flag), &close_event_server); soup_websocket_connection_close (test->client, code, reason); g_assert_cmpint (soup_websocket_connection_get_state (test->client), ==, SOUP_WEBSOCKET_STATE_CLOSING); WAIT_UNTIL (soup_websocket_connection_get_state (test->server) == SOUP_WEBSOCKET_STATE_CLOSED); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); g_assert (close_event_client); g_assert (close_event_server); g_assert_cmpint (soup_websocket_connection_get_close_code (test->client), ==, expected_sender_code); g_assert_cmpstr (soup_websocket_connection_get_close_data (test->client), ==, expected_sender_reason); g_assert_cmpint (soup_websocket_connection_get_close_code (test->server), ==, expected_receiver_code); g_assert_cmpstr (soup_websocket_connection_get_close_data (test->server), ==, expected_receiver_reason); } static void test_close_clean_client_soup (Test *test, gconstpointer data) { guint i; for (i = 0; i < G_N_ELEMENTS (close_clean_tests); i++) { if (!(close_clean_tests[i].flags & CLOSE_TEST_FLAG_CLIENT)) continue; setup_soup_connection (test, data); do_close_clean_client (test, close_clean_tests[i].code, close_clean_tests[i].reason, close_clean_tests[i].expected_sender_code, close_clean_tests[i].expected_sender_reason, close_clean_tests[i].expected_receiver_code, close_clean_tests[i].expected_receiver_reason); teardown_soup_connection (test, data); } } static void test_close_clean_client_direct (Test *test, gconstpointer data) { guint i; for (i = 0; i < G_N_ELEMENTS (close_clean_tests); i++) { if (!(close_clean_tests[i].flags & CLOSE_TEST_FLAG_CLIENT)) continue; setup_direct_connection (test, data); do_close_clean_client (test, close_clean_tests[i].code, close_clean_tests[i].reason, close_clean_tests[i].expected_sender_code, close_clean_tests[i].expected_sender_reason, close_clean_tests[i].expected_receiver_code, close_clean_tests[i].expected_receiver_reason); teardown_direct_connection (test, data); } } static void do_close_clean_server (Test *test, gushort code, const char *reason, gushort expected_sender_code, const char *expected_sender_reason, gushort expected_receiver_code, const char *expected_receiver_reason) { gboolean close_event_client = FALSE; gboolean close_event_server = FALSE; g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event_client); g_signal_connect (test->server, "closed", G_CALLBACK (on_close_set_flag), &close_event_server); soup_websocket_connection_close (test->server, code, reason); g_assert_cmpint (soup_websocket_connection_get_state (test->server), ==, SOUP_WEBSOCKET_STATE_CLOSING); WAIT_UNTIL (soup_websocket_connection_get_state (test->server) == SOUP_WEBSOCKET_STATE_CLOSED); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); g_assert (close_event_client); g_assert (close_event_server); g_assert_cmpint (soup_websocket_connection_get_close_code (test->server), ==, expected_sender_code); g_assert_cmpstr (soup_websocket_connection_get_close_data (test->server), ==, expected_sender_reason); g_assert_cmpint (soup_websocket_connection_get_close_code (test->client), ==, expected_receiver_code); g_assert_cmpstr (soup_websocket_connection_get_close_data (test->client), ==, expected_receiver_reason); } static void test_close_clean_server_soup (Test *test, gconstpointer data) { guint i; for (i = 0; i < G_N_ELEMENTS (close_clean_tests); i++) { if (!(close_clean_tests[i].flags & CLOSE_TEST_FLAG_SERVER)) continue; setup_direct_connection (test, data); do_close_clean_server (test, close_clean_tests[i].code, close_clean_tests[i].reason, close_clean_tests[i].expected_sender_code, close_clean_tests[i].expected_sender_reason, close_clean_tests[i].expected_receiver_code, close_clean_tests[i].expected_receiver_reason); teardown_direct_connection (test, data); } } static void test_close_clean_server_direct (Test *test, gconstpointer data) { guint i; for (i = 0; i < G_N_ELEMENTS (close_clean_tests); i++) { if (!(close_clean_tests[i].flags & CLOSE_TEST_FLAG_SERVER)) continue; setup_direct_connection (test, data); do_close_clean_server (test, close_clean_tests[i].code, close_clean_tests[i].reason, close_clean_tests[i].expected_sender_code, close_clean_tests[i].expected_sender_reason, close_clean_tests[i].expected_receiver_code, close_clean_tests[i].expected_receiver_reason); teardown_direct_connection (test, data); } } static gboolean on_closing_send_message (SoupWebsocketConnection *ws, gpointer data) { GBytes *message = data; soup_websocket_connection_send_text (ws, g_bytes_get_data (message, NULL)); g_signal_handlers_disconnect_by_func (ws, on_closing_send_message, data); return TRUE; } static void test_message_after_closing (Test *test, gconstpointer data) { gboolean close_event_client = FALSE; gboolean close_event_server = FALSE; GBytes *received = NULL; GBytes *message; message = g_bytes_new_static ("another test because", strlen ("another test because")); g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event_client); g_signal_connect (test->client, "message", G_CALLBACK (on_text_message), &received); g_signal_connect (test->server, "closed", G_CALLBACK (on_close_set_flag), &close_event_server); g_signal_connect (test->server, "closing", G_CALLBACK (on_closing_send_message), message); soup_websocket_connection_close (test->client, SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "another reason"); g_assert_cmpint (soup_websocket_connection_get_state (test->client), ==, SOUP_WEBSOCKET_STATE_CLOSING); WAIT_UNTIL (soup_websocket_connection_get_state (test->server) == SOUP_WEBSOCKET_STATE_CLOSED); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); g_assert (close_event_client); g_assert (close_event_server); g_assert (received != NULL); g_assert (g_bytes_equal (message, received)); g_bytes_unref (received); g_bytes_unref (message); } static gpointer close_after_close_server_thread (gpointer user_data) { Test *test = user_data; gsize written; const char frames[] = "\x88\x09\x03\xe8""reason1" "\x88\x09\x03\xe8""reason2"; GSocket *socket; GError *error = NULL; g_mutex_lock (&test->mutex); g_mutex_unlock (&test->mutex); g_output_stream_write_all (g_io_stream_get_output_stream (test->raw_server), frames, sizeof (frames) -1, &written, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (written, ==, sizeof (frames) - 1); socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (test->raw_server)); g_socket_shutdown (socket, FALSE, TRUE, &error); g_assert_no_error (error); return NULL; } static void test_close_after_close (Test *test, gconstpointer data) { GThread *thread; g_mutex_lock (&test->mutex); thread = g_thread_new ("close-after-close-thread", close_after_close_server_thread, test); soup_websocket_connection_close (test->client, SOUP_WEBSOCKET_CLOSE_NORMAL, "reason1"); g_mutex_unlock (&test->mutex); g_thread_join (thread); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); g_assert_cmpuint (soup_websocket_connection_get_close_code (test->client), ==, SOUP_WEBSOCKET_CLOSE_NORMAL); g_assert_cmpstr (soup_websocket_connection_get_close_data (test->client), ==, "reason1"); g_io_stream_close (test->raw_server, NULL, NULL); } static gboolean on_close_unref_connection (SoupWebsocketConnection *ws, gpointer user_data) { Test *test = user_data; g_assert_true (test->server == ws); g_clear_object (&test->server); return TRUE; } static void test_server_unref_connection_on_close (Test *test, gconstpointer data) { gboolean close_event_client = FALSE; g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event_client); g_signal_connect (test->server, "closed", G_CALLBACK (on_close_unref_connection), test); soup_websocket_connection_close (test->client, SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "client closed"); g_assert_cmpint (soup_websocket_connection_get_state (test->client), ==, SOUP_WEBSOCKET_STATE_CLOSING); WAIT_UNTIL (test->server == NULL); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); g_assert_true (close_event_client); } static gpointer timeout_server_thread (gpointer user_data) { Test *test = user_data; GError *error = NULL; /* don't close until the client has timed out */ g_mutex_lock (&test->mutex); g_mutex_unlock (&test->mutex); g_io_stream_close (test->raw_server, NULL, &error); g_assert_no_error (error); return NULL; } static void test_close_after_timeout (Test *test, gconstpointer data) { gboolean close_event = FALSE; GThread *thread; g_mutex_lock (&test->mutex); /* Note that no real server is around in this test, so no close happens */ thread = g_thread_new ("timeout-thread", timeout_server_thread, test); g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event); g_signal_connect (test->client, "error", G_CALLBACK (on_error_not_reached), NULL); /* Now try and close things */ soup_websocket_connection_close (test->client, 0, NULL); g_assert_cmpint (soup_websocket_connection_get_state (test->client), ==, SOUP_WEBSOCKET_STATE_CLOSING); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); g_assert (close_event == TRUE); /* Now actually close the server side stream */ g_mutex_unlock (&test->mutex); g_thread_join (thread); } static gpointer send_fragments_server_thread (gpointer user_data) { Test *test = user_data; gsize written; const char fragments[] = "\x01\x04""one " /* !fin | opcode */ "\x00\x04""two " /* !fin | no opcode */ "\x80\x05""three"; /* fin | no opcode */ GError *error = NULL; g_output_stream_write_all (g_io_stream_get_output_stream (test->raw_server), fragments, sizeof (fragments) -1, &written, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (written, ==, sizeof (fragments) - 1); g_io_stream_close (test->raw_server, NULL, &error); g_assert_no_error (error); return NULL; } static void do_deflate (z_stream *zstream, const char *str, guint8 *buffer, gsize *length) { zstream->next_in = (void *)str; zstream->avail_in = strlen (str); zstream->next_out = buffer; zstream->avail_out = 512; g_assert_cmpint (deflate(zstream, Z_NO_FLUSH), ==, Z_OK); g_assert_cmpint (zstream->avail_in, ==, 0); g_assert_cmpint (deflate(zstream, Z_SYNC_FLUSH), ==, Z_OK); g_assert_cmpint (deflate(zstream, Z_SYNC_FLUSH), ==, Z_BUF_ERROR); *length = 512 - zstream->avail_out; g_assert_cmpuint (*length, <, 126); } static gpointer send_compressed_fragments_server_thread (gpointer user_data) { Test *test = user_data; gsize written; z_stream zstream; GByteArray *data; guint8 byte; guint8 buffer[512]; gsize buffer_length; GError *error = NULL; memset (&zstream, 0, sizeof(z_stream)); g_assert (deflateInit2 (&zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) == Z_OK); data = g_byte_array_new (); do_deflate (&zstream, "one ", buffer, &buffer_length); byte = 0x00 | 0x01 | 0x40; /* !fin | opcode | compressed */ data = g_byte_array_append (data, &byte, 1); byte = (0xFF & buffer_length); /* mask | 7-bit-len */ data = g_byte_array_append (data, &byte, 1); data = g_byte_array_append (data, buffer, buffer_length); do_deflate (&zstream, "two ", buffer, &buffer_length); byte = 0x00; /* !fin | no opcode */ data = g_byte_array_append (data, &byte, 1); byte = (0xFF & buffer_length); /* mask | 7-bit-len */ data = g_byte_array_append (data, &byte, 1); data = g_byte_array_append (data, buffer, buffer_length); do_deflate (&zstream, "three", buffer, &buffer_length); g_assert_cmpuint (buffer_length, >=, 4); buffer_length -= 4; byte = 0x80; /* fin | no opcode */ data = g_byte_array_append (data, &byte, 1); byte = (0xFF & buffer_length); /* mask | 7-bit-len */ data = g_byte_array_append (data, &byte, 1); data = g_byte_array_append (data, buffer, buffer_length); g_output_stream_write_all (g_io_stream_get_output_stream (test->raw_server), data->data, data->len, &written, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (written, ==, data->len); g_io_stream_close (test->raw_server, NULL, &error); g_assert_no_error (error); deflateEnd (&zstream); g_byte_array_free (data, TRUE); return NULL; } static void test_receive_fragmented (Test *test, gconstpointer data) { GThread *thread; GBytes *received = NULL; GBytes *expect; thread = g_thread_new ("fragment-thread", test->enable_extensions ? send_compressed_fragments_server_thread : send_fragments_server_thread, test); g_signal_connect (test->client, "error", G_CALLBACK (on_error_not_reached), NULL); g_signal_connect (test->client, "message", G_CALLBACK (on_text_message), &received); WAIT_UNTIL (received != NULL); expect = g_bytes_new ("one two three", 13); g_assert (g_bytes_equal (expect, received)); g_bytes_unref (expect); g_bytes_unref (received); g_thread_join (thread); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); } typedef struct { Test *test; const char *header; GString *payload; } InvalidEncodeLengthTest; static gpointer send_invalid_encode_length_server_thread (gpointer user_data) { InvalidEncodeLengthTest *test = user_data; gsize header_size; gsize written; GError *error = NULL; header_size = test->payload->len == 125 ? 5 : 10; g_output_stream_write_all (g_io_stream_get_output_stream (test->test->raw_server), test->header, header_size, &written, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (written, ==, header_size); g_output_stream_write_all (g_io_stream_get_output_stream (test->test->raw_server), test->payload->str, test->payload->len, &written, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (written, ==, test->payload->len); g_io_stream_close (test->test->raw_server, NULL, &error); g_assert_no_error (error); return NULL; } static void test_receive_invalid_encode_length_16 (Test *test, gconstpointer data) { GThread *thread; GBytes *received = NULL; GError *error = NULL; InvalidEncodeLengthTest context = { test, NULL }; guint i; g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error); g_signal_connect (test->client, "message", G_CALLBACK (on_binary_message), &received); /* We use 126(~) as payload length with 125 extended length */ context.header = "\x82~\x00}"; context.payload = g_string_new (NULL); for (i = 0; i < 125; i++) g_string_append (context.payload, "X"); thread = g_thread_new ("invalid-encode-length-thread", send_invalid_encode_length_server_thread, &context); WAIT_UNTIL (error != NULL || received != NULL); g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR); g_clear_error (&error); g_assert_null (received); g_thread_join (thread); g_string_free (context.payload, TRUE); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); } static void test_receive_invalid_encode_length_64 (Test *test, gconstpointer data) { GThread *thread; GBytes *received = NULL; GError *error = NULL; InvalidEncodeLengthTest context = { test, NULL }; guint i; g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error); g_signal_connect (test->client, "message", G_CALLBACK (on_binary_message), &received); /* We use 127(\x7f) as payload length with 65535 extended length */ context.header = "\x82\x7f\x00\x00\x00\x00\x00\x00\xff\xff"; context.payload = g_string_new (NULL); for (i = 0; i < 65535; i++) g_string_append (context.payload, "X"); thread = g_thread_new ("invalid-encode-length-thread", send_invalid_encode_length_server_thread, &context); WAIT_UNTIL (error != NULL || received != NULL); g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR); g_clear_error (&error); g_assert_null (received); g_thread_join (thread); g_string_free (context.payload, TRUE); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); } static gpointer send_masked_frame_server_thread (gpointer user_data) { Test *test = user_data; const char frame[] = "\x82\x8e\x9a"; gsize written; GError *error = NULL; g_output_stream_write_all (g_io_stream_get_output_stream (test->raw_server), frame, sizeof (frame), &written, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (written, ==, sizeof (frame)); g_io_stream_close (test->raw_server, NULL, &error); g_assert_no_error (error); return NULL; } static void test_client_receive_masked_frame (Test *test, gconstpointer data) { GThread *thread; GBytes *received = NULL; GError *error = NULL; g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error); g_signal_connect (test->client, "message", G_CALLBACK (on_binary_message), &received); thread = g_thread_new ("send-masked-frame-thread", send_masked_frame_server_thread, test); WAIT_UNTIL (error != NULL || received != NULL); g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR); g_clear_error (&error); g_assert_null (received); g_thread_join (thread); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); } static void test_server_receive_unmasked_frame (Test *test, gconstpointer data) { GError *error = NULL; GIOStream *io; gsize written; const char *frame; gboolean close_event = FALSE; g_signal_handlers_disconnect_by_func (test->server, on_error_not_reached, NULL); g_signal_connect (test->server, "error", G_CALLBACK (on_error_copy), &error); g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event); io = soup_websocket_connection_get_io_stream (test->client); /* Unmasked frame */ frame = "\x81\x0bHello World"; if (!g_output_stream_write_all (g_io_stream_get_output_stream (io), frame, 13, &written, NULL, NULL)) g_assert_not_reached (); g_assert_cmpuint (written, ==, 13); WAIT_UNTIL (error != NULL); g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR); g_clear_error (&error); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); g_assert (close_event); g_assert_cmpuint (soup_websocket_connection_get_close_code (test->client), ==, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR); } static void test_client_context_got_server_connection (SoupServer *server, SoupServerMessage *msg, const char *path, SoupWebsocketConnection *connection, gpointer user_data) { Test *test = user_data; GSocketAddress *addr; GInetAddress *iaddr; char *str; const char *remote_ip; addr = soup_server_message_get_local_address (msg); iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (addr)); str = g_inet_address_to_string (iaddr); if (g_inet_address_get_family (iaddr) == G_SOCKET_FAMILY_IPV4) g_assert_cmpstr (str, ==, "127.0.0.1"); else g_assert_cmpstr (str, ==, "::1"); g_free (str); addr = soup_server_message_get_remote_address (msg); iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (addr)); str = g_inet_address_to_string (iaddr); if (g_inet_address_get_family (iaddr) == G_SOCKET_FAMILY_IPV4) g_assert_cmpstr (str, ==, "127.0.0.1"); else g_assert_cmpstr (str, ==, "::1"); remote_ip = soup_server_message_get_remote_host (msg); g_assert_cmpstr (remote_ip, ==, str); g_free (str); g_assert_nonnull (soup_server_message_get_socket (msg)); test->server = g_object_ref (connection); } static void test_client_context (Test *test, gconstpointer unused) { setup_soup_server (test, NULL, NULL, test_client_context_got_server_connection, test); client_connect (test, NULL, NULL, got_client_connection, test); WAIT_UNTIL (test->server != NULL); WAIT_UNTIL (test->client != NULL || test->client_error != NULL); g_assert_no_error (test->client_error); } static struct { const char *client_extension; gboolean expected_prepare_result; gboolean server_supports_extensions; gboolean expected_check_result; gboolean expected_accepted_extension; gboolean expected_verify_result; const char *server_extension; } deflate_negotiate_tests[] = { { "permessage-deflate", /* prepare supported check accepted verify */ TRUE, TRUE, TRUE, TRUE, TRUE, "permessage-deflate" }, { "permessage-deflate", /* prepare supported check accepted verify */ TRUE, FALSE, TRUE, FALSE, TRUE, "permessage-deflate" }, { "permessage-deflate; server_no_context_takeover", /* prepare supported check accepted verify */ TRUE, TRUE, TRUE, TRUE, TRUE, "permessage-deflate; server_no_context_takeover" }, { "permessage-deflate; client_no_context_takeover", /* prepare supported check accepted verify */ TRUE, TRUE, TRUE, TRUE, TRUE, "permessage-deflate; client_no_context_takeover" }, { "permessage-deflate; server_max_window_bits=8", /* prepare supported check accepted verify */ TRUE, TRUE, TRUE, TRUE, TRUE, "permessage-deflate; server_max_window_bits=8" }, { "permessage-deflate; client_max_window_bits", /* prepare supported check accepted verify */ TRUE, TRUE, TRUE, TRUE, TRUE, "permessage-deflate; client_max_window_bits=15" }, { "permessage-deflate; client_max_window_bits=10", /* prepare supported check accepted verify */ TRUE, TRUE, TRUE, TRUE, TRUE, "permessage-deflate; client_max_window_bits=10" }, { "permessage-deflate; client_no_context_takeover; server_max_window_bits=10", /* prepare supported check accepted verify */ TRUE, TRUE, TRUE, TRUE, TRUE, "permessage-deflate; client_no_context_takeover; server_max_window_bits=10" }, { "permessage-deflate; unknown_parameter", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; client_no_context_takeover; client_no_context_takeover", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; server_max_window_bits=10; server_max_window_bits=15", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; client_no_context_takeover=15", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; server_no_context_takeover=15", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; server_max_window_bits", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; server_max_window_bits=7", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; server_max_window_bits=16", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; client_max_window_bits=7", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; client_max_window_bits=16", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; client_max_window_bits=foo", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; server_max_window_bits=bar", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; client_max_window_bits=15foo", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, { "permessage-deflate; server_max_window_bits=10bar", /* prepare supported check accepted verify */ TRUE, TRUE, FALSE, FALSE, FALSE, NULL }, }; static void test_deflate_negotiate_direct (Test *test, gconstpointer unused) { GPtrArray *supported_extensions; guint i; supported_extensions = g_ptr_array_new_full (1, g_type_class_unref); g_ptr_array_add (supported_extensions, g_type_class_ref (SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE)); for (i = 0; i < G_N_ELEMENTS (deflate_negotiate_tests); i++) { SoupMessage *msg; SoupServerMessage *server_msg; SoupMessageHeaders *request_headers; SoupMessageHeaders *response_headers; SoupMessageHeadersIter iter; const char *name, *value; gboolean result; GList *accepted_extensions = NULL; GError *error = NULL; msg = soup_message_new ("GET", "http://127.0.0.1"); soup_websocket_client_prepare_handshake (msg, NULL, NULL, NULL); soup_message_headers_append (soup_message_get_request_headers (msg), "Sec-WebSocket-Extensions", deflate_negotiate_tests[i].client_extension); server_msg = g_object_new (SOUP_TYPE_SERVER_MESSAGE, NULL); soup_server_message_set_method (server_msg, soup_message_get_method (msg)); soup_server_message_set_uri (server_msg, soup_message_get_uri (msg)); request_headers = soup_server_message_get_request_headers (server_msg); soup_message_headers_iter_init (&iter, soup_message_get_request_headers (msg)); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (request_headers, name, value); result = soup_websocket_server_check_handshake (server_msg, NULL, NULL, deflate_negotiate_tests[i].server_supports_extensions ? supported_extensions : NULL, &error); g_assert (result == deflate_negotiate_tests[i].expected_check_result); if (result) { g_assert_no_error (error); } else { g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE); g_clear_error (&error); } result = soup_websocket_server_process_handshake (server_msg, NULL, NULL, deflate_negotiate_tests[i].server_supports_extensions ? supported_extensions : NULL, &accepted_extensions); g_assert (result == deflate_negotiate_tests[i].expected_check_result); soup_message_set_status (msg, soup_server_message_get_status (server_msg), NULL); response_headers = soup_server_message_get_response_headers (server_msg); soup_message_headers_iter_init (&iter, response_headers); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (soup_message_get_response_headers (msg), name, value); if (deflate_negotiate_tests[i].expected_accepted_extension) { const char *extension; extension = soup_message_headers_get_one (soup_message_get_response_headers (msg), "Sec-WebSocket-Extensions"); g_assert_cmpstr (extension, ==, deflate_negotiate_tests[i].server_extension); g_assert_nonnull (accepted_extensions); g_assert_cmpuint (g_list_length (accepted_extensions), ==, 1); g_assert (SOUP_IS_WEBSOCKET_EXTENSION_DEFLATE (accepted_extensions->data)); g_list_free_full (accepted_extensions, g_object_unref); accepted_extensions = NULL; } else { g_assert_null (accepted_extensions); } result = soup_websocket_client_verify_handshake (msg, supported_extensions, &accepted_extensions, &error); g_assert (result == deflate_negotiate_tests[i].expected_verify_result); if (result) { g_assert_no_error (error); } else { g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE); g_clear_error (&error); } if (deflate_negotiate_tests[i].expected_accepted_extension) { g_assert_nonnull (accepted_extensions); g_assert_cmpuint (g_list_length (accepted_extensions), ==, 1); g_assert (SOUP_IS_WEBSOCKET_EXTENSION_DEFLATE (accepted_extensions->data)); g_list_free_full (accepted_extensions, g_object_unref); accepted_extensions = NULL; } else { g_assert_null (accepted_extensions); } g_object_unref (msg); g_object_unref (server_msg); } g_ptr_array_unref (supported_extensions); } static void test_deflate_disabled_in_message_direct (Test *test, gconstpointer unused) { SoupMessage *msg; SoupServerMessage *server_msg; SoupMessageHeaders *request_headers; SoupMessageHeaders *response_headers; SoupMessageHeadersIter iter; const char *name, *value; GPtrArray *supported_extensions; GList *accepted_extensions = NULL; GError *error = NULL; supported_extensions = g_ptr_array_new_full (1, g_type_class_unref); g_ptr_array_add (supported_extensions, g_type_class_ref (SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE)); msg = soup_message_new ("GET", "http://127.0.0.1"); soup_message_disable_feature (msg, SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE); soup_websocket_client_prepare_handshake (msg, NULL, NULL, supported_extensions); g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_request_headers (msg), "Sec-WebSocket-Extensions"), ==, NULL); server_msg = g_object_new (SOUP_TYPE_SERVER_MESSAGE, NULL); soup_server_message_set_method (server_msg, soup_message_get_method (msg)); soup_server_message_set_uri (server_msg, soup_message_get_uri (msg)); request_headers = soup_server_message_get_request_headers (server_msg); soup_message_headers_iter_init (&iter, soup_message_get_request_headers (msg)); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (request_headers, name, value); g_assert_true (soup_websocket_server_check_handshake (server_msg, NULL, NULL, supported_extensions, &error)); g_assert_no_error (error); g_assert_true (soup_websocket_server_process_handshake (server_msg, NULL, NULL, supported_extensions, &accepted_extensions)); g_assert_null (accepted_extensions); soup_message_set_status (msg, soup_server_message_get_status (server_msg), NULL); response_headers = soup_server_message_get_response_headers (server_msg); soup_message_headers_iter_init (&iter, response_headers); while (soup_message_headers_iter_next (&iter, &name, &value)) soup_message_headers_append (soup_message_get_response_headers (msg), name, value); g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_response_headers (msg), "Sec-WebSocket-Extensions"), ==, NULL); g_assert_true (soup_websocket_client_verify_handshake (msg, supported_extensions, &accepted_extensions, &error)); g_assert_no_error (error); g_assert_null (accepted_extensions); g_object_unref (msg); g_object_unref (server_msg); g_ptr_array_unref (supported_extensions); } static void test_deflate_disabled_in_message_soup (Test *test, gconstpointer unused) { test->enable_extensions = TRUE; test->disable_deflate_in_message = TRUE; setup_soup_server (test, NULL, NULL, got_server_connection, test); client_connect (test, NULL, NULL, got_client_connection, test); WAIT_UNTIL (test->server != NULL); WAIT_UNTIL (test->client != NULL || test->client_error != NULL); g_assert_no_error (test->client_error); g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_request_headers (test->msg), "Sec-WebSocket-Extensions"), ==, NULL); g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_response_headers (test->msg), "Sec-WebSocket-Extensions"), ==, NULL); } static gpointer send_compressed_fragments_error_server_thread (gpointer user_data) { Test *test = user_data; gsize written; z_stream zstream; GByteArray *data; guint8 byte; guint8 buffer[512]; gsize buffer_length; GError *error = NULL; memset (&zstream, 0, sizeof(z_stream)); g_assert (deflateInit2 (&zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) == Z_OK); data = g_byte_array_new (); do_deflate (&zstream, "one ", buffer, &buffer_length); byte = 0x00 | 0x01 | 0x40; /* !fin | opcode | compressed */ data = g_byte_array_append (data, &byte, 1); byte = (0xFF & buffer_length); /* mask | 7-bit-len */ data = g_byte_array_append (data, &byte, 1); data = g_byte_array_append (data, buffer, buffer_length); /* Only the first fragment should include the compressed bit set. */ do_deflate (&zstream, "two ", buffer, &buffer_length); byte = 0x00 | 0x00 | 0x40; /* !fin | no opcode | compressed */ data = g_byte_array_append (data, &byte, 1); byte = (0xFF & buffer_length); /* mask | 7-bit-len */ data = g_byte_array_append (data, &byte, 1); data = g_byte_array_append (data, buffer, buffer_length); do_deflate (&zstream, "three", buffer, &buffer_length); g_assert_cmpuint (buffer_length, >=, 4); buffer_length -= 4; byte = 0x80; /* fin | no opcode */ data = g_byte_array_append (data, &byte, 1); byte = (0xFF & buffer_length); /* mask | 7-bit-len */ data = g_byte_array_append (data, &byte, 1); data = g_byte_array_append (data, buffer, buffer_length); g_output_stream_write_all (g_io_stream_get_output_stream (test->raw_server), data->data, data->len, &written, NULL, &error); g_assert_no_error (error); g_assert_cmpuint (written, ==, data->len); g_io_stream_close (test->raw_server, NULL, &error); g_assert_no_error (error); deflateEnd (&zstream); g_byte_array_free (data, TRUE); return NULL; } static void test_deflate_receive_fragmented_error (Test *test, gconstpointer data) { GThread *thread; GBytes *received = NULL; gboolean close_event = FALSE; GError *error = NULL; thread = g_thread_new ("deflate-fragment-error-thread", send_compressed_fragments_error_server_thread, test); g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error); g_signal_connect (test->client, "message", G_CALLBACK (on_text_message), &received); g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event); WAIT_UNTIL (error != NULL || received != NULL); g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR); g_clear_error (&error); g_assert_null (received); g_thread_join (thread); WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); g_assert (close_event); } static void test_cookies_in_request (Test *test, gconstpointer data) { SoupCookie *cookie; const char *cookie_header; SoupCookie *requested_cookie; cookie = soup_cookie_new ("foo", "bar", "127.0.0.1", "/", -1); test->initial_cookies = g_list_prepend (test->initial_cookies, soup_cookie_copy (cookie)); setup_soup_server (test, NULL, NULL, got_server_connection, test); client_connect (test, NULL, NULL, got_client_connection, test); WAIT_UNTIL (test->server != NULL); WAIT_UNTIL (test->client != NULL || test->client_error != NULL); g_assert_no_error (test->client_error); cookie_header = soup_message_headers_get_one (soup_message_get_request_headers (test->msg), "Cookie"); requested_cookie = soup_cookie_parse (cookie_header, NULL); g_assert_true (soup_cookie_equal (cookie, requested_cookie)); soup_cookie_free (cookie); soup_cookie_free (requested_cookie); } static void cookies_test_websocket_server_request_started (SoupServer *server, SoupServerMessage *msg, gpointer user_data) { soup_message_headers_append (soup_server_message_get_response_headers (msg), "Set-Cookie", "foo=bar; Path=/"); } static void test_cookies_in_response (Test *test, gconstpointer data) { SoupCookieJar *jar; GSList *cookies; SoupCookie *cookie; setup_soup_server (test, NULL, NULL, got_server_connection, test); g_signal_connect (test->soup_server, "request-started", G_CALLBACK (cookies_test_websocket_server_request_started), NULL); client_connect (test, NULL, NULL, got_client_connection, test); WAIT_UNTIL (test->server != NULL); WAIT_UNTIL (test->client != NULL || test->client_error != NULL); g_assert_no_error (test->client_error); jar = SOUP_COOKIE_JAR (soup_session_get_feature (test->session, SOUP_TYPE_COOKIE_JAR)); cookies = soup_cookie_jar_all_cookies (jar); g_assert_nonnull (cookies); g_assert_cmpuint (g_slist_length (cookies), ==, 1); cookie = soup_cookie_new ("foo", "bar", "127.0.0.1", "/", -1); g_assert_true (soup_cookie_equal (cookie, (SoupCookie *)cookies->data)); g_slist_free_full (cookies, (GDestroyNotify)soup_cookie_free); soup_cookie_free (cookie); } static void test_connection_error_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GError **error = user_data; g_assert_null (soup_session_websocket_connect_finish (SOUP_SESSION (object), result, error)); } static void test_connection_error (void) { SoupSession *session; SoupMessage *msg; GError *error = NULL; session = soup_test_session_new (NULL); msg = soup_message_new ("GET", "ws://127.0.0.1:1234/unix"); soup_session_websocket_connect_async (session, msg, NULL, NULL, G_PRIORITY_DEFAULT, NULL, test_connection_error_cb, &error); WAIT_UNTIL (error != NULL); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED); g_error_free (error); g_object_unref (msg); soup_test_session_abort_unref (session); } int main (int argc, char *argv[]) { int ret; test_init (argc, argv, NULL); g_test_add ("/websocket/soup/handshake", Test, NULL, setup_soup_connection, test_handshake, teardown_soup_connection); g_test_add ("/websocket/soup/handshake-error", Test, NULL, NULL, test_handshake_unsupported_extension, teardown_soup_connection); g_test_add ("/websocket/direct/send-client-to-server", Test, NULL, setup_direct_connection, test_send_client_to_server, teardown_direct_connection); g_test_add ("/websocket/soup/send-client-to-server", Test, NULL, setup_soup_connection, test_send_client_to_server, teardown_soup_connection); g_test_add ("/websocket/direct/send-server-to-client", Test, NULL, setup_direct_connection, test_send_server_to_client, teardown_direct_connection); g_test_add ("/websocket/soup/send-server-to-client", Test, NULL, setup_soup_connection, test_send_server_to_client, teardown_soup_connection); g_test_add ("/websocket/direct/send-big-packets", Test, NULL, setup_direct_connection, test_send_big_packets, teardown_direct_connection); g_test_add ("/websocket/soup/send-big-packets", Test, NULL, setup_soup_connection, test_send_big_packets, teardown_soup_connection); g_test_add ("/websocket/direct/send-empty-packets", Test, NULL, setup_direct_connection, test_send_empty_packets, teardown_direct_connection); g_test_add ("/websocket/soup/send-empty-packets", Test, NULL, setup_soup_connection, test_send_empty_packets, teardown_soup_connection); g_test_add ("/websocket/direct/send-bad-data", Test, NULL, setup_direct_connection, test_send_bad_data, teardown_direct_connection); g_test_add ("/websocket/soup/send-bad-data", Test, NULL, setup_soup_connection, test_send_bad_data, teardown_soup_connection); g_test_add ("/websocket/direct/close-clean-client", Test, NULL, NULL, test_close_clean_client_direct, NULL); g_test_add ("/websocket/soup/close-clean-client", Test, NULL, NULL, test_close_clean_client_soup, NULL); g_test_add ("/websocket/direct/close-clean-server", Test, NULL, NULL, test_close_clean_server_direct, NULL); g_test_add ("/websocket/soup/close-clean-server", Test, NULL, NULL, test_close_clean_server_soup, NULL); g_test_add ("/websocket/direct/message-after-closing", Test, NULL, setup_direct_connection, test_message_after_closing, teardown_direct_connection); g_test_add ("/websocket/soup/message-after-closing", Test, NULL, setup_soup_connection, test_message_after_closing, teardown_soup_connection); g_test_add ("/websocket/direct/close-after-close", Test, NULL, setup_half_direct_connection, test_close_after_close, teardown_direct_connection); g_test_add ("/websocket/soup/server-unref-connection-on-close", Test, NULL, setup_soup_connection, test_server_unref_connection_on_close, teardown_soup_connection); g_test_add ("/websocket/direct/protocol-negotiate", Test, NULL, NULL, test_protocol_negotiate_direct, NULL); g_test_add ("/websocket/soup/protocol-negotiate", Test, NULL, NULL, test_protocol_negotiate_soup, teardown_soup_connection); g_test_add ("/websocket/direct/protocol-mismatch", Test, NULL, NULL, test_protocol_mismatch_direct, NULL); g_test_add ("/websocket/soup/protocol-mismatch", Test, NULL, NULL, test_protocol_mismatch_soup, teardown_soup_connection); g_test_add ("/websocket/direct/protocol-server-any", Test, NULL, NULL, test_protocol_server_any_direct, NULL); g_test_add ("/websocket/soup/protocol-server-any", Test, NULL, NULL, test_protocol_server_any_soup, teardown_soup_connection); g_test_add ("/websocket/direct/protocol-client-any", Test, NULL, NULL, test_protocol_client_any_direct, NULL); g_test_add ("/websocket/soup/protocol-client-any", Test, NULL, NULL, test_protocol_client_any_soup, teardown_soup_connection); g_test_add ("/websocket/direct/invalid-protocols-ignored", Test, NULL, NULL, test_soup_websocket_client_prepare_handshake_ignores_invalid_protocols, NULL); g_test_add ("/websocket/direct/invalid-protocols-rejected", Test, NULL, NULL, test_protocol_client_invalid_direct, NULL); g_test_add ("/websocket/direct/receive-fragmented", Test, NULL, setup_half_direct_connection, test_receive_fragmented, teardown_direct_connection); g_test_add ("/websocket/direct/receive-invalid-encode-length-16", Test, NULL, setup_half_direct_connection, test_receive_invalid_encode_length_16, teardown_direct_connection); g_test_add ("/websocket/direct/receive-invalid-encode-length-64", Test, NULL, setup_half_direct_connection, test_receive_invalid_encode_length_64, teardown_direct_connection); g_test_add ("/websocket/direct/client-receive-masked-frame", Test, NULL, setup_half_direct_connection, test_client_receive_masked_frame, teardown_direct_connection); g_test_add ("/websocket/direct/server-receive-unmasked-frame", Test, NULL, setup_direct_connection, test_server_receive_unmasked_frame, teardown_direct_connection); g_test_add ("/websocket/soup/server-receive-unmasked-frame", Test, NULL, setup_soup_connection, test_server_receive_unmasked_frame, teardown_soup_connection); g_test_add ("/websocket/soup/deflate-handshake", Test, NULL, setup_soup_connection_with_extensions, test_handshake, teardown_soup_connection); g_test_add ("/websocket/direct/deflate-negotiate", Test, NULL, NULL, test_deflate_negotiate_direct, NULL); g_test_add ("/websocket/direct/deflate-disabled-in-message", Test, NULL, NULL, test_deflate_disabled_in_message_direct, NULL); g_test_add ("/websocket/soup/deflate-disabled-in-message", Test, NULL, NULL, test_deflate_disabled_in_message_soup, teardown_soup_connection); g_test_add ("/websocket/direct/deflate-send-client-to-server", Test, NULL, setup_direct_connection_with_extensions, test_send_client_to_server, teardown_direct_connection); g_test_add ("/websocket/soup/deflate-send-client-to-server", Test, NULL, setup_soup_connection_with_extensions, test_send_client_to_server, teardown_soup_connection); g_test_add ("/websocket/direct/deflate-send-server-to-client", Test, NULL, setup_direct_connection_with_extensions, test_send_server_to_client, teardown_direct_connection); g_test_add ("/websocket/soup/deflate-send-server-to-client", Test, NULL, setup_soup_connection_with_extensions, test_send_server_to_client, teardown_soup_connection); g_test_add ("/websocket/direct/deflate-send-big-packets", Test, NULL, setup_direct_connection_with_extensions, test_send_big_packets, teardown_direct_connection); g_test_add ("/websocket/soup/deflate-send-big-packets", Test, NULL, setup_soup_connection_with_extensions, test_send_big_packets, teardown_soup_connection); g_test_add ("/websocket/direct/deflate-send-empty-packets", Test, NULL, setup_direct_connection_with_extensions, test_send_empty_packets, teardown_direct_connection); g_test_add ("/websocket/soup/deflate-send-empty-packets", Test, NULL, setup_soup_connection_with_extensions, test_send_empty_packets, teardown_soup_connection); g_test_add ("/websocket/direct/deflate-receive-fragmented", Test, NULL, setup_half_direct_connection_with_extensions, test_receive_fragmented, teardown_direct_connection); g_test_add ("/websocket/direct/deflate-receive-fragmented-error", Test, NULL, setup_half_direct_connection_with_extensions, test_deflate_receive_fragmented_error, teardown_direct_connection); if (g_test_slow ()) { g_test_add ("/websocket/direct/close-after-timeout", Test, NULL, setup_half_direct_connection, test_close_after_timeout, teardown_direct_connection); } g_test_add ("/websocket/soup/client-context", Test, NULL, NULL, test_client_context, teardown_soup_connection); g_test_add ("/websocket/soup/cookies-in-request", Test, NULL, NULL, test_cookies_in_request, teardown_soup_connection); g_test_add ("/websocket/soup/cookies-in-response", Test, NULL, NULL, test_cookies_in_response, teardown_soup_connection); g_test_add_func ("/websocket/soup/connection-error", test_connection_error); ret = g_test_run (); test_cleanup (); return ret; }