|
Packit Service |
ca3877 |
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
Packit Service |
ca3877 |
/*
|
|
Packit Service |
ca3877 |
* soup-websocket.c: This file was originally part of Cockpit.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Copyright 2013, 2014 Red Hat, Inc.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Cockpit is free software; you can redistribute it and/or modify it
|
|
Packit Service |
ca3877 |
* under the terms of the GNU Lesser General Public License as published by
|
|
Packit Service |
ca3877 |
* the Free Software Foundation; either version 2.1 of the License, or
|
|
Packit Service |
ca3877 |
* (at your option) any later version.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Cockpit is distributed in the hope that it will be useful, but
|
|
Packit Service |
ca3877 |
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit Service |
ca3877 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Packit Service |
ca3877 |
* Lesser General Public License for more details.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* You should have received a copy of the GNU Lesser General Public License
|
|
Packit Service |
ca3877 |
* along with this library; If not, see <http://www.gnu.org/licenses/>.
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
#include "config.h"
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
#include <stdlib.h>
|
|
Packit Service |
ca3877 |
#include <string.h>
|
|
Packit Service |
ca3877 |
#include <glib/gi18n-lib.h>
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
#include "soup-websocket.h"
|
|
Packit Service |
ca3877 |
#include "soup-headers.h"
|
|
Packit Service |
ca3877 |
#include "soup-message.h"
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
#define FIXED_DIGEST_LEN 20
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/**
|
|
Packit Service |
ca3877 |
* SECTION:soup-websocket
|
|
Packit Service |
ca3877 |
* @short_description: The WebSocket Protocol
|
|
Packit Service |
ca3877 |
* @see_also: soup_session_websocket_connect_async(),
|
|
Packit Service |
ca3877 |
* soup_server_add_websocket_handler()
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* #SoupWebsocketConnection provides support for the
|
|
Packit Service |
ca3877 |
* url="http://tools.ietf.org/html/rfc6455">WebSocket</ulink> protocol.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* To connect to a WebSocket server, create a #SoupSession and call
|
|
Packit Service |
ca3877 |
* soup_session_websocket_connect_async(). To accept WebSocket
|
|
Packit Service |
ca3877 |
* connections, create a #SoupServer and add a handler to it with
|
|
Packit Service |
ca3877 |
* soup_server_add_websocket_handler().
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* (Lower-level support is available via
|
|
Packit Service |
ca3877 |
* soup_websocket_client_prepare_handshake() and
|
|
Packit Service |
ca3877 |
* soup_websocket_client_verify_handshake(), for handling the client
|
|
Packit Service |
ca3877 |
* side of the WebSocket handshake, and
|
|
Packit Service |
ca3877 |
* soup_websocket_server_process_handshake() for handling the server
|
|
Packit Service |
ca3877 |
* side.)
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* #SoupWebsocketConnection handles the details of WebSocket
|
|
Packit Service |
ca3877 |
* communication. You can use soup_websocket_connection_send_text()
|
|
Packit Service |
ca3877 |
* and soup_websocket_connection_send_binary() to send data, and the
|
|
Packit Service |
ca3877 |
* #SoupWebsocketConnection::message signal to receive data.
|
|
Packit Service |
ca3877 |
* (#SoupWebsocketConnection currently only supports asynchronous
|
|
Packit Service |
ca3877 |
* I/O.)
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Since: 2.50
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/**
|
|
Packit Service |
ca3877 |
* SOUP_WEBSOCKET_ERROR:
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* A #GError domain for WebSocket-related errors. Used with
|
|
Packit Service |
ca3877 |
* #SoupWebsocketError.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Since: 2.50
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/**
|
|
Packit Service |
ca3877 |
* SoupWebsocketError:
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_ERROR_FAILED: a generic error
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET: attempted to handshake with a
|
|
Packit Service |
ca3877 |
* server that does not appear to understand WebSockets.
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE: the WebSocket handshake failed
|
|
Packit Service |
ca3877 |
* because some detail was invalid (eg, incorrect accept key).
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_ERROR_BAD_ORIGIN: the WebSocket handshake failed
|
|
Packit Service |
ca3877 |
* because the "Origin" header was not an allowed value.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* WebSocket-related errors.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Since: 2.50
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/**
|
|
Packit Service |
ca3877 |
* SoupWebsocketConnectionType:
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CONNECTION_UNKNOWN: unknown/invalid connection
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CONNECTION_CLIENT: a client-side connection
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CONNECTION_SERVER: a server-side connection
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* The type of a #SoupWebsocketConnection.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Since: 2.50
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/**
|
|
Packit Service |
ca3877 |
* SoupWebsocketDataType:
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_DATA_TEXT: UTF-8 text
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_DATA_BINARY: binary data
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* The type of data contained in a #SoupWebsocketConnection::message
|
|
Packit Service |
ca3877 |
* signal.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Since: 2.50
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/**
|
|
Packit Service |
ca3877 |
* SoupWebsocketCloseCode:
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_NORMAL: a normal, non-error close
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_GOING_AWAY: the client/server is going away
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR: a protocol error occurred
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_UNSUPPORTED_DATA: the endpoint received data
|
|
Packit Service |
ca3877 |
* of a type that it does not support.
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_NO_STATUS: reserved value indicating that
|
|
Packit Service |
ca3877 |
* no close code was present; must not be sent.
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_ABNORMAL: reserved value indicating that
|
|
Packit Service |
ca3877 |
* the connection was closed abnormally; must not be sent.
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_BAD_DATA: the endpoint received data that
|
|
Packit Service |
ca3877 |
* was invalid (eg, non-UTF-8 data in a text message).
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_POLICY_VIOLATION: generic error code
|
|
Packit Service |
ca3877 |
* indicating some sort of policy violation.
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_TOO_BIG: the endpoint received a message
|
|
Packit Service |
ca3877 |
* that is too big to process.
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_NO_EXTENSION: the client is closing the
|
|
Packit Service |
ca3877 |
* connection because the server failed to negotiate a required
|
|
Packit Service |
ca3877 |
* extension.
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_SERVER_ERROR: the server is closing the
|
|
Packit Service |
ca3877 |
* connection because it was unable to fulfill the request.
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_CLOSE_TLS_HANDSHAKE: reserved value indicating that
|
|
Packit Service |
ca3877 |
* the TLS handshake failed; must not be sent.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Pre-defined close codes that can be passed to
|
|
Packit Service |
ca3877 |
* soup_websocket_connection_close() or received from
|
|
Packit Service |
ca3877 |
* soup_websocket_connection_get_close_code(). (However, other codes
|
|
Packit Service |
ca3877 |
* are also allowed.)
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Since: 2.50
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/**
|
|
Packit Service |
ca3877 |
* SoupWebsocketState:
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_STATE_OPEN: the connection is ready to send messages
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_STATE_CLOSING: the connection is in the process of
|
|
Packit Service |
ca3877 |
* closing down; messages may be received, but not sent
|
|
Packit Service |
ca3877 |
* @SOUP_WEBSOCKET_STATE_CLOSED: the connection is completely closed down
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* The state of the WebSocket connection.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Since: 2.50
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
GQuark
|
|
Packit Service |
ca3877 |
soup_websocket_error_get_quark (void)
|
|
Packit Service |
ca3877 |
{
|
|
Packit Service |
ca3877 |
return g_quark_from_static_string ("web-socket-error-quark");
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
static gboolean
|
|
Packit Service |
ca3877 |
validate_key (const char *key)
|
|
Packit Service |
ca3877 |
{
|
|
Packit Service |
ca3877 |
guchar buf[18];
|
|
Packit Service |
ca3877 |
int state = 0;
|
|
Packit Service |
ca3877 |
guint save = 0;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/* The spec requires us to check that the key is "a
|
|
Packit Service |
ca3877 |
* base64-encoded value that, when decoded, is 16 bytes in
|
|
Packit Service |
ca3877 |
* length".
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
if (strlen (key) != 24)
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
if (g_base64_decode_step (key, 24, buf, &state, &save) != 16)
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
return TRUE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
static char *
|
|
Packit Service |
ca3877 |
compute_accept_key (const char *key)
|
|
Packit Service |
ca3877 |
{
|
|
Packit Service |
ca3877 |
gsize digest_len = FIXED_DIGEST_LEN;
|
|
Packit Service |
ca3877 |
guchar digest[FIXED_DIGEST_LEN];
|
|
Packit Service |
ca3877 |
GChecksum *checksum;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (!key)
|
|
Packit Service |
ca3877 |
return NULL;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
checksum = g_checksum_new (G_CHECKSUM_SHA1);
|
|
Packit Service |
ca3877 |
g_return_val_if_fail (checksum != NULL, NULL);
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
g_checksum_update (checksum, (guchar *)key, -1);
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/* magic from: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 */
|
|
Packit Service |
ca3877 |
g_checksum_update (checksum, (guchar *)"258EAFA5-E914-47DA-95CA-C5AB0DC85B11", -1);
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
g_checksum_get_digest (checksum, digest, &digest_len);
|
|
Packit Service |
ca3877 |
g_checksum_free (checksum);
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
g_assert (digest_len == FIXED_DIGEST_LEN);
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
return g_base64_encode (digest, digest_len);
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
static gboolean
|
|
Packit Service |
ca3877 |
choose_subprotocol (SoupMessage *msg,
|
|
Packit Service |
ca3877 |
const char **server_protocols,
|
|
Packit Service |
ca3877 |
const char **chosen_protocol)
|
|
Packit Service |
ca3877 |
{
|
|
Packit Service |
ca3877 |
const char *client_protocols_str;
|
|
Packit Service |
ca3877 |
char **client_protocols;
|
|
Packit Service |
ca3877 |
int i, j;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (chosen_protocol)
|
|
Packit Service |
ca3877 |
*chosen_protocol = NULL;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (!server_protocols)
|
|
Packit Service |
ca3877 |
return TRUE;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
client_protocols_str = soup_message_headers_get_one (msg->request_headers,
|
|
Packit Service |
ca3877 |
"Sec-Websocket-Protocol");
|
|
Packit Service |
ca3877 |
if (!client_protocols_str)
|
|
Packit Service |
ca3877 |
return TRUE;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
client_protocols = g_strsplit_set (client_protocols_str, ", ", -1);
|
|
Packit Service |
ca3877 |
if (!client_protocols || !client_protocols[0]) {
|
|
Packit Service |
ca3877 |
g_strfreev (client_protocols);
|
|
Packit Service |
ca3877 |
return TRUE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
for (i = 0; server_protocols[i] != NULL; i++) {
|
|
Packit Service |
ca3877 |
for (j = 0; client_protocols[j] != NULL; j++) {
|
|
Packit Service |
ca3877 |
if (g_str_equal (server_protocols[i], client_protocols[j])) {
|
|
Packit Service |
ca3877 |
g_strfreev (client_protocols);
|
|
Packit Service |
ca3877 |
if (chosen_protocol)
|
|
Packit Service |
ca3877 |
*chosen_protocol = server_protocols[i];
|
|
Packit Service |
ca3877 |
return TRUE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
g_strfreev (client_protocols);
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/**
|
|
Packit Service |
ca3877 |
* soup_websocket_client_prepare_handshake:
|
|
Packit Service |
ca3877 |
* @msg: a #SoupMessage
|
|
Packit Service |
ca3877 |
* @origin: (allow-none): the "Origin" header to set
|
|
Packit Service |
ca3877 |
* @protocols: (allow-none) (array zero-terminated=1): list of
|
|
Packit Service |
ca3877 |
* protocols to offer
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Adds the necessary headers to @msg to request a WebSocket
|
|
Packit Service |
ca3877 |
* handshake. The message body and non-WebSocket-related headers are
|
|
Packit Service |
ca3877 |
* not modified.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* This is a low-level function; if you use
|
|
Packit Service |
ca3877 |
* soup_session_websocket_connect_async() to create a WebSocket
|
|
Packit Service |
ca3877 |
* connection, it will call this for you.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Since: 2.50
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
void
|
|
Packit Service |
ca3877 |
soup_websocket_client_prepare_handshake (SoupMessage *msg,
|
|
Packit Service |
ca3877 |
const char *origin,
|
|
Packit Service |
ca3877 |
char **protocols)
|
|
Packit Service |
ca3877 |
{
|
|
Packit Service |
ca3877 |
guint32 raw[4];
|
|
Packit Service |
ca3877 |
char *key;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
soup_message_headers_replace (msg->request_headers, "Upgrade", "websocket");
|
|
Packit Service |
ca3877 |
soup_message_headers_append (msg->request_headers, "Connection", "Upgrade");
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
raw[0] = g_random_int ();
|
|
Packit Service |
ca3877 |
raw[1] = g_random_int ();
|
|
Packit Service |
ca3877 |
raw[2] = g_random_int ();
|
|
Packit Service |
ca3877 |
raw[3] = g_random_int ();
|
|
Packit Service |
ca3877 |
key = g_base64_encode ((const guchar *)raw, sizeof (raw));
|
|
Packit Service |
ca3877 |
soup_message_headers_replace (msg->request_headers, "Sec-WebSocket-Key", key);
|
|
Packit Service |
ca3877 |
g_free (key);
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
soup_message_headers_replace (msg->request_headers, "Sec-WebSocket-Version", "13");
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (origin)
|
|
Packit Service |
ca3877 |
soup_message_headers_replace (msg->request_headers, "Origin", origin);
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (protocols) {
|
|
Packit Service |
ca3877 |
char *protocols_str;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
protocols_str = g_strjoinv (", ", protocols);
|
|
Packit Service |
ca3877 |
soup_message_headers_replace (msg->request_headers,
|
|
Packit Service |
ca3877 |
"Sec-WebSocket-Protocol", protocols_str);
|
|
Packit Service |
ca3877 |
g_free (protocols_str);
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/**
|
|
Packit Service |
ca3877 |
* soup_websocket_server_check_handshake:
|
|
Packit Service |
ca3877 |
* @msg: #SoupMessage containing the client side of a WebSocket handshake
|
|
Packit Service |
ca3877 |
* @origin: (allow-none): expected Origin header
|
|
Packit Service |
ca3877 |
* @protocols: (allow-none) (array zero-terminated=1): allowed WebSocket
|
|
Packit Service |
ca3877 |
* protocols.
|
|
Packit Service |
ca3877 |
* @error: return location for a #GError
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Examines the method and request headers in @msg and determines
|
|
Packit Service |
ca3877 |
* whether @msg contains a valid handshake request.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* If @origin is non-%NULL, then only requests containing a matching
|
|
Packit Service |
ca3877 |
* "Origin" header will be accepted. If @protocols is non-%NULL, then
|
|
Packit Service |
ca3877 |
* only requests containing a compatible "Sec-WebSocket-Protocols"
|
|
Packit Service |
ca3877 |
* header will be accepted.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Normally soup_websocket_server_process_handshake() will take care
|
|
Packit Service |
ca3877 |
* of this for you, and if you use soup_server_add_websocket_handler()
|
|
Packit Service |
ca3877 |
* to handle accepting WebSocket connections, it will call that for
|
|
Packit Service |
ca3877 |
* you. However, this function may be useful if you need to perform
|
|
Packit Service |
ca3877 |
* more complicated validation; eg, accepting multiple different Origins,
|
|
Packit Service |
ca3877 |
* or handling different protocols depending on the path.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Returns: %TRUE if @msg contained a valid WebSocket handshake,
|
|
Packit Service |
ca3877 |
* %FALSE and an error if not.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Since: 2.50
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
gboolean
|
|
Packit Service |
ca3877 |
soup_websocket_server_check_handshake (SoupMessage *msg,
|
|
Packit Service |
ca3877 |
const char *expected_origin,
|
|
Packit Service |
ca3877 |
char **protocols,
|
|
Packit Service |
ca3877 |
GError **error)
|
|
Packit Service |
ca3877 |
{
|
|
Packit Service |
ca3877 |
const char *origin;
|
|
Packit Service |
ca3877 |
const char *key;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (msg->method != SOUP_METHOD_GET) {
|
|
Packit Service |
ca3877 |
g_set_error_literal (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET,
|
|
Packit Service |
ca3877 |
_("WebSocket handshake expected"));
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (!soup_message_headers_header_equals (msg->request_headers, "Upgrade", "websocket") ||
|
|
Packit Service |
ca3877 |
!soup_message_headers_header_contains (msg->request_headers, "Connection", "upgrade")) {
|
|
Packit Service |
ca3877 |
g_set_error_literal (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET,
|
|
Packit Service |
ca3877 |
_("WebSocket handshake expected"));
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (!soup_message_headers_header_equals (msg->request_headers, "Sec-WebSocket-Version", "13")) {
|
|
Packit Service |
ca3877 |
g_set_error_literal (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
|
|
Packit Service |
ca3877 |
_("Unsupported WebSocket version"));
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
key = soup_message_headers_get_one (msg->request_headers, "Sec-WebSocket-Key");
|
|
Packit Service |
ca3877 |
if (key == NULL || !validate_key (key)) {
|
|
Packit Service |
ca3877 |
g_set_error_literal (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
|
|
Packit Service |
ca3877 |
_("Invalid WebSocket key"));
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (expected_origin) {
|
|
Packit Service |
ca3877 |
origin = soup_message_headers_get_one (msg->request_headers, "Origin");
|
|
Packit Service |
ca3877 |
if (!origin || g_ascii_strcasecmp (origin, expected_origin) != 0) {
|
|
Packit Service |
ca3877 |
g_set_error (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_BAD_ORIGIN,
|
|
Packit Service |
ca3877 |
_("Incorrect WebSocket ā%sā header"), "Origin");
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (!choose_subprotocol (msg, (const char **) protocols, NULL)) {
|
|
Packit Service |
ca3877 |
g_set_error_literal (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
|
|
Packit Service |
ca3877 |
_("Unsupported WebSocket subprotocol"));
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
return TRUE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
#define RESPONSE_FORBIDDEN "<html><head><title>400 Forbidden</title></head>\r\n" \
|
|
Packit Service |
ca3877 |
"<body>Received invalid WebSocket request</body></html>\r\n"
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
static void
|
|
Packit Service |
ca3877 |
respond_handshake_forbidden (SoupMessage *msg)
|
|
Packit Service |
ca3877 |
{
|
|
Packit Service |
ca3877 |
soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
|
|
Packit Service |
ca3877 |
soup_message_headers_append (msg->response_headers, "Connection", "close");
|
|
Packit Service |
ca3877 |
soup_message_set_response (msg, "text/html", SOUP_MEMORY_COPY,
|
|
Packit Service |
ca3877 |
RESPONSE_FORBIDDEN, strlen (RESPONSE_FORBIDDEN));
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
#define RESPONSE_BAD "<html><head><title>400 Bad Request</title></head>\r\n" \
|
|
Packit Service |
ca3877 |
"<body>Received invalid WebSocket request: %s</body></html>\r\n"
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
static void
|
|
Packit Service |
ca3877 |
respond_handshake_bad (SoupMessage *msg, const char *why)
|
|
Packit Service |
ca3877 |
{
|
|
Packit Service |
ca3877 |
char *text;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
text = g_strdup_printf (RESPONSE_BAD, why);
|
|
Packit Service |
ca3877 |
soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
|
|
Packit Service |
ca3877 |
soup_message_headers_append (msg->response_headers, "Connection", "close");
|
|
Packit Service |
ca3877 |
soup_message_set_response (msg, "text/html", SOUP_MEMORY_TAKE,
|
|
Packit Service |
ca3877 |
text, strlen (text));
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/**
|
|
Packit Service |
ca3877 |
* soup_websocket_server_process_handshake:
|
|
Packit Service |
ca3877 |
* @msg: #SoupMessage containing the client side of a WebSocket handshake
|
|
Packit Service |
ca3877 |
* @expected_origin: (allow-none): expected Origin header
|
|
Packit Service |
ca3877 |
* @protocols: (allow-none) (array zero-terminated=1): allowed WebSocket
|
|
Packit Service |
ca3877 |
* protocols.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Examines the method and request headers in @msg and (assuming @msg
|
|
Packit Service |
ca3877 |
* contains a valid handshake request), fills in the handshake
|
|
Packit Service |
ca3877 |
* response.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* If @expected_origin is non-%NULL, then only requests containing a matching
|
|
Packit Service |
ca3877 |
* "Origin" header will be accepted. If @protocols is non-%NULL, then
|
|
Packit Service |
ca3877 |
* only requests containing a compatible "Sec-WebSocket-Protocols"
|
|
Packit Service |
ca3877 |
* header will be accepted.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* This is a low-level function; if you use
|
|
Packit Service |
ca3877 |
* soup_server_add_websocket_handler() to handle accepting WebSocket
|
|
Packit Service |
ca3877 |
* connections, it will call this for you.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Returns: %TRUE if @msg contained a valid WebSocket handshake
|
|
Packit Service |
ca3877 |
* request and was updated to contain a handshake response. %FALSE if not.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Since: 2.50
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
gboolean
|
|
Packit Service |
ca3877 |
soup_websocket_server_process_handshake (SoupMessage *msg,
|
|
Packit Service |
ca3877 |
const char *expected_origin,
|
|
Packit Service |
ca3877 |
char **protocols)
|
|
Packit Service |
ca3877 |
{
|
|
Packit Service |
ca3877 |
const char *chosen_protocol = NULL;
|
|
Packit Service |
ca3877 |
const char *key;
|
|
Packit Service |
ca3877 |
char *accept_key;
|
|
Packit Service |
ca3877 |
GError *error = NULL;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (!soup_websocket_server_check_handshake (msg, expected_origin, protocols, &error)) {
|
|
Packit Service |
ca3877 |
if (g_error_matches (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_BAD_ORIGIN))
|
|
Packit Service |
ca3877 |
respond_handshake_forbidden (msg);
|
|
Packit Service |
ca3877 |
else
|
|
Packit Service |
ca3877 |
respond_handshake_bad (msg, error->message);
|
|
Packit Service |
ca3877 |
g_error_free (error);
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
soup_message_set_status (msg, SOUP_STATUS_SWITCHING_PROTOCOLS);
|
|
Packit Service |
ca3877 |
soup_message_headers_replace (msg->response_headers, "Upgrade", "websocket");
|
|
Packit Service |
ca3877 |
soup_message_headers_append (msg->response_headers, "Connection", "Upgrade");
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
key = soup_message_headers_get_one (msg->request_headers, "Sec-WebSocket-Key");
|
|
Packit Service |
ca3877 |
accept_key = compute_accept_key (key);
|
|
Packit Service |
ca3877 |
soup_message_headers_append (msg->response_headers, "Sec-WebSocket-Accept", accept_key);
|
|
Packit Service |
ca3877 |
g_free (accept_key);
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
choose_subprotocol (msg, (const char **) protocols, &chosen_protocol);
|
|
Packit Service |
ca3877 |
if (chosen_protocol)
|
|
Packit Service |
ca3877 |
soup_message_headers_append (msg->response_headers, "Sec-WebSocket-Protocol", chosen_protocol);
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
return TRUE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
/**
|
|
Packit Service |
ca3877 |
* soup_websocket_client_verify_handshake:
|
|
Packit Service |
ca3877 |
* @msg: #SoupMessage containing both client and server sides of a
|
|
Packit Service |
ca3877 |
* WebSocket handshake
|
|
Packit Service |
ca3877 |
* @error: return location for a #GError
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Looks at the response status code and headers in @msg and
|
|
Packit Service |
ca3877 |
* determines if they contain a valid WebSocket handshake response
|
|
Packit Service |
ca3877 |
* (given the handshake request in @msg's request headers).
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* This is a low-level function; if you use
|
|
Packit Service |
ca3877 |
* soup_session_websocket_connect_async() to create a WebSocket
|
|
Packit Service |
ca3877 |
* connection, it will call this for you.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Returns: %TRUE if @msg contains a completed valid WebSocket
|
|
Packit Service |
ca3877 |
* handshake, %FALSE and an error if not.
|
|
Packit Service |
ca3877 |
*
|
|
Packit Service |
ca3877 |
* Since: 2.50
|
|
Packit Service |
ca3877 |
*/
|
|
Packit Service |
ca3877 |
gboolean
|
|
Packit Service |
ca3877 |
soup_websocket_client_verify_handshake (SoupMessage *msg,
|
|
Packit Service |
ca3877 |
GError **error)
|
|
Packit Service |
ca3877 |
{
|
|
Packit Service |
ca3877 |
const char *protocol, *request_protocols, *extensions, *accept_key;
|
|
Packit Service |
ca3877 |
char *expected_accept_key;
|
|
Packit Service |
ca3877 |
gboolean key_ok;
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (msg->status_code == SOUP_STATUS_BAD_REQUEST) {
|
|
Packit Service |
ca3877 |
g_set_error_literal (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
|
|
Packit Service |
ca3877 |
_("Server rejected WebSocket handshake"));
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (msg->status_code != SOUP_STATUS_SWITCHING_PROTOCOLS) {
|
|
Packit Service |
ca3877 |
g_set_error_literal (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET,
|
|
Packit Service |
ca3877 |
_("Server ignored WebSocket handshake"));
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
if (!soup_message_headers_header_equals (msg->response_headers, "Upgrade", "websocket") ||
|
|
Packit Service |
ca3877 |
!soup_message_headers_header_contains (msg->response_headers, "Connection", "upgrade")) {
|
|
Packit Service |
ca3877 |
g_set_error_literal (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET,
|
|
Packit Service |
ca3877 |
_("Server ignored WebSocket handshake"));
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
protocol = soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Protocol");
|
|
Packit Service |
ca3877 |
if (protocol) {
|
|
Packit Service |
ca3877 |
request_protocols = soup_message_headers_get_one (msg->request_headers, "Sec-WebSocket-Protocol");
|
|
Packit Service |
ca3877 |
if (!request_protocols ||
|
|
Packit Service |
ca3877 |
!soup_header_contains (request_protocols, protocol)) {
|
|
Packit Service |
ca3877 |
g_set_error_literal (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
|
|
Packit Service |
ca3877 |
_("Server requested unsupported protocol"));
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
extensions = soup_message_headers_get_list (msg->response_headers, "Sec-WebSocket-Extensions");
|
|
Packit Service |
ca3877 |
if (extensions && *extensions) {
|
|
Packit Service |
ca3877 |
g_set_error_literal (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
|
|
Packit Service |
ca3877 |
_("Server requested unsupported extension"));
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
accept_key = soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Accept");
|
|
Packit Service |
ca3877 |
expected_accept_key = compute_accept_key (soup_message_headers_get_one (msg->request_headers, "Sec-WebSocket-Key"));
|
|
Packit Service |
ca3877 |
key_ok = (accept_key && expected_accept_key &&
|
|
Packit Service |
ca3877 |
!g_ascii_strcasecmp (accept_key, expected_accept_key));
|
|
Packit Service |
ca3877 |
g_free (expected_accept_key);
|
|
Packit Service |
ca3877 |
if (!key_ok) {
|
|
Packit Service |
ca3877 |
g_set_error (error,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR,
|
|
Packit Service |
ca3877 |
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
|
|
Packit Service |
ca3877 |
_("Server returned incorrect ā%sā key"),
|
|
Packit Service |
ca3877 |
"Sec-WebSocket-Accept");
|
|
Packit Service |
ca3877 |
return FALSE;
|
|
Packit Service |
ca3877 |
}
|
|
Packit Service |
ca3877 |
|
|
Packit Service |
ca3877 |
return TRUE;
|
|
Packit Service |
ca3877 |
}
|