Blame libsoup/soup-websocket.c

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
}