Commit e590c906 authored by Carlos Garcia Campos's avatar Carlos Garcia Campos Committed by Carlos Garcia Campos
Browse files

WebSockets: allow to send close frames with no body

Using the code SOUP_WEBSOCKET_CLOSE_NO_STATUS. The spec says that code
should not be included in a frame, but that doesn't mean we can't use it
on the API level to mean no status. We were using 0 internally which is
not a valid code either. When an empty close frame is received we still
reply with a SOUP_WEBSOCKET_CLOSE_NORMAL code frame, but we return
SOUP_WEBSOCKET_CLOSE_NO_STATUS from
soup_websocket_connection_get_close_code() because that's actually what
we received.
parent d9c729aa
Pipeline #97715 passed with stage
in 55 seconds
......@@ -660,6 +660,10 @@ close_connection (SoupWebsocketConnection *self,
code);
}
break;
case SOUP_WEBSOCKET_CLOSE_NO_STATUS:
/* This is special case to send a close message with no body */
code = 0;
break;
default:
if (code < 3000) {
g_debug ("Wrong closing code %d received", code);
......@@ -695,6 +699,7 @@ receive_close (SoupWebsocketConnection *self,
switch (len) {
case 0:
/* Send a clean close when having an empty payload */
pv->peer_close_code = SOUP_WEBSOCKET_CLOSE_NO_STATUS;
close_connection (self, 1000, NULL);
return;
case 1:
......@@ -1913,6 +1918,8 @@ soup_websocket_connection_send_message (SoupWebsocketConnection *self,
* main loop runs.
*
* The @code and @data are sent to the peer along with the close request.
* If @code is %SOUP_WEBSOCKET_CLOSE_NO_STATUS a close message with no body
* (without code and data) is sent.
* Note that the @data must be UTF-8 valid.
*
* Since: 2.50
......@@ -1928,8 +1935,7 @@ soup_websocket_connection_close (SoupWebsocketConnection *self,
pv = self->pv;
g_return_if_fail (!pv->close_sent);
g_return_if_fail (code != SOUP_WEBSOCKET_CLOSE_NO_STATUS &&
code != SOUP_WEBSOCKET_CLOSE_ABNORMAL &&
g_return_if_fail (code != SOUP_WEBSOCKET_CLOSE_ABNORMAL &&
code != SOUP_WEBSOCKET_CLOSE_TLS_HANDSHAKE);
if (pv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER)
g_return_if_fail (code != SOUP_WEBSOCKET_CLOSE_NO_EXTENSION);
......
......@@ -733,9 +733,28 @@ test_protocol_client_any_soup (Test *test,
g_assert_cmpstr (soup_message_headers_get_one (test->msg->response_headers, "Sec-WebSocket-Protocol"), ==, NULL);
}
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;
} close_clean_tests[] = {
{ SOUP_WEBSOCKET_CLOSE_NORMAL, "NORMAL", SOUP_WEBSOCKET_CLOSE_NORMAL, "NORMAL", SOUP_WEBSOCKET_CLOSE_NORMAL, "NORMAL" },
{ SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "GOING_AWAY", SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "GOING_AWAY", SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "GOING_AWAY" },
{ SOUP_WEBSOCKET_CLOSE_NORMAL, NULL, SOUP_WEBSOCKET_CLOSE_NORMAL, NULL, SOUP_WEBSOCKET_CLOSE_NORMAL, NULL },
{ SOUP_WEBSOCKET_CLOSE_NO_STATUS, NULL, SOUP_WEBSOCKET_CLOSE_NORMAL, NULL, SOUP_WEBSOCKET_CLOSE_NO_STATUS, NULL },
};
static void
test_close_clean_client (Test *test,
gconstpointer data)
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;
......@@ -743,7 +762,7 @@ test_close_clean_client (Test *test,
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, SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "give me a reason");
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);
......@@ -752,14 +771,62 @@ test_close_clean_client (Test *test,
g_assert (close_event_client);
g_assert (close_event_server);
g_assert_cmpint (soup_websocket_connection_get_close_code (test->client), ==, SOUP_WEBSOCKET_CLOSE_GOING_AWAY);
g_assert_cmpint (soup_websocket_connection_get_close_code (test->server), ==, SOUP_WEBSOCKET_CLOSE_GOING_AWAY);
g_assert_cmpstr (soup_websocket_connection_get_close_data (test->server), ==, "give me a reason");
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++) {
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++) {
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
test_close_clean_server (Test *test,
gconstpointer data)
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;
......@@ -767,7 +834,7 @@ test_close_clean_server (Test *test,
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, SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "another reason");
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);
......@@ -776,9 +843,52 @@ test_close_clean_server (Test *test,
g_assert (close_event_client);
g_assert (close_event_server);
g_assert_cmpint (soup_websocket_connection_get_close_code (test->server), ==, SOUP_WEBSOCKET_CLOSE_GOING_AWAY);
g_assert_cmpint (soup_websocket_connection_get_close_code (test->client), ==, SOUP_WEBSOCKET_CLOSE_GOING_AWAY);
g_assert_cmpstr (soup_websocket_connection_get_close_data (test->client), ==, "another reason");
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++) {
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++) {
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
......@@ -1233,23 +1343,19 @@ main (int argc,
test_send_bad_data,
teardown_soup_connection);
g_test_add ("/websocket/direct/close-clean-client", Test, NULL,
setup_direct_connection,
test_close_clean_client,
teardown_direct_connection);
g_test_add ("/websocket/soup/close-clean-client", Test, NULL,
setup_soup_connection,
test_close_clean_client,
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,
setup_direct_connection,
test_close_clean_server,
teardown_direct_connection);
g_test_add ("/websocket/soup/close-clean-server", Test, NULL,
setup_soup_connection,
test_close_clean_server,
teardown_soup_connection);
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,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment