WebSockets: add support for permessage-deflate extension

Add new API to add WebSocket extensions to SoupSession and SoupServer
and include an implementation of permessage-deflate extension (see RFC
7692). In the client side, supported extensions are added to the session
as sub-features of a new session feature, SoupWebsocketExtensionManager.
In the client side, supported extensions are added/removed directly using
the new SoupServer API. All functions to negotiate the handshake
(client_prepare, client_verify, server_check and server_process) have
now a _with_extensions alternative to handle the extensions.
This commit is contained in:
Carlos Garcia Campos
2019-07-04 09:31:39 +02:00
committed by Carlos Garcia Campos
parent 03d0e3ed43
commit 17761724d8
25 changed files with 2662 additions and 97 deletions
+44
View File
@@ -240,8 +240,12 @@ soup_server_add_handler
soup_server_add_early_handler
soup_server_remove_handler
<SUBSECTION>
SOUP_SERVER_ADD_WEBSOCKET_EXTENSION
SOUP_SERVER_REMOVE_WEBSOCKET_EXTENSION
SoupServerWebsocketCallback
soup_server_add_websocket_handler
soup_server_add_websocket_extension
soup_server_remove_websocket_extension
<SUBSECTION>
SoupClientContext
soup_client_context_get_local_address
@@ -1304,19 +1308,25 @@ SOUP_VERSION_PREV_STABLE
<TITLE>WebSockets</TITLE>
<SUBSECTION>
soup_websocket_client_prepare_handshake
soup_websocket_client_prepare_handshake_with_extensions
soup_websocket_client_verify_handshake
soup_websocket_client_verify_handshake_with_extensions
<SUBSECTION>
soup_websocket_server_check_handshake
soup_websocket_server_check_handshake_with_extensions
soup_websocket_server_process_handshake
soup_websocket_server_process_handshake_with_extensions
<SUBSECTION>
SoupWebsocketConnection
SoupWebsocketConnectionType
soup_websocket_connection_new
soup_websocket_connection_new_with_extensions
soup_websocket_connection_get_io_stream
soup_websocket_connection_get_connection_type
soup_websocket_connection_get_uri
soup_websocket_connection_get_origin
soup_websocket_connection_get_protocol
soup_websocket_connection_get_extensions
SoupWebsocketState
soup_websocket_connection_get_state
SoupWebsocketDataType
@@ -1328,20 +1338,54 @@ soup_websocket_connection_close
soup_websocket_connection_get_close_code
soup_websocket_connection_get_close_data
<SUBSECTION>
SoupWebsocketExtensionManager
<SUBSECTION>
SoupWebsocketExtension
SoupWebsocketExtensionDeflate
soup_websocket_extension_configure
soup_websocket_extension_get_request_params
soup_websocket_extension_get_response_params
soup_websocket_extension_process_outgoing_message
soup_websocket_extension_process_incoming_message
<SUBSECTION>
SoupWebsocketError
SOUP_WEBSOCKET_ERROR
<SUBSECTION Private>
SoupWebsocketConnectionClass
SoupWebsocketConnectionPrivate
SoupWebsocketExtensionManagerClass
SoupWebsocketExtensionClass
SoupWebsocketExtensionDeflateClass
SOUP_IS_WEBSOCKET_CONNECTION
SOUP_IS_WEBSOCKET_CONNECTION_CLASS
SOUP_TYPE_WEBSOCKET_CONNECTION
SOUP_WEBSOCKET_CONNECTION
SOUP_WEBSOCKET_CONNECTION_CLASS
SOUP_WEBSOCKET_CONNECTION_GET_CLASS
SOUP_IS_WEBSOCKET_EXTENSION_MANAGER
SOUP_IS_WEBSOCKET_EXTENSION_MANAGER_CLASS
SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER
SOUP_WEBSOCKET_EXTENSION_MANAGER
SOUP_WEBSOCKET_EXTENSION_MANAGER_CLASS
SOUP_WEBSOCKET_EXTENSION_MANAGER_GET_CLASS
SOUP_IS_WEBSOCKET_EXTENSION
SOUP_IS_WEBSOCKET_EXTENSION_CLASS
SOUP_TYPE_WEBSOCKET_EXTENSION
SOUP_WEBSOCKET_EXTENSION
SOUP_WEBSOCKET_EXTENSION_CLASS
SOUP_WEBSOCKET_EXTENSION_GET_CLASS
SOUP_IS_WEBSOCKET_EXTENSION_DEFLATE
SOUP_IS_WEBSOCKET_EXTENSION_DEFLATE_CLASS
SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE
SOUP_WEBSOCKET_EXTENSION_DEFLATE
SOUP_WEBSOCKET_EXTENSION_DEFLATE_CLASS
SOUP_WEBSOCKET_EXTENSION_DEFLATE_GET_CLASS
soup_websocket_close_code_get_type
soup_websocket_connection_get_type
soup_websocket_connection_type_get_type
soup_websocket_extension_manager_get_type
soup_websocket_extension_get_type
soup_websocket_extension_deflate_get_type
soup_websocket_data_type_get_type
soup_websocket_error_get_quark
soup_websocket_error_get_type
+1
View File
@@ -40,6 +40,7 @@ ignore_headers = [
'soup-cache-client-input-stream.h',
'soup-socket-private.h',
'soup-value-utils.h',
'soup-websocket-extension-manager-private.h',
'soup-xmlrpc-old.h'
]
+8
View File
@@ -75,6 +75,9 @@ soup_sources = [
'soup-version.c',
'soup-websocket.c',
'soup-websocket-connection.c',
'soup-websocket-extension.c',
'soup-websocket-extension-deflate.c',
'soup-websocket-extension-manager.c',
'soup-xmlrpc.c',
'soup-xmlrpc-old.c',
]
@@ -106,6 +109,7 @@ soup_headers = [
'soup-proxy-resolver-wrapper.h',
'soup-session-private.h',
'soup-socket-private.h',
'soup-websocket-extension-manager-private.h',
]
soup_introspection_headers = [
@@ -160,6 +164,9 @@ soup_introspection_headers = [
'soup-value-utils.h',
'soup-websocket.h',
'soup-websocket-connection.h',
'soup-websocket-extension.h',
'soup-websocket-extension-deflate.h',
'soup-websocket-extension-manager.h',
'soup-xmlrpc.h',
'soup-xmlrpc-old.h',
]
@@ -234,6 +241,7 @@ deps = [
libpsl_dep,
brotlidec_dep,
platform_deps,
libz_dep,
]
libsoup = library('soup-@0@'.format(apiversion),
+2
View File
@@ -140,6 +140,8 @@ GInputStream *soup_message_io_get_response_istream (SoupMessage *msg,
gboolean soup_message_disables_feature (SoupMessage *msg,
gpointer feature);
gboolean soup_message_disables_feature_by_type (SoupMessage *msg,
GType feature_type);
GSList *soup_message_get_disabled_features (SoupMessage *msg);
+17
View File
@@ -1860,6 +1860,23 @@ soup_message_disables_feature (SoupMessage *msg, gpointer feature)
return FALSE;
}
gboolean
soup_message_disables_feature_by_type (SoupMessage *msg, GType feature_type)
{
SoupMessagePrivate *priv;
GSList *f;
g_return_val_if_fail (SOUP_IS_MESSAGE (msg), FALSE);
priv = soup_message_get_instance_private (msg);
for (f = priv->disabled_features; f; f = f->next) {
if (g_type_is_a ((GType)GPOINTER_TO_SIZE (f->data), feature_type))
return TRUE;
}
return FALSE;
}
GSList *
soup_message_get_disabled_features (SoupMessage *msg)
{
+150 -7
View File
@@ -21,6 +21,7 @@
#include "soup-socket-private.h"
#include "soup-websocket.h"
#include "soup-websocket-connection.h"
#include "soup-websocket-extension-deflate.h"
/**
* SECTION:soup-server
@@ -156,6 +157,7 @@ typedef struct {
char *websocket_origin;
char **websocket_protocols;
GList *websocket_extensions;
SoupServerWebsocketCallback websocket_callback;
GDestroyNotify websocket_destroy;
gpointer websocket_user_data;
@@ -183,6 +185,8 @@ typedef struct {
SoupAddress *legacy_iface;
int legacy_port;
GPtrArray *websocket_extension_types;
gboolean disposed;
} SoupServerPrivate;
@@ -204,6 +208,8 @@ enum {
PROP_SERVER_HEADER,
PROP_HTTP_ALIASES,
PROP_HTTPS_ALIASES,
PROP_ADD_WEBSOCKET_EXTENSION,
PROP_REMOVE_WEBSOCKET_EXTENSION,
LAST_PROP
};
@@ -219,6 +225,7 @@ free_handler (SoupServerHandler *handler)
g_free (handler->path);
g_free (handler->websocket_origin);
g_strfreev (handler->websocket_protocols);
g_list_free_full (handler->websocket_extensions, g_object_unref);
if (handler->early_destroy)
handler->early_destroy (handler->early_user_data);
if (handler->destroy)
@@ -240,6 +247,11 @@ soup_server_init (SoupServer *server)
priv->http_aliases[1] = NULL;
priv->legacy_port = -1;
priv->websocket_extension_types = g_ptr_array_new_with_free_func ((GDestroyNotify)g_type_class_unref);
/* Use permessage-deflate extension by default */
g_ptr_array_add (priv->websocket_extension_types, g_type_class_ref (SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE));
}
static void
@@ -278,6 +290,8 @@ soup_server_finalize (GObject *object)
g_free (priv->http_aliases);
g_free (priv->https_aliases);
g_ptr_array_free (priv->websocket_extension_types, TRUE);
G_OBJECT_CLASS (soup_server_parent_class)->finalize (object);
}
@@ -466,6 +480,12 @@ soup_server_set_property (GObject *object, guint prop_id,
case PROP_HTTPS_ALIASES:
set_aliases (&priv->https_aliases, g_value_get_boxed (value));
break;
case PROP_ADD_WEBSOCKET_EXTENSION:
soup_server_add_websocket_extension (server, g_value_get_gtype (value));
break;
case PROP_REMOVE_WEBSOCKET_EXTENSION:
soup_server_remove_websocket_extension (server, g_value_get_gtype (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -927,6 +947,51 @@ soup_server_class_init (SoupServerClass *server_class)
"URI schemes that are considered aliases for 'https'",
G_TYPE_STRV,
G_PARAM_READWRITE));
/**
* SoupServer:add-websocket-extension: (skip)
*
* Add support for #SoupWebsocketExtension of the given type.
* (Shortcut for calling soup_server_add_websocket_extension().)
*
* Since: 2.68
**/
/**
* SOUP_SERVER_ADD_WEBSOCKET_EXTENSION: (skip)
*
* Alias for the #SoupServer:add-websocket-extension property, qv.
*
* Since: 2.68
**/
g_object_class_install_property (
object_class, PROP_ADD_WEBSOCKET_EXTENSION,
g_param_spec_gtype (SOUP_SERVER_ADD_WEBSOCKET_EXTENSION,
"Add support for a WebSocket extension",
"Add support for a WebSocket extension of the given type",
SOUP_TYPE_WEBSOCKET_EXTENSION,
G_PARAM_WRITABLE));
/**
* SoupServer:remove-websocket-extension: (skip)
*
* Remove support for #SoupWebsocketExtension of the given type. (Shortcut for
* calling soup_server_remove_websocket_extension().)
*
* Since: 2.68
**/
/**
* SOUP_SERVER_REMOVE_WEBSOCKET_EXTENSION: (skip)
*
* Alias for the #SoupServer:remove-websocket-extension property, qv.
*
* Since: 2.68
**/
g_object_class_install_property (
object_class, PROP_REMOVE_WEBSOCKET_EXTENSION,
g_param_spec_gtype (SOUP_SERVER_REMOVE_WEBSOCKET_EXTENSION,
"Remove support for a WebSocket extension",
"Remove support for a WebSocket extension of the given type",
SOUP_TYPE_WEBSOCKET_EXTENSION,
G_PARAM_WRITABLE));
}
/**
@@ -1367,10 +1432,12 @@ complete_websocket_upgrade (SoupMessage *msg, gpointer user_data)
soup_client_context_ref (client);
stream = soup_client_context_steal_connection (client);
conn = soup_websocket_connection_new (stream, uri,
SOUP_WEBSOCKET_CONNECTION_SERVER,
soup_message_headers_get_one (msg->request_headers, "Origin"),
soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Protocol"));
conn = soup_websocket_connection_new_with_extensions (stream, uri,
SOUP_WEBSOCKET_CONNECTION_SERVER,
soup_message_headers_get_one (msg->request_headers, "Origin"),
soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Protocol"),
handler->websocket_extensions);
handler->websocket_extensions = NULL;
g_object_unref (stream);
soup_client_context_unref (client);
@@ -1402,9 +1469,14 @@ got_body (SoupMessage *msg, SoupClientContext *client)
return;
if (handler->websocket_callback) {
if (soup_websocket_server_process_handshake (msg,
handler->websocket_origin,
handler->websocket_protocols)) {
SoupServerPrivate *priv;
priv = soup_server_get_instance_private (server);
if (soup_websocket_server_process_handshake_with_extensions (msg,
handler->websocket_origin,
handler->websocket_protocols,
priv->websocket_extension_types,
&handler->websocket_extensions)) {
g_signal_connect (msg, "wrote-informational",
G_CALLBACK (complete_websocket_upgrade),
soup_client_context_ref (client));
@@ -2696,12 +2768,14 @@ soup_server_add_websocket_handler (SoupServer *server,
g_free (handler->websocket_origin);
if (handler->websocket_protocols)
g_strfreev (handler->websocket_protocols);
g_list_free_full (handler->websocket_extensions, g_object_unref);
handler->websocket_callback = callback;
handler->websocket_destroy = destroy;
handler->websocket_user_data = user_data;
handler->websocket_origin = g_strdup (origin);
handler->websocket_protocols = g_strdupv (protocols);
handler->websocket_extensions = NULL;
}
/**
@@ -2817,3 +2891,72 @@ soup_server_unpause_message (SoupServer *server,
soup_message_io_unpause (msg);
}
/**
* soup_server_add_websocket_extension:
* @server: a #SoupServer
* @extension_type: a #GType
*
* Add support for a WebSocket extension of the given @extension_type.
* When a WebSocket client requests an extension of @extension_type,
* a new #SoupWebsocketExtension of type @extension_type will be created
* to handle the request.
*
* You can also add support for a WebSocket extension to the server at
* construct time by using the %SOUP_SERVER_ADD_WEBSOCKET_EXTENSION property.
* Note that #SoupWebsocketExtensionDeflate is supported by default, use
* soup_server_remove_websocket_extension() if you want to disable it.
*
* Since: 2.68
*/
void
soup_server_add_websocket_extension (SoupServer *server, GType extension_type)
{
SoupServerPrivate *priv;
g_return_if_fail (SOUP_IS_SERVER (server));
priv = soup_server_get_instance_private (server);
if (!g_type_is_a (extension_type, SOUP_TYPE_WEBSOCKET_EXTENSION)) {
g_warning ("Type '%s' is not a SoupWebsocketExtension", g_type_name (extension_type));
return;
}
g_ptr_array_add (priv->websocket_extension_types, g_type_class_ref (extension_type));
}
/**
* soup_server_remove_websocket_extension:
* @server: a #SoupServer
* @extension_type: a #GType
*
* Removes support for WebSocket extension of type @extension_type (or any subclass of
* @extension_type) from @server. You can also remove extensions enabled by default
* from the server at construct time by using the %SOUP_SERVER_REMOVE_WEBSOCKET_EXTENSION
* property.
*
* Since: 2.68
*/
void
soup_server_remove_websocket_extension (SoupServer *server, GType extension_type)
{
SoupServerPrivate *priv;
SoupWebsocketExtensionClass *extension_class;
guint i;
g_return_if_fail (SOUP_IS_SERVER (server));
priv = soup_server_get_instance_private (server);
if (!g_type_is_a (extension_type, SOUP_TYPE_WEBSOCKET_EXTENSION)) {
g_warning ("Type '%s' is not a SoupWebsocketExtension", g_type_name (extension_type));
return;
}
extension_class = g_type_class_peek (extension_type);
for (i = 0; i < priv->websocket_extension_types->len; i++) {
if (priv->websocket_extension_types->pdata[i] == (gpointer)extension_class) {
g_ptr_array_remove_index (priv->websocket_extension_types, i);
break;
}
}
}
+9
View File
@@ -138,6 +138,9 @@ void soup_server_add_early_handler (SoupServer *server,
gpointer user_data,
GDestroyNotify destroy);
#define SOUP_SERVER_ADD_WEBSOCKET_EXTENSION "add-websocket-extension"
#define SOUP_SERVER_REMOVE_WEBSOCKET_EXTENSION "remove-websocket-extension"
typedef void (*SoupServerWebsocketCallback) (SoupServer *server,
SoupWebsocketConnection *connection,
const char *path,
@@ -151,6 +154,12 @@ void soup_server_add_websocket_handler (SoupServer
SoupServerWebsocketCallback callback,
gpointer user_data,
GDestroyNotify destroy);
SOUP_AVAILABLE_IN_2_68
void soup_server_add_websocket_extension (SoupServer *server,
GType extension_type);
SOUP_AVAILABLE_IN_2_68
void soup_server_remove_websocket_extension (SoupServer *server,
GType extension_type);
SOUP_AVAILABLE_IN_2_4
void soup_server_remove_handler (SoupServer *server,
+34 -11
View File
@@ -24,6 +24,7 @@
#include "soup-socket-private.h"
#include "soup-websocket.h"
#include "soup-websocket-connection.h"
#include "soup-websocket-extension-manager-private.h"
#define HOST_KEEP_ALIVE 5 * 60 * 1000 /* 5 min in msecs */
@@ -4775,6 +4776,19 @@ soup_session_steal_connection (SoupSession *session,
return stream;
}
static GPtrArray *
soup_session_get_supported_websocket_extensions_for_message (SoupSession *session,
SoupMessage *msg)
{
SoupSessionFeature *extension_manager;
extension_manager = soup_session_get_feature_for_message (session, SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER, msg);
if (!extension_manager)
return NULL;
return soup_websocket_extension_manager_get_supported_extensions (SOUP_WEBSOCKET_EXTENSION_MANAGER (extension_manager));
}
static void websocket_connect_async_stop (SoupMessage *msg, gpointer user_data);
static void
@@ -4799,6 +4813,9 @@ websocket_connect_async_stop (SoupMessage *msg, gpointer user_data)
SoupMessageQueueItem *item = g_task_get_task_data (task);
GIOStream *stream;
SoupWebsocketConnection *client;
SoupSession *session = g_task_get_source_object (task);
GPtrArray *supported_extensions;
GList *accepted_extensions = NULL;
GError *error = NULL;
/* Disconnect websocket_connect_async_stop() handler. */
@@ -4807,20 +4824,24 @@ websocket_connect_async_stop (SoupMessage *msg, gpointer user_data)
/* Ensure websocket_connect_async_complete is not called either. */
item->callback = NULL;
if (soup_websocket_client_verify_handshake (item->msg, &error)){
supported_extensions = soup_session_get_supported_websocket_extensions_for_message (session, msg);
if (soup_websocket_client_verify_handshake_with_extensions (item->msg, supported_extensions, &accepted_extensions, &error)) {
stream = soup_session_steal_connection (item->session, item->msg);
client = soup_websocket_connection_new (stream,
soup_message_get_uri (item->msg),
SOUP_WEBSOCKET_CONNECTION_CLIENT,
soup_message_headers_get_one (msg->request_headers, "Origin"),
soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Protocol"));
client = soup_websocket_connection_new_with_extensions (stream,
soup_message_get_uri (item->msg),
SOUP_WEBSOCKET_CONNECTION_CLIENT,
soup_message_headers_get_one (msg->request_headers, "Origin"),
soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Protocol"),
accepted_extensions);
g_object_unref (stream);
g_task_return_pointer (task, client, g_object_unref);
} else {
soup_message_io_finished (item->msg);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
soup_message_io_finished (item->msg);
g_task_return_error (task, error);
g_object_unref (task);
}
@@ -4868,12 +4889,14 @@ soup_session_websocket_connect_async (SoupSession *session,
SoupSessionPrivate *priv = soup_session_get_instance_private (session);
SoupMessageQueueItem *item;
GTask *task;
GPtrArray *supported_extensions;
g_return_if_fail (SOUP_IS_SESSION (session));
g_return_if_fail (priv->use_thread_context);
g_return_if_fail (SOUP_IS_MESSAGE (msg));
soup_websocket_client_prepare_handshake (msg, origin, protocols);
supported_extensions = soup_session_get_supported_websocket_extensions_for_message (session, msg);
soup_websocket_client_prepare_handshake_with_extensions (msg, origin, protocols, supported_extensions);
task = g_task_new (session, cancellable, callback, user_data);
item = soup_session_append_queue_item (session, msg, TRUE, FALSE,
+1 -1
View File
@@ -32,7 +32,7 @@ typedef struct _SoupSessionSync SoupSessionSync;
typedef struct _SoupSocket SoupSocket;
typedef struct _SoupURI SoupURI;
typedef struct _SoupWebsocketConnection SoupWebsocketConnection;
typedef struct _SoupWebsocketExtension SoupWebsocketExtension;
/*< private >*/
typedef struct _SoupConnection SoupConnection;
+142 -20
View File
@@ -26,6 +26,7 @@
#include "soup-enum-types.h"
#include "soup-io-stream.h"
#include "soup-uri.h"
#include "soup-websocket-extension.h"
/*
* SECTION:websocketconnection
@@ -84,6 +85,7 @@ enum {
PROP_STATE,
PROP_MAX_INCOMING_PAYLOAD_SIZE,
PROP_KEEPALIVE_INTERVAL,
PROP_EXTENSIONS
};
enum {
@@ -145,6 +147,8 @@ struct _SoupWebsocketConnectionPrivate {
GByteArray *message_data;
GSource *keepalive_timeout;
GList *extensions;
};
#define MAX_INCOMING_PAYLOAD_SIZE_DEFAULT 128 * 1024
@@ -154,6 +158,9 @@ G_DEFINE_TYPE_WITH_PRIVATE (SoupWebsocketConnection, soup_websocket_connection,
static void queue_frame (SoupWebsocketConnection *self, SoupWebsocketQueueFlags flags,
gpointer data, gsize len, gsize amount);
static void emit_error_and_close (SoupWebsocketConnection *self,
GError *error, gboolean prejudice);
static void protocol_error_and_close (SoupWebsocketConnection *self);
/* Code below is based on g_utf8_validate() implementation,
@@ -427,12 +434,15 @@ send_message (SoupWebsocketConnection *self,
const guint8 *data,
gsize length)
{
gsize buffered_amount = length;
gsize buffered_amount;
GByteArray *bytes;
gsize frame_len;
guint8 *outer;
guint8 *mask = 0;
guint8 *at;
GBytes *filtered_bytes;
GList *l;
GError *error = NULL;
if (!(soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_OPEN)) {
g_debug ("Ignoring message since the connection is closed or is closing");
@@ -443,6 +453,21 @@ send_message (SoupWebsocketConnection *self,
outer = bytes->data;
outer[0] = 0x80 | opcode;
filtered_bytes = g_bytes_new_static (data, length);
for (l = self->pv->extensions; l != NULL; l = g_list_next (l)) {
SoupWebsocketExtension *extension;
extension = (SoupWebsocketExtension *)l->data;
filtered_bytes = soup_websocket_extension_process_outgoing_message (extension, outer, filtered_bytes, &error);
if (error) {
emit_error_and_close (self, error, FALSE);
return;
}
}
data = g_bytes_get_data (filtered_bytes, &length);
buffered_amount = length;
/* If control message, check payload size */
if (opcode & 0x08) {
if (length > 125) {
@@ -499,6 +524,7 @@ send_message (SoupWebsocketConnection *self,
frame_len = bytes->len;
queue_frame (self, flags, g_byte_array_free (bytes, FALSE),
frame_len, buffered_amount);
g_bytes_unref (filtered_bytes);
g_debug ("queued %d frame of len %u", (int)opcode, (guint)frame_len);
}
@@ -771,11 +797,14 @@ process_contents (SoupWebsocketConnection *self,
gboolean control,
gboolean fin,
guint8 opcode,
gconstpointer payload,
gsize payload_len)
GBytes *payload_data)
{
SoupWebsocketConnectionPrivate *pv = self->pv;
GBytes *message;
gconstpointer payload;
gsize payload_len;
payload = g_bytes_get_data (payload_data, &payload_len);
if (pv->close_sent && pv->close_received)
return;
@@ -909,6 +938,9 @@ process_frame (SoupWebsocketConnection *self)
guint8 opcode;
gsize len;
gsize at;
GBytes *filtered_bytes;
GList *l;
GError *error = NULL;
len = self->pv->incoming->len;
if (len < 2)
@@ -938,12 +970,6 @@ process_frame (SoupWebsocketConnection *self)
return FALSE;
}
/* We do not support extensions, reserved bits must be 0 */
if (header[0] & 0x70) {
protocol_error_and_close (self);
return FALSE;
}
switch (header[1] & 0x7f) {
case 126:
/* If 126, the following 2 bytes interpreted as a 16-bit
@@ -1013,13 +1039,37 @@ process_frame (SoupWebsocketConnection *self)
xor_with_mask (mask, payload, payload_len);
}
filtered_bytes = g_bytes_new_static (payload, payload_len);
for (l = self->pv->extensions; l != NULL; l = g_list_next (l)) {
SoupWebsocketExtension *extension;
extension = (SoupWebsocketExtension *)l->data;
filtered_bytes = soup_websocket_extension_process_incoming_message (extension, self->pv->incoming->data, filtered_bytes, &error);
if (error) {
emit_error_and_close (self, error, FALSE);
g_bytes_unref (filtered_bytes);
return FALSE;
}
}
/* After being processed by extensions reserved bits must be 0 */
if (header[0] & 0x70) {
protocol_error_and_close (self);
g_bytes_unref (filtered_bytes);
return FALSE;
}
/* Note that now that we've unmasked, we've modified the buffer, we can
* only return below via discarding or processing the message
*/
process_contents (self, control, fin, opcode, payload, payload_len);
process_contents (self, control, fin, opcode, filtered_bytes);
g_bytes_unref (filtered_bytes);
/* Move past the parsed frame */
g_byte_array_remove_range (self->pv->incoming, 0, at + payload_len);
return TRUE;
}
@@ -1271,6 +1321,10 @@ soup_websocket_connection_get_property (GObject *object,
g_value_set_uint (value, pv->keepalive_interval);
break;
case PROP_EXTENSIONS:
g_value_set_pointer (value, pv->extensions);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1320,6 +1374,10 @@ soup_websocket_connection_set_property (GObject *object,
g_value_get_uint (value));
break;
case PROP_EXTENSIONS:
pv->extensions = g_value_get_pointer (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1368,6 +1426,8 @@ soup_websocket_connection_finalize (GObject *object)
g_free (pv->origin);
g_free (pv->protocol);
g_list_free_full (pv->extensions, g_object_unref);
G_OBJECT_CLASS (soup_websocket_connection_parent_class)->finalize (object);
}
@@ -1525,6 +1585,21 @@ soup_websocket_connection_class_init (SoupWebsocketConnectionClass *klass)
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* SoupWebsocketConnection:extensions:
*
* List of #SoupWebsocketExtension objects that are active in the connection.
*
* Since: 2.68
*/
g_object_class_install_property (gobject_class, PROP_EXTENSIONS,
g_param_spec_pointer ("extensions",
"Active extensions",
"The list of active extensions",
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* SoupWebsocketConnection::message:
* @self: the WebSocket
@@ -1644,17 +1719,46 @@ soup_websocket_connection_new (GIOStream *stream,
const char *origin,
const char *protocol)
{
g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL);
g_return_val_if_fail (uri != NULL, NULL);
g_return_val_if_fail (type != SOUP_WEBSOCKET_CONNECTION_UNKNOWN, NULL);
return soup_websocket_connection_new_with_extensions (stream, uri, type, origin, protocol, NULL);
}
return g_object_new (SOUP_TYPE_WEBSOCKET_CONNECTION,
"io-stream", stream,
"uri", uri,
"connection-type", type,
"origin", origin,
"protocol", protocol,
NULL);
/**
* soup_websocket_connection_new_with_extensions:
* @stream: a #GIOStream connected to the WebSocket server
* @uri: the URI of the connection
* @type: the type of connection (client/side)
* @origin: (allow-none): the Origin of the client
* @protocol: (allow-none): the subprotocol in use
* @extensions: (element-type SoupWebsocketExtension) (transfer full): a #GList of #SoupWebsocketExtension objects
*
* Creates a #SoupWebsocketConnection on @stream with the given active @extensions.
* This should be called after completing the handshake to begin using the WebSocket
* protocol.
*
* Returns: a new #SoupWebsocketConnection
*
* Since: 2.68
*/
SoupWebsocketConnection *
soup_websocket_connection_new_with_extensions (GIOStream *stream,
SoupURI *uri,
SoupWebsocketConnectionType type,
const char *origin,
const char *protocol,
GList *extensions)
{
g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL);
g_return_val_if_fail (uri != NULL, NULL);
g_return_val_if_fail (type != SOUP_WEBSOCKET_CONNECTION_UNKNOWN, NULL);
return g_object_new (SOUP_TYPE_WEBSOCKET_CONNECTION,
"io-stream", stream,
"uri", uri,
"connection-type", type,
"origin", origin,
"protocol", protocol,
"extensions", extensions,
NULL);
}
/**
@@ -1750,6 +1854,24 @@ soup_websocket_connection_get_protocol (SoupWebsocketConnection *self)
return self->pv->protocol;
}
/**
* soup_websocket_connection_get_extensions:
* @self: the WebSocket
*
* Get the extensions chosen via negotiation with the peer.
*
* Returns: (element-type SoupWebsocketExtension) (transfer none): a #GList of #SoupWebsocketExtension objects
*
* Since: 2.68
*/
GList *
soup_websocket_connection_get_extensions (SoupWebsocketConnection *self)
{
g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
return self->pv->extensions;
}
/**
* soup_websocket_connection_get_state:
* @self: the WebSocket
+10
View File
@@ -70,6 +70,13 @@ SoupWebsocketConnection *soup_websocket_connection_new (GIOStream
SoupWebsocketConnectionType type,
const char *origin,
const char *protocol);
SOUP_AVAILABLE_IN_2_68
SoupWebsocketConnection *soup_websocket_connection_new_with_extensions (GIOStream *stream,
SoupURI *uri,
SoupWebsocketConnectionType type,
const char *origin,
const char *protocol,
GList *extensions);
SOUP_AVAILABLE_IN_2_50
GIOStream * soup_websocket_connection_get_io_stream (SoupWebsocketConnection *self);
@@ -86,6 +93,9 @@ const char * soup_websocket_connection_get_origin (SoupWebsocketConne
SOUP_AVAILABLE_IN_2_50
const char * soup_websocket_connection_get_protocol (SoupWebsocketConnection *self);
SOUP_AVAILABLE_IN_2_68
GList * soup_websocket_connection_get_extensions (SoupWebsocketConnection *self);
SOUP_AVAILABLE_IN_2_50
SoupWebsocketState soup_websocket_connection_get_state (SoupWebsocketConnection *self);
+503
View File
@@ -0,0 +1,503 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* soup-websocket-extension-deflate.c
*
* Copyright (C) 2019 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "soup-websocket-extension-deflate.h"
#include <zlib.h>
typedef struct {
z_stream zstream;
gboolean no_context_takeover;
} Deflater;
typedef struct {
z_stream zstream;
gboolean uncompress_ongoing;
} Inflater;
#define BUFFER_SIZE 4096
typedef enum {
PARAM_SERVER_NO_CONTEXT_TAKEOVER = 1 << 0,
PARAM_CLIENT_NO_CONTEXT_TAKEOVER = 1 << 1,
PARAM_SERVER_MAX_WINDOW_BITS = 1 << 2,
PARAM_CLIENT_MAX_WINDOW_BITS = 1 << 3,
PARAM_CLIENT_MAX_WINDOW_BITS_VALUE = 1 << 4
} ParamFlags;
typedef struct {
ParamFlags flags;
gushort server_max_window_bits;
gushort client_max_window_bits;
} Params;
typedef struct {
Params params;
gboolean enabled;
Deflater deflater;
Inflater inflater;
} SoupWebsocketExtensionDeflatePrivate;
/*
* SECTION:soup-websocket-extension-deflate
* @title: SoupWebsocketExtensionDeflate
* @short_description: A permessage-deflate WebSocketExtension
* @see_also: #SoupWebsocketExtension
*
* A SoupWebsocketExtensionDeflate is a #SoupWebsocketExtension
* implementing permessage-deflate (RFC 7692).
*
* This extension is used by default in a #SoupSession when #SoupWebsocketExtensionManager
* feature is present, and always used by #SoupServer.
*
* Since: 2.68
*/
/**
* SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE:
*
* A #GType corresponding to permessage-deflate WebSocket extension.
*
* Since: 2.68
*/
G_DEFINE_TYPE_WITH_PRIVATE (SoupWebsocketExtensionDeflate, soup_websocket_extension_deflate, SOUP_TYPE_WEBSOCKET_EXTENSION)
static void
soup_websocket_extension_deflate_init (SoupWebsocketExtensionDeflate *basic)
{
}
static void
soup_websocket_extension_deflate_finalize (GObject *object)
{
SoupWebsocketExtensionDeflatePrivate *priv = soup_websocket_extension_deflate_get_instance_private (SOUP_WEBSOCKET_EXTENSION_DEFLATE (object));
if (priv->enabled) {
deflateEnd (&priv->deflater.zstream);
inflateEnd (&priv->inflater.zstream);
}
G_OBJECT_CLASS (soup_websocket_extension_deflate_parent_class)->finalize (object);
}
static gboolean
parse_window_bits (const char *value,
gushort *out)
{
guint64 int_value;
char *end = NULL;
if (!value || !*value)
return FALSE;
int_value = g_ascii_strtoull (value, &end, 10);
if (*end != '\0')
return FALSE;
if (int_value < 8 || int_value > 15)
return FALSE;
*out = (gushort)int_value;
return TRUE;
}
static gboolean
return_invalid_param_error (GError **error,
const char *param)
{
g_set_error (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
"Invalid parameter '%s' in permessage-deflate extension header",
param);
return FALSE;
}
static gboolean
return_invalid_param_value_error (GError **error,
const char *param)
{
g_set_error (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
"Invalid value of parameter '%s' in permessage-deflate extension header",
param);
return FALSE;
}
static gboolean
parse_params (GHashTable *params,
Params *out,
GError **error)
{
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init (&iter, params);
while (g_hash_table_iter_next (&iter, &key, &value)) {
if (g_str_equal ((char *)key, "server_no_context_takeover")) {
if (value)
return return_invalid_param_value_error(error, "server_no_context_takeover");
out->flags |= PARAM_SERVER_NO_CONTEXT_TAKEOVER;
} else if (g_str_equal ((char *)key, "client_no_context_takeover")) {
if (value)
return return_invalid_param_value_error(error, "client_no_context_takeover");
out->flags |= PARAM_CLIENT_NO_CONTEXT_TAKEOVER;
} else if (g_str_equal ((char *)key, "server_max_window_bits")) {
if (!parse_window_bits ((char *)value, &out->server_max_window_bits))
return return_invalid_param_value_error(error, "server_max_window_bits");
out->flags |= PARAM_SERVER_MAX_WINDOW_BITS;
} else if (g_str_equal ((char *)key, "client_max_window_bits")) {
if (value) {
if (!parse_window_bits ((char *)value, &out->client_max_window_bits))
return return_invalid_param_value_error(error, "client_max_window_bits");
out->flags |= PARAM_CLIENT_MAX_WINDOW_BITS_VALUE;
} else {
out->client_max_window_bits = 15;
}
out->flags |= PARAM_CLIENT_MAX_WINDOW_BITS;
} else {
return return_invalid_param_error (error, (char *)key);
}
}
return TRUE;
}
static gboolean
soup_websocket_extension_deflate_configure (SoupWebsocketExtension *extension,
SoupWebsocketConnectionType connection_type,
GHashTable *params,
GError **error)
{
gushort deflater_max_window_bits;
gushort inflater_max_window_bits;
SoupWebsocketExtensionDeflatePrivate *priv;
priv = soup_websocket_extension_deflate_get_instance_private (SOUP_WEBSOCKET_EXTENSION_DEFLATE (extension));
if (params && !parse_params (params, &priv->params, error))
return FALSE;
switch (connection_type) {
case SOUP_WEBSOCKET_CONNECTION_CLIENT:
priv->deflater.no_context_takeover = priv->params.flags & PARAM_CLIENT_NO_CONTEXT_TAKEOVER;
deflater_max_window_bits = priv->params.flags & PARAM_CLIENT_MAX_WINDOW_BITS ? priv->params.client_max_window_bits : 15;
inflater_max_window_bits = priv->params.flags & PARAM_SERVER_MAX_WINDOW_BITS ? priv->params.server_max_window_bits : 15;
break;
case SOUP_WEBSOCKET_CONNECTION_SERVER:
priv->deflater.no_context_takeover = priv->params.flags & PARAM_SERVER_NO_CONTEXT_TAKEOVER;
deflater_max_window_bits = priv->params.flags & PARAM_SERVER_MAX_WINDOW_BITS ? priv->params.server_max_window_bits : 15;
inflater_max_window_bits = priv->params.flags & PARAM_CLIENT_MAX_WINDOW_BITS ? priv->params.client_max_window_bits : 15;
break;
default:
g_assert_not_reached ();
}
/* zlib is unable to compress with window_bits=8, so use 9
* instead. This is compatible with decompressing using
* window_bits=8.
*/
deflater_max_window_bits = MAX (deflater_max_window_bits, 9);
/* In case of failing to initialize zlib deflater/inflater,
* we return TRUE without setting enabled = TRUE, so that the
* hanshake doesn't fail.
*/
if (deflateInit2 (&priv->deflater.zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -deflater_max_window_bits, 8, Z_DEFAULT_STRATEGY) != Z_OK)
return TRUE;
if (inflateInit2 (&priv->inflater.zstream, -inflater_max_window_bits) != Z_OK) {
deflateEnd (&priv->deflater.zstream);
return TRUE;
}
priv->enabled = TRUE;
return TRUE;
}
static char *
soup_websocket_extension_deflate_get_request_params (SoupWebsocketExtension *extension)
{
return g_strdup ("; client_max_window_bits");
}
static char *
soup_websocket_extension_deflate_get_response_params (SoupWebsocketExtension *extension)
{
GString *params;
SoupWebsocketExtensionDeflatePrivate *priv;
priv = soup_websocket_extension_deflate_get_instance_private (SOUP_WEBSOCKET_EXTENSION_DEFLATE (extension));
if (!priv->enabled)
return NULL;
if (priv->params.flags == 0)
return NULL;
params = g_string_new (NULL);
if (priv->params.flags & PARAM_SERVER_NO_CONTEXT_TAKEOVER)
params = g_string_append (params, "; server_no_context_takeover");
if (priv->params.flags & PARAM_CLIENT_NO_CONTEXT_TAKEOVER)
params = g_string_append (params, "; client_no_context_takeover");
if (priv->params.flags & PARAM_SERVER_MAX_WINDOW_BITS)
g_string_append_printf (params, "; server_max_window_bits=%u", priv->params.server_max_window_bits);
if (priv->params.flags & PARAM_CLIENT_MAX_WINDOW_BITS) {
if (priv->params.flags & PARAM_CLIENT_MAX_WINDOW_BITS_VALUE)
g_string_append_printf (params, "; client_max_window_bits=%u", priv->params.client_max_window_bits);
else
params = g_string_append (params, "; client_max_window_bits");
}
return g_string_free (params, FALSE);
}
static void
deflater_reset (Deflater *deflater)
{
if (deflater->no_context_takeover)
deflateReset (&deflater->zstream);
}
static GBytes *
soup_websocket_extension_deflate_process_outgoing_message (SoupWebsocketExtension *extension,
guint8 *header,
GBytes *payload,
GError **error)
{
const guint8 *payload_data;
gsize payload_length;
guint max_length;
gboolean control;
GByteArray *buffer;
gsize bytes_written;
int result;
gboolean in_sync_flush;
SoupWebsocketExtensionDeflatePrivate *priv;
priv = soup_websocket_extension_deflate_get_instance_private (SOUP_WEBSOCKET_EXTENSION_DEFLATE (extension));
if (!priv->enabled)
return payload;
control = header[0] & 0x08;
/* Do not compress control frames */
if (control)
return payload;
payload_data = g_bytes_get_data (payload, &payload_length);
if (payload_length == 0)
return payload;
/* Mark the frame as compressed using reserved bit 1 (0x40) */
header[0] |= 0x40;
buffer = g_byte_array_new ();
max_length = deflateBound(&priv->deflater.zstream, payload_length);
priv->deflater.zstream.next_in = (void *)payload_data;
priv->deflater.zstream.avail_in = payload_length;
bytes_written = 0;
priv->deflater.zstream.avail_out = 0;
do {
gsize write_remaining;
if (priv->deflater.zstream.avail_out == 0) {
guint write_position;
priv->deflater.zstream.avail_out = max_length;
write_position = buffer->len;
g_byte_array_set_size (buffer, buffer->len + max_length);
priv->deflater.zstream.next_out = buffer->data + write_position;
/* Use a fixed value for buffer increments */
max_length = BUFFER_SIZE;
}
write_remaining = buffer->len - bytes_written;
in_sync_flush = priv->deflater.zstream.avail_in == 0;
result = deflate (&priv->deflater.zstream, in_sync_flush ? Z_SYNC_FLUSH : Z_NO_FLUSH);
bytes_written += write_remaining - priv->deflater.zstream.avail_out;
} while (result == Z_OK);
if (result != Z_BUF_ERROR || bytes_written < 4) {
g_set_error_literal (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR,
"Failed to compress outgoing frame");
g_byte_array_unref (buffer);
deflater_reset (&priv->deflater);
return NULL;
}
/* Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end. */
g_byte_array_set_size (buffer, bytes_written - 4);
g_bytes_unref (payload);
deflater_reset (&priv->deflater);
return g_byte_array_free_to_bytes (buffer);
}
static GBytes *
soup_websocket_extension_deflate_process_incoming_message (SoupWebsocketExtension *extension,
guint8 *header,
GBytes *payload,
GError **error)
{
const guint8 *payload_data;
gsize payload_length;
gboolean fin, control, compressed;
GByteArray *buffer;
gsize bytes_read, bytes_written;
int result;
gboolean tail_added = FALSE;
SoupWebsocketExtensionDeflatePrivate *priv;
priv = soup_websocket_extension_deflate_get_instance_private (SOUP_WEBSOCKET_EXTENSION_DEFLATE (extension));
if (!priv->enabled)
return payload;
control = header[0] & 0x08;
/* Do not uncompress control frames */
if (control)
return payload;
compressed = header[0] & 0x40;
if (!priv->inflater.uncompress_ongoing && !compressed)
return payload;
if (priv->inflater.uncompress_ongoing && compressed) {
g_set_error_literal (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR,
"Received a non-first frame with RSV1 flag set");
return NULL;
}
/* Remove the compressed flag */
header[0] &= ~0x40;
fin = header[0] & 0x80;
payload_data = g_bytes_get_data (payload, &payload_length);
if (payload_length == 0 && ((!priv->inflater.uncompress_ongoing && fin) || (priv->inflater.uncompress_ongoing && !fin)))
return payload;
priv->inflater.uncompress_ongoing = !fin;
buffer = g_byte_array_new ();
bytes_read = 0;
priv->inflater.zstream.next_in = (void *)payload_data;
priv->inflater.zstream.avail_in = payload_length;
bytes_written = 0;
priv->inflater.zstream.avail_out = 0;
do {
gsize read_remaining;
gsize write_remaining;
if (priv->inflater.zstream.avail_out == 0) {
guint current_position;
priv->inflater.zstream.avail_out = BUFFER_SIZE;
current_position = buffer->len;
g_byte_array_set_size (buffer, buffer->len + BUFFER_SIZE);
priv->inflater.zstream.next_out = buffer->data + current_position;
}
if (priv->inflater.zstream.avail_in == 0 && !tail_added && fin) {
/* Append 4 octets of 0x00 0x00 0xff 0xff to the tail end */
priv->inflater.zstream.next_in = (void *)"\x00\x00\xff\xff";
priv->inflater.zstream.avail_in = 4;
bytes_read = 0;
tail_added = TRUE;
}
read_remaining = tail_added ? 4 : payload_length - bytes_read;
write_remaining = buffer->len - bytes_written;
result = inflate (&priv->inflater.zstream, tail_added ? Z_FINISH : Z_NO_FLUSH);
bytes_read += read_remaining - priv->inflater.zstream.avail_in;
bytes_written += write_remaining - priv->inflater.zstream.avail_out;
if (!tail_added && result == Z_STREAM_END) {
/* Received a block with BFINAL set to 1. Reset decompression state. */
result = inflateReset (&priv->inflater.zstream);
}
if ((!fin && bytes_read == payload_length) || (fin && tail_added && bytes_read == 4))
break;
} while (result == Z_OK || result == Z_BUF_ERROR);
if (result != Z_OK && result != Z_BUF_ERROR) {
priv->inflater.uncompress_ongoing = FALSE;
g_set_error_literal (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR,
"Failed to uncompress incoming frame");
g_byte_array_unref (buffer);
return NULL;
}
g_byte_array_set_size (buffer, bytes_written);
g_bytes_unref (payload);
return g_byte_array_free_to_bytes (buffer);
}
static void
soup_websocket_extension_deflate_class_init (SoupWebsocketExtensionDeflateClass *klass)
{
SoupWebsocketExtensionClass *extension_class = SOUP_WEBSOCKET_EXTENSION_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
extension_class->name = "permessage-deflate";
extension_class->configure = soup_websocket_extension_deflate_configure;
extension_class->get_request_params = soup_websocket_extension_deflate_get_request_params;
extension_class->get_response_params = soup_websocket_extension_deflate_get_response_params;
extension_class->process_outgoing_message = soup_websocket_extension_deflate_process_outgoing_message;
extension_class->process_incoming_message = soup_websocket_extension_deflate_process_incoming_message;
object_class->finalize = soup_websocket_extension_deflate_finalize;
}
@@ -0,0 +1,49 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* soup-websocket-extension-deflate.h
*
* Copyright (C) 2019 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __SOUP_WEBSOCKET_EXTENSION_DEFLATE_H__
#define __SOUP_WEBSOCKET_EXTENSION_DEFLATE_H__ 1
#include "soup-websocket-extension.h"
#define SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE (soup_websocket_extension_deflate_get_type ())
#define SOUP_WEBSOCKET_EXTENSION_DEFLATE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE, SoupWebsocketExtensionDeflate))
#define SOUP_IS_WEBSOCKET_EXTENSION_DEFLATE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE))
#define SOUP_WEBSOCKET_EXTENSION_DEFLATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE, SoupWebsocketExtensionDeflateClass))
#define SOUP_IS_WEBSOCKET_EXTENSION_DEFLATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE))
#define SOUP_WEBSOCKET_EXTENSION_DEFLATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE, SoupWebsocketExtensionDeflateClass))
typedef struct _SoupWebsocketExtensionDeflate SoupWebsocketExtensionDeflate;
typedef struct _SoupWebsocketExtensionDeflateClass SoupWebsocketExtensionDeflateClass;
struct _SoupWebsocketExtensionDeflate {
SoupWebsocketExtension parent;
};
struct _SoupWebsocketExtensionDeflateClass {
SoupWebsocketExtensionClass parent_class;
};
SOUP_AVAILABLE_IN_2_68
GType soup_websocket_extension_deflate_get_type (void);
#endif /* __SOUP_WEBSOCKET_EXTENSION_DEFLATE_H__ */
@@ -0,0 +1,30 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* soup-websocket-extension-manager-private.h
*
* Copyright (C) 2019 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __SOUP_WEBSOCKET_EXTENSION_MANAGER_PRIVATE_H__
#define __SOUP_WEBSOCKET_EXTENSION_MANAGER_PRIVATE_H__ 1
#include "soup-websocket-extension-manager.h"
GPtrArray *soup_websocket_extension_manager_get_supported_extensions (SoupWebsocketExtensionManager *manager);
#endif /* __SOUP_WEBSOCKET_EXTENSION_MANAGER_PRIVATE_H__ */
+180
View File
@@ -0,0 +1,180 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* soup-websocket-extension-manager.c
*
* Copyright (C) 2019 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "soup-websocket-extension-manager.h"
#include "soup-headers.h"
#include "soup-session-feature.h"
#include "soup-websocket.h"
#include "soup-websocket-extension.h"
#include "soup-websocket-extension-deflate.h"
#include "soup-websocket-extension-manager-private.h"
/**
* SECTION:soup-websocket-extension-manager
* @title: SoupWebsocketExtensionManager
* @short_description: WebSocket extensions manager
* @see_also: #SoupSession, #SoupWebsocketExtension
*
* SoupWebsocketExtensionManager is the #SoupSessionFeature that handles WebSockets
* extensions for a #SoupSession.
*
* A SoupWebsocketExtensionManager is added to the session by default, and normally
* you don't need to worry about it at all. However, if you want to
* disable WebSocket extensions, you can remove the feature from the
* session with soup_session_remove_feature_by_type(), or disable it on
* individual requests with soup_message_disable_feature().
*
* Since: 2.68
**/
/**
* SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER:
*
* The #GType of #SoupWebsocketExtensionManager; you can use this with
* soup_session_remove_feature_by_type() or
* soup_message_disable_feature().
*
* Since: 2.68
*/
static void soup_websocket_extension_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
typedef struct {
GPtrArray *extension_types;
} SoupWebsocketExtensionManagerPrivate;
G_DEFINE_TYPE_WITH_CODE (SoupWebsocketExtensionManager, soup_websocket_extension_manager, G_TYPE_OBJECT,
G_ADD_PRIVATE (SoupWebsocketExtensionManager)
G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
soup_websocket_extension_manager_session_feature_init))
static void
soup_websocket_extension_manager_init (SoupWebsocketExtensionManager *manager)
{
SoupWebsocketExtensionManagerPrivate *priv = soup_websocket_extension_manager_get_instance_private (manager);
priv->extension_types = g_ptr_array_new_with_free_func ((GDestroyNotify)g_type_class_unref);
/* Use permessage-deflate extension by default */
soup_session_feature_add_feature (SOUP_SESSION_FEATURE (manager), SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE);
}
static void
soup_websocket_extension_manager_finalize (GObject *object)
{
SoupWebsocketExtensionManagerPrivate *priv;
priv = soup_websocket_extension_manager_get_instance_private (SOUP_WEBSOCKET_EXTENSION_MANAGER (object));
g_ptr_array_free (priv->extension_types, TRUE);
G_OBJECT_CLASS (soup_websocket_extension_manager_parent_class)->finalize (object);
}
static void
soup_websocket_extension_manager_class_init (SoupWebsocketExtensionManagerClass *websocket_extension_manager_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (websocket_extension_manager_class);
object_class->finalize = soup_websocket_extension_manager_finalize;
}
static gboolean
soup_websocket_extension_manager_add_feature (SoupSessionFeature *feature, GType type)
{
SoupWebsocketExtensionManagerPrivate *priv;
if (!g_type_is_a (type, SOUP_TYPE_WEBSOCKET_EXTENSION))
return FALSE;
priv = soup_websocket_extension_manager_get_instance_private (SOUP_WEBSOCKET_EXTENSION_MANAGER (feature));
g_ptr_array_add (priv->extension_types, g_type_class_ref (type));
return TRUE;
}
static gboolean
soup_websocket_extension_manager_remove_feature (SoupSessionFeature *feature, GType type)
{
SoupWebsocketExtensionManagerPrivate *priv;
SoupWebsocketExtensionClass *extension_class;
guint i;
if (!g_type_is_a (type, SOUP_TYPE_WEBSOCKET_EXTENSION))
return FALSE;
priv = soup_websocket_extension_manager_get_instance_private (SOUP_WEBSOCKET_EXTENSION_MANAGER (feature));
extension_class = g_type_class_peek (type);
for (i = 0; i < priv->extension_types->len; i++) {
if (priv->extension_types->pdata[i] == (gpointer)extension_class) {
g_ptr_array_remove_index (priv->extension_types, i);
return TRUE;
}
}
return FALSE;
}
static gboolean
soup_websocket_extension_manager_has_feature (SoupSessionFeature *feature, GType type)
{
SoupWebsocketExtensionManagerPrivate *priv;
SoupWebsocketExtensionClass *extension_class;
guint i;
if (!g_type_is_a (type, SOUP_TYPE_WEBSOCKET_EXTENSION))
return FALSE;
priv = soup_websocket_extension_manager_get_instance_private (SOUP_WEBSOCKET_EXTENSION_MANAGER (feature));
extension_class = g_type_class_peek (type);
for (i = 0; i < priv->extension_types->len; i++) {
if (priv->extension_types->pdata[i] == (gpointer)extension_class)
return TRUE;
}
return FALSE;
}
static void
soup_websocket_extension_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface,
gpointer interface_data)
{
feature_interface->add_feature = soup_websocket_extension_manager_add_feature;
feature_interface->remove_feature = soup_websocket_extension_manager_remove_feature;
feature_interface->has_feature = soup_websocket_extension_manager_has_feature;
}
GPtrArray *
soup_websocket_extension_manager_get_supported_extensions (SoupWebsocketExtensionManager *manager)
{
SoupWebsocketExtensionManagerPrivate *priv;
g_return_val_if_fail (SOUP_IS_WEBSOCKET_EXTENSION_MANAGER (manager), NULL);
priv = soup_websocket_extension_manager_get_instance_private (manager);
return priv->extension_types;
}
@@ -0,0 +1,50 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* soup-websocket-extension-manager.h
*
* Copyright (C) 2019 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __SOUP_WEBSOCKET_EXTENSION_MANAGER_H__
#define __SOUP_WEBSOCKET_EXTENSION_MANAGER_H__ 1
#include <libsoup/soup-types.h>
G_BEGIN_DECLS
#define SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER (soup_websocket_extension_manager_get_type ())
#define SOUP_WEBSOCKET_EXTENSION_MANAGER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER, SoupWebsocketExtensionManager))
#define SOUP_IS_WEBSOCKET_EXTENSION_MANAGER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER))
#define SOUP_WEBSOCKET_EXTENSION_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER, SoupWebsocketExtensionManagerClass))
#define SOUP_IS_WEBSOCKET_EXTENSION_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER))
#define SOUP_WEBSOCKET_EXTENSION_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER, SoupWebsocketExtensionManagerClass))
typedef struct {
GObject parent;
} SoupWebsocketExtensionManager;
typedef struct {
GObjectClass parent_class;
} SoupWebsocketExtensionManagerClass;
SOUP_AVAILABLE_IN_2_68
GType soup_websocket_extension_manager_get_type (void);
G_END_DECLS
#endif /* __SOUP_WEBSOCKET_EXTENSION_MANAGER_H__ */
+221
View File
@@ -0,0 +1,221 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* soup-websocket-extension.c
*
* Copyright (C) 2019 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "soup-websocket-extension.h"
/**
* SECTION:soup-websocket-extension
* @short_description: a WebSocket extension
* @see_also: #SoupSession, #SoupWebsocketExtensionManager
*
* SoupWebsocketExtension is the base class for WebSocket extension objects.
*
* Since: 2.68
*/
/**
* SoupWebsocketExtensionClass:
* @parent_class: the parent class
* @configure: called to configure the extension with the given parameters
* @get_request_params: called by the client to build the request header.
* It should include the parameters string starting with ';'
* @get_response_params: called by the server to build the response header.
* It should include the parameters string starting with ';'
* @process_outgoing_message: called to process the payload data of a message
* before it's sent. Reserved bits of the header should be changed.
* @process_incoming_message: called to process the payload data of a message
* after it's received. Reserved bits of the header should be cleared.
*
* The class structure for the SoupWebsocketExtension.
*
* Since: 2.68
*/
G_DEFINE_ABSTRACT_TYPE (SoupWebsocketExtension, soup_websocket_extension, G_TYPE_OBJECT)
static void
soup_websocket_extension_init (SoupWebsocketExtension *extension)
{
}
static void
soup_websocket_extension_class_init (SoupWebsocketExtensionClass *auth_class)
{
}
/**
* soup_websocket_extension_configure:
* @extension: a #SoupWebsocketExtension
* @connection_type: either %SOUP_WEBSOCKET_CONNECTION_CLIENT or %SOUP_WEBSOCKET_CONNECTION_SERVER
* @params: (nullable): the parameters, or %NULL
* @error: return location for a #GError
*
* Configures @extension with the given @params
*
* Return value: %TRUE if extension could be configured with the given parameters, or %FALSE otherwise
*/
gboolean
soup_websocket_extension_configure (SoupWebsocketExtension *extension,
SoupWebsocketConnectionType connection_type,
GHashTable *params,
GError **error)
{
SoupWebsocketExtensionClass *klass;
g_return_val_if_fail (SOUP_IS_WEBSOCKET_EXTENSION (extension), FALSE);
g_return_val_if_fail (connection_type != SOUP_WEBSOCKET_CONNECTION_UNKNOWN, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
klass = SOUP_WEBSOCKET_EXTENSION_GET_CLASS (extension);
if (!klass->configure)
return TRUE;
return klass->configure (extension, connection_type, params, error);
}
/**
* soup_websocket_extension_get_request_params:
* @extension: a #SoupWebsocketExtension
*
* Get the parameters strings to be included in the request header. If the extension
* doesn't include any parameter in the request, this function returns %NULL.
*
* Returns: (nullable) (transfer full): a new allocated string with the parameters
*
* Since: 2.68
*/
char *
soup_websocket_extension_get_request_params (SoupWebsocketExtension *extension)
{
SoupWebsocketExtensionClass *klass;
g_return_val_if_fail (SOUP_IS_WEBSOCKET_EXTENSION (extension), NULL);
klass = SOUP_WEBSOCKET_EXTENSION_GET_CLASS (extension);
if (!klass->get_request_params)
return NULL;
return klass->get_request_params (extension);
}
/**
* soup_websocket_extension_get_response_params:
* @extension: a #SoupWebsocketExtension
*
* Get the parameters strings to be included in the response header. If the extension
* doesn't include any parameter in the response, this function returns %NULL.
*
* Returns: (nullable) (transfer full): a new allocated string with the parameters
*
* Since: 2.68
*/
char *
soup_websocket_extension_get_response_params (SoupWebsocketExtension *extension)
{
SoupWebsocketExtensionClass *klass;
g_return_val_if_fail (SOUP_IS_WEBSOCKET_EXTENSION (extension), NULL);
klass = SOUP_WEBSOCKET_EXTENSION_GET_CLASS (extension);
if (!klass->get_response_params)
return NULL;
return klass->get_response_params (extension);
}
/**
* soup_websocket_extension_process_outgoing_message:
* @extension: a #SoupWebsocketExtension
* @header: (inout): the message header
* @payload: (transfer full): the payload data
* @error: return location for a #GError
*
* Process a message before it's sent. If the payload isn't changed the given
* @payload is just returned, otherwise g_bytes_unref() is called on the given
* @payload and a new #GBytes is returned with the new data.
*
* Extensions using reserved bits of the header will change them in @header.
*
* Returns: (transfer full): the message payload data
*
* Since: 2.68
*/
GBytes *
soup_websocket_extension_process_outgoing_message (SoupWebsocketExtension *extension,
guint8 *header,
GBytes *payload,
GError **error)
{
SoupWebsocketExtensionClass *klass;
g_return_val_if_fail (SOUP_IS_WEBSOCKET_EXTENSION (extension), NULL);
g_return_val_if_fail (header != NULL, NULL);
g_return_val_if_fail (payload != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
klass = SOUP_WEBSOCKET_EXTENSION_GET_CLASS (extension);
if (!klass->process_outgoing_message)
return payload;
return klass->process_outgoing_message (extension, header, payload, error);
}
/**
* soup_websocket_extension_process_incoming_message:
* @extension: a #SoupWebsocketExtension
* @header: (inout): the message header
* @payload: (transfer full): the payload data
* @error: return location for a #GError
*
* Process a message after it's received. If the payload isn't changed the given
* @payload is just returned, otherwise g_bytes_unref() is called on the given
* @payload and a new #GBytes is returned with the new data.
*
* Extensions using reserved bits of the header will reset them in @header.
*
* Returns: (transfer full): the message payload data
*
* Since: 2.68
*/
GBytes *
soup_websocket_extension_process_incoming_message (SoupWebsocketExtension *extension,
guint8 *header,
GBytes *payload,
GError **error)
{
SoupWebsocketExtensionClass *klass;
g_return_val_if_fail (SOUP_IS_WEBSOCKET_EXTENSION (extension), NULL);
g_return_val_if_fail (header != NULL, NULL);
g_return_val_if_fail (payload != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
klass = SOUP_WEBSOCKET_EXTENSION_GET_CLASS (extension);
if (!klass->process_incoming_message)
return payload;
return klass->process_incoming_message (extension, header, payload, error);
}
+100
View File
@@ -0,0 +1,100 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* soup-websocket-extension.h
*
* Copyright (C) 2019 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __SOUP_WEBSOCKET_EXTENSION_H__
#define __SOUP_WEBSOCKET_EXTENSION_H__ 1
#include <libsoup/soup-types.h>
#include <libsoup/soup-websocket.h>
G_BEGIN_DECLS
#define SOUP_TYPE_WEBSOCKET_EXTENSION (soup_websocket_extension_get_type ())
#define SOUP_WEBSOCKET_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_WEBSOCKET_EXTENSION, SoupWebsocketExtension))
#define SOUP_IS_WEBSOCKET_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_WEBSOCKET_EXTENSION))
#define SOUP_WEBSOCKET_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_WEBSOCKET_EXTENSION, SoupWebsocketExtensionClass))
#define SOUP_IS_WEBSOCKET_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_WEBSOCKET_EXTENSION))
#define SOUP_WEBSOCKET_EXTENSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_WEBSOCKET_EXTENSION, SoupWebsocketExtensionClass))
struct _SoupWebsocketExtension {
GObject parent;
};
typedef struct {
GObjectClass parent_class;
const char *name;
gboolean (* configure) (SoupWebsocketExtension *extension,
SoupWebsocketConnectionType connection_type,
GHashTable *params,
GError **error);
char *(* get_request_params) (SoupWebsocketExtension *extension);
char *(* get_response_params) (SoupWebsocketExtension *extension);
GBytes *(* process_outgoing_message) (SoupWebsocketExtension *extension,
guint8 *header,
GBytes *payload,
GError **error);
GBytes *(* process_incoming_message) (SoupWebsocketExtension *extension,
guint8 *header,
GBytes *payload,
GError **error);
/* Padding for future expansion */
void (*_libsoup_reserved1) (void);
void (*_libsoup_reserved2) (void);
void (*_libsoup_reserved3) (void);
void (*_libsoup_reserved4) (void);
} SoupWebsocketExtensionClass;
SOUP_AVAILABLE_IN_2_68
GType soup_websocket_extension_get_type (void);
SOUP_AVAILABLE_IN_2_68
gboolean soup_websocket_extension_configure (SoupWebsocketExtension *extension,
SoupWebsocketConnectionType connection_type,
GHashTable *params,
GError **error);
SOUP_AVAILABLE_IN_2_68
char *soup_websocket_extension_get_request_params (SoupWebsocketExtension *extension);
SOUP_AVAILABLE_IN_2_68
char *soup_websocket_extension_get_response_params (SoupWebsocketExtension *extension);
SOUP_AVAILABLE_IN_2_68
GBytes *soup_websocket_extension_process_outgoing_message (SoupWebsocketExtension *extension,
guint8 *header,
GBytes *payload,
GError **error);
SOUP_AVAILABLE_IN_2_68
GBytes *soup_websocket_extension_process_incoming_message (SoupWebsocketExtension *extension,
guint8 *header,
GBytes *payload,
GError **error);
G_END_DECLS
#endif /* __SOUP_WEBSOCKET_EXTENSION_H__ */
+469 -7
View File
@@ -26,7 +26,8 @@
#include "soup-websocket.h"
#include "soup-headers.h"
#include "soup-message.h"
#include "soup-message-private.h"
#include "soup-websocket-extension.h"
#define FIXED_DIGEST_LEN 20
@@ -254,6 +255,9 @@ choose_subprotocol (SoupMessage *msg,
* handshake. The message body and non-WebSocket-related headers are
* not modified.
*
* Use soup_websocket_client_prepare_handshake_with_extensions() if you
* want to include "Sec-WebSocket-Extensions" header in the request.
*
* This is a low-level function; if you use
* soup_session_websocket_connect_async() to create a WebSocket
* connection, it will call this for you.
@@ -264,10 +268,41 @@ void
soup_websocket_client_prepare_handshake (SoupMessage *msg,
const char *origin,
char **protocols)
{
soup_websocket_client_prepare_handshake_with_extensions (msg, origin, protocols, NULL);
}
/**
* soup_websocket_client_prepare_handshake_with_extensions:
* @msg: a #SoupMessage
* @origin: (nullable): the "Origin" header to set
* @protocols: (nullable) (array zero-terminated=1): list of
* protocols to offer
* @supported_extensions: (nullable) (element-type GObject.TypeClass): list
* of supported extension types
*
* Adds the necessary headers to @msg to request a WebSocket
* handshake including supported WebSocket extensions.
* The message body and non-WebSocket-related headers are
* not modified.
*
* This is a low-level function; if you use
* soup_session_websocket_connect_async() to create a WebSocket
* connection, it will call this for you.
*
* Since: 2.68
*/
void
soup_websocket_client_prepare_handshake_with_extensions (SoupMessage *msg,
const char *origin,
char **protocols,
GPtrArray *supported_extensions)
{
guint32 raw[4];
char *key;
g_return_if_fail (SOUP_IS_MESSAGE (msg));
soup_message_headers_replace (msg->request_headers, "Upgrade", "websocket");
soup_message_headers_append (msg->request_headers, "Connection", "Upgrade");
@@ -292,6 +327,47 @@ soup_websocket_client_prepare_handshake (SoupMessage *msg,
"Sec-WebSocket-Protocol", protocols_str);
g_free (protocols_str);
}
if (supported_extensions && supported_extensions->len > 0) {
guint i;
GString *extensions;
extensions = g_string_new (NULL);
for (i = 0; i < supported_extensions->len; i++) {
SoupWebsocketExtensionClass *extension_class = (SoupWebsocketExtensionClass *)supported_extensions->pdata[i];
if (soup_message_disables_feature_by_type (msg, G_TYPE_FROM_CLASS (extension_class)))
continue;
if (i != 0)
extensions = g_string_append (extensions, ", ");
extensions = g_string_append (extensions, extension_class->name);
if (extension_class->get_request_params) {
SoupWebsocketExtension *websocket_extension;
gchar *params;
websocket_extension = g_object_new (G_TYPE_FROM_CLASS (extension_class), NULL);
params = soup_websocket_extension_get_request_params (websocket_extension);
if (params) {
extensions = g_string_append (extensions, params);
g_free (params);
}
g_object_unref (websocket_extension);
}
}
if (extensions->len > 0) {
soup_message_headers_replace (msg->request_headers,
"Sec-WebSocket-Extensions",
extensions->str);
} else {
soup_message_headers_remove (msg->request_headers,
"Sec-WebSocket-Extensions");
}
g_string_free (extensions, TRUE);
}
}
/**
@@ -310,6 +386,12 @@ soup_websocket_client_prepare_handshake (SoupMessage *msg,
* only requests containing a compatible "Sec-WebSocket-Protocols"
* header will be accepted.
*
* Requests containing "Sec-WebSocket-Extensions" header will be
* accepted even if the header is not valid. To check a request
* with extensions you need to use
* soup_websocket_server_check_handshake_with_extensions() and provide
* the list of supported extension types.
*
* Normally soup_websocket_server_process_handshake() will take care
* of this for you, and if you use soup_server_add_websocket_handler()
* to handle accepting WebSocket connections, it will call that for
@@ -327,9 +409,247 @@ soup_websocket_server_check_handshake (SoupMessage *msg,
const char *expected_origin,
char **protocols,
GError **error)
{
return soup_websocket_server_check_handshake_with_extensions (msg, expected_origin, protocols, NULL, error);
}
static gboolean
websocket_extension_class_equal (gconstpointer a,
gconstpointer b)
{
return g_str_equal (((const SoupWebsocketExtensionClass *)a)->name, (const char *)b);
}
static GHashTable *
extract_extension_names_from_request (SoupMessage *msg)
{
const char *extensions;
GSList *extension_list, *l;
GHashTable *return_value = NULL;
extensions = soup_message_headers_get_list (msg->request_headers, "Sec-WebSocket-Extensions");
if (!extensions || !*extensions)
return NULL;
extension_list = soup_header_parse_list (extensions);
for (l = extension_list; l != NULL; l = g_slist_next (l)) {
char *extension = (char *)l->data;
char *p, *end;
while (g_ascii_isspace (*extension))
extension++;
if (!*extension)
continue;
p = strstr (extension, ";");
end = p ? p : extension + strlen (extension);
while (end > extension && g_ascii_isspace (*(end - 1)))
end--;
*end = '\0';
if (!return_value)
return_value = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
g_hash_table_add (return_value, g_strdup (extension));
}
soup_header_free_list (extension_list);
return return_value;
}
static gboolean
process_extensions (SoupMessage *msg,
const char *extensions,
gboolean is_server,
GPtrArray *supported_extensions,
GList **accepted_extensions,
GError **error)
{
GSList *extension_list, *l;
GHashTable *requested_extensions = NULL;
if (!supported_extensions || supported_extensions->len == 0) {
if (is_server)
return TRUE;
g_set_error_literal (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
_("Server requested unsupported extension"));
return FALSE;
}
if (!is_server)
requested_extensions = extract_extension_names_from_request (msg);
extension_list = soup_header_parse_list (extensions);
for (l = extension_list; l != NULL; l = g_slist_next (l)) {
char *extension = (char *)l->data;
char *p, *end;
guint index;
GHashTable *params = NULL;
SoupWebsocketExtension *websocket_extension;
while (g_ascii_isspace (*extension))
extension++;
if (!*extension) {
g_set_error (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
is_server ?
_("Incorrect WebSocket “%s” header") :
_("Server returned incorrect “%s” key"),
"Sec-WebSocket-Extensions");
if (accepted_extensions)
g_list_free_full (*accepted_extensions, g_object_unref);
g_clear_pointer (&requested_extensions, g_hash_table_destroy);
soup_header_free_list (extension_list);
return FALSE;
}
p = strstr (extension, ";");
end = p ? p : extension + strlen (extension);
while (end > extension && g_ascii_isspace (*(end - 1)))
end--;
*end = '\0';
if (requested_extensions && !g_hash_table_contains (requested_extensions, extension)) {
g_set_error_literal (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
_("Server requested unsupported extension"));
if (accepted_extensions)
g_list_free_full (*accepted_extensions, g_object_unref);
g_clear_pointer (&requested_extensions, g_hash_table_destroy);
soup_header_free_list (extension_list);
return FALSE;
}
if (!g_ptr_array_find_with_equal_func (supported_extensions, extension, websocket_extension_class_equal, &index)) {
if (is_server)
continue;
g_set_error_literal (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
_("Server requested unsupported extension"));
if (accepted_extensions)
g_list_free_full (*accepted_extensions, g_object_unref);
g_clear_pointer (&requested_extensions, g_hash_table_destroy);
soup_header_free_list (extension_list);
return FALSE;
}
/* If we are just checking headers in server side
* and there's no parameters, it's enough to know
* the extension is supported.
*/
if (is_server && !accepted_extensions && !p)
continue;
websocket_extension = g_object_new (G_TYPE_FROM_CLASS (supported_extensions->pdata[index]), NULL);
if (accepted_extensions)
*accepted_extensions = g_list_prepend (*accepted_extensions, websocket_extension);
if (p) {
params = soup_header_parse_semi_param_list_strict (p + 1);
if (!params) {
g_set_error (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
is_server ?
_("Duplicated parameter in “%s” WebSocket extension header") :
_("Server returned a duplicated parameter in “%s” WebSocket extension header"),
extension);
if (accepted_extensions)
g_list_free_full (*accepted_extensions, g_object_unref);
else
g_object_unref (websocket_extension);
g_clear_pointer (&requested_extensions, g_hash_table_destroy);
soup_header_free_list (extension_list);
return FALSE;
}
}
if (!soup_websocket_extension_configure (websocket_extension,
is_server ? SOUP_WEBSOCKET_CONNECTION_SERVER : SOUP_WEBSOCKET_CONNECTION_CLIENT,
params,
error)) {
g_clear_pointer (&params, g_hash_table_destroy);
if (accepted_extensions)
g_list_free_full (*accepted_extensions, g_object_unref);
else
g_object_unref (websocket_extension);
g_clear_pointer (&requested_extensions, g_hash_table_destroy);
soup_header_free_list (extension_list);
return FALSE;
}
g_clear_pointer (&params, g_hash_table_destroy);
if (!accepted_extensions)
g_object_unref (websocket_extension);
}
soup_header_free_list (extension_list);
g_clear_pointer (&requested_extensions, g_hash_table_destroy);
if (accepted_extensions)
*accepted_extensions = g_list_reverse (*accepted_extensions);
return TRUE;
}
/**
* soup_websocket_server_check_handshake_with_extensions:
* @msg: #SoupMessage containing the client side of a WebSocket handshake
* @origin: (nullable): expected Origin header
* @protocols: (nullable) (array zero-terminated=1): allowed WebSocket
* protocols.
* @supported_extensions: (nullable) (element-type GObject.TypeClass): list
* of supported extension types
* @error: return location for a #GError
*
* Examines the method and request headers in @msg and determines
* whether @msg contains a valid handshake request.
*
* If @origin is non-%NULL, then only requests containing a matching
* "Origin" header will be accepted. If @protocols is non-%NULL, then
* only requests containing a compatible "Sec-WebSocket-Protocols"
* header will be accepted. If @supported_extensions is non-%NULL, then
* only requests containing valid supported extensions in
* "Sec-WebSocket-Extensions" header will be accepted.
*
* Normally soup_websocket_server_process_handshake_with_extensioins()
* will take care of this for you, and if you use
* soup_server_add_websocket_handler() to handle accepting WebSocket
* connections, it will call that for you. However, this function may
* be useful if you need to perform more complicated validation; eg,
* accepting multiple different Origins, or handling different protocols
* depending on the path.
*
* Returns: %TRUE if @msg contained a valid WebSocket handshake,
* %FALSE and an error if not.
*
* Since: 2.68
*/
gboolean
soup_websocket_server_check_handshake_with_extensions (SoupMessage *msg,
const char *expected_origin,
char **protocols,
GPtrArray *supported_extensions,
GError **error)
{
const char *origin;
const char *key;
const char *extensions;
g_return_val_if_fail (SOUP_IS_MESSAGE (msg), FALSE);
if (msg->method != SOUP_METHOD_GET) {
g_set_error_literal (error,
@@ -384,6 +704,12 @@ soup_websocket_server_check_handshake (SoupMessage *msg,
return FALSE;
}
extensions = soup_message_headers_get_list (msg->request_headers, "Sec-WebSocket-Extensions");
if (extensions && *extensions) {
if (!process_extensions (msg, extensions, TRUE, supported_extensions, NULL, error))
return FALSE;
}
return TRUE;
}
@@ -430,6 +756,12 @@ respond_handshake_bad (SoupMessage *msg, const char *why)
* only requests containing a compatible "Sec-WebSocket-Protocols"
* header will be accepted.
*
* Requests containing "Sec-WebSocket-Extensions" header will be
* accepted even if the header is not valid. To process a request
* with extensions you need to use
* soup_websocket_server_process_handshake_with_extensions() and provide
* the list of supported extension types.
*
* This is a low-level function; if you use
* soup_server_add_websocket_handler() to handle accepting WebSocket
* connections, it will call this for you.
@@ -443,13 +775,58 @@ gboolean
soup_websocket_server_process_handshake (SoupMessage *msg,
const char *expected_origin,
char **protocols)
{
return soup_websocket_server_process_handshake_with_extensions (msg, expected_origin, protocols, NULL, NULL);
}
/**
* soup_websocket_server_process_handshake_with_extensions:
* @msg: #SoupMessage containing the client side of a WebSocket handshake
* @expected_origin: (nullable): expected Origin header
* @protocols: (nullable) (array zero-terminated=1): allowed WebSocket
* protocols.
* @supported_extensions: (nullable) (element-type GObject.TypeClass): list
* of supported extension types
* @accepted_extensions: (out) (optional) (element-type SoupWebsocketExtension): a
* #GList of #SoupWebsocketExtension objects
*
* Examines the method and request headers in @msg and (assuming @msg
* contains a valid handshake request), fills in the handshake
* response.
*
* If @expected_origin is non-%NULL, then only requests containing a matching
* "Origin" header will be accepted. If @protocols is non-%NULL, then
* only requests containing a compatible "Sec-WebSocket-Protocols"
* header will be accepted. If @supported_extensions is non-%NULL, then
* only requests containing valid supported extensions in
* "Sec-WebSocket-Extensions" header will be accepted. The accepted extensions
* will be returned in @accepted_extensions parameter if non-%NULL.
*
* This is a low-level function; if you use
* soup_server_add_websocket_handler() to handle accepting WebSocket
* connections, it will call this for you.
*
* Returns: %TRUE if @msg contained a valid WebSocket handshake
* request and was updated to contain a handshake response. %FALSE if not.
*
* Since: 2.68
*/
gboolean
soup_websocket_server_process_handshake_with_extensions (SoupMessage *msg,
const char *expected_origin,
char **protocols,
GPtrArray *supported_extensions,
GList **accepted_extensions)
{
const char *chosen_protocol = NULL;
const char *key;
const char *extensions;
char *accept_key;
GError *error = NULL;
if (!soup_websocket_server_check_handshake (msg, expected_origin, protocols, &error)) {
g_return_val_if_fail (accepted_extensions == NULL || *accepted_extensions == NULL, FALSE);
if (!soup_websocket_server_check_handshake_with_extensions (msg, expected_origin, protocols, supported_extensions, &error)) {
if (g_error_matches (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_ERROR_BAD_ORIGIN))
@@ -473,6 +850,49 @@ soup_websocket_server_process_handshake (SoupMessage *msg,
if (chosen_protocol)
soup_message_headers_append (msg->response_headers, "Sec-WebSocket-Protocol", chosen_protocol);
extensions = soup_message_headers_get_list (msg->request_headers, "Sec-WebSocket-Extensions");
if (extensions && *extensions) {
GList *websocket_extensions = NULL;
GList *l;
process_extensions (msg, extensions, TRUE, supported_extensions, &websocket_extensions, NULL);
if (websocket_extensions) {
GString *response_extensions;
response_extensions = g_string_new (NULL);
for (l = websocket_extensions; l && l->data; l = g_list_next (l)) {
SoupWebsocketExtension *websocket_extension;
gchar *params;
websocket_extension = (SoupWebsocketExtension *)l->data;
if (response_extensions->len > 0)
response_extensions = g_string_append (response_extensions, ", ");
response_extensions = g_string_append (response_extensions, SOUP_WEBSOCKET_EXTENSION_GET_CLASS (websocket_extension)->name);
params = soup_websocket_extension_get_response_params (websocket_extension);
if (params) {
response_extensions = g_string_append (response_extensions, params);
g_free (params);
}
}
if (response_extensions->len > 0) {
soup_message_headers_replace (msg->response_headers,
"Sec-WebSocket-Extensions",
response_extensions->str);
} else {
soup_message_headers_remove (msg->response_headers,
"Sec-WebSocket-Extensions");
}
g_string_free (response_extensions, TRUE);
if (accepted_extensions)
*accepted_extensions = websocket_extensions;
else
g_list_free_full (websocket_extensions, g_object_unref);
}
}
return TRUE;
}
@@ -486,6 +906,11 @@ soup_websocket_server_process_handshake (SoupMessage *msg,
* determines if they contain a valid WebSocket handshake response
* (given the handshake request in @msg's request headers).
*
* If the response contains the "Sec-WebSocket-Extensions" header,
* the handshake will be considered invalid. You need to use
* soup_websocket_client_verify_handshake_with_extensions() to handle
* responses with extensions.
*
* This is a low-level function; if you use
* soup_session_websocket_connect_async() to create a WebSocket
* connection, it will call this for you.
@@ -498,11 +923,51 @@ soup_websocket_server_process_handshake (SoupMessage *msg,
gboolean
soup_websocket_client_verify_handshake (SoupMessage *msg,
GError **error)
{
return soup_websocket_client_verify_handshake_with_extensions (msg, NULL, NULL, error);
}
/**
* soup_websocket_client_verify_handshake_with_extensions:
* @msg: #SoupMessage containing both client and server sides of a
* WebSocket handshake
* @supported_extensions: (nullable) (element-type GObject.TypeClass): list
* of supported extension types
* @accepted_extensions: (out) (optional) (element-type SoupWebsocketExtension): a
* #GList of #SoupWebsocketExtension objects
* @error: return location for a #GError
*
* Looks at the response status code and headers in @msg and
* determines if they contain a valid WebSocket handshake response
* (given the handshake request in @msg's request headers).
*
* If @supported_extensions is non-%NULL, extensions included in the
* response "Sec-WebSocket-Extensions" are verified too. Accepted
* extensions are returned in @accepted_extensions parameter if non-%NULL.
*
* This is a low-level function; if you use
* soup_session_websocket_connect_async() to create a WebSocket
* connection, it will call this for you.
*
* Returns: %TRUE if @msg contains a completed valid WebSocket
* handshake, %FALSE and an error if not.
*
* Since: 2.68
*/
gboolean
soup_websocket_client_verify_handshake_with_extensions (SoupMessage *msg,
GPtrArray *supported_extensions,
GList **accepted_extensions,
GError **error)
{
const char *protocol, *request_protocols, *extensions, *accept_key;
char *expected_accept_key;
gboolean key_ok;
g_return_val_if_fail (SOUP_IS_MESSAGE (msg), FALSE);
g_return_val_if_fail (accepted_extensions == NULL || *accepted_extensions == NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (msg->status_code == SOUP_STATUS_BAD_REQUEST) {
g_set_error_literal (error,
SOUP_WEBSOCKET_ERROR,
@@ -543,11 +1008,8 @@ soup_websocket_client_verify_handshake (SoupMessage *msg,
extensions = soup_message_headers_get_list (msg->response_headers, "Sec-WebSocket-Extensions");
if (extensions && *extensions) {
g_set_error_literal (error,
SOUP_WEBSOCKET_ERROR,
SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
_("Server requested unsupported extension"));
return FALSE;
if (!process_extensions (msg, extensions, FALSE, supported_extensions, accepted_extensions, error))
return FALSE;
}
accept_key = soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Accept");
+24
View File
@@ -72,21 +72,45 @@ SOUP_AVAILABLE_IN_2_50
void soup_websocket_client_prepare_handshake (SoupMessage *msg,
const char *origin,
char **protocols);
SOUP_AVAILABLE_IN_2_68
void soup_websocket_client_prepare_handshake_with_extensions (SoupMessage *msg,
const char *origin,
char **protocols,
GPtrArray *supported_extensions);
SOUP_AVAILABLE_IN_2_50
gboolean soup_websocket_client_verify_handshake (SoupMessage *msg,
GError **error);
SOUP_AVAILABLE_IN_2_68
gboolean soup_websocket_client_verify_handshake_with_extensions (SoupMessage *msg,
GPtrArray *supported_extensions,
GList **accepted_extensions,
GError **error);
SOUP_AVAILABLE_IN_2_50
gboolean soup_websocket_server_check_handshake (SoupMessage *msg,
const char *origin,
char **protocols,
GError **error);
SOUP_AVAILABLE_IN_2_68
gboolean
soup_websocket_server_check_handshake_with_extensions (SoupMessage *msg,
const char *origin,
char **protocols,
GPtrArray *supported_extensions,
GError **error);
SOUP_AVAILABLE_IN_2_50
gboolean soup_websocket_server_process_handshake (SoupMessage *msg,
const char *expected_origin,
char **protocols);
SOUP_AVAILABLE_IN_2_68
gboolean
soup_websocket_server_process_handshake_with_extensions (SoupMessage *msg,
const char *expected_origin,
char **protocols,
GPtrArray *supported_extensions,
GList **accepted_extensions);
G_END_DECLS
+3
View File
@@ -58,6 +58,9 @@ extern "C" {
#include <libsoup/soup-version.h>
#include <libsoup/soup-websocket.h>
#include <libsoup/soup-websocket-connection.h>
#include <libsoup/soup-websocket-extension.h>
#include <libsoup/soup-websocket-extension-deflate.h>
#include <libsoup/soup-websocket-extension-manager.h>
#include <libsoup/soup-xmlrpc.h>
#include <libsoup/soup-xmlrpc-old.h>
+15
View File
@@ -159,6 +159,21 @@ if enable_tls_check
endif
endif
libz_dep = dependency('zlib', required : false)
if not libz_dep.found()
if cc.get_id() != 'msvc'
libz_dep = cc.find_library('z', required : false)
else
libz_dep = cc.find_library('zlib1', required : false)
if not libz_dep.found()
libz_dep = cc.find_library('zlib', required : false)
endif
endif
if not libz_dep.found() or not cc.has_header('zlib.h')
libz_dep = subproject('zlib').get_variable('zlib_dep')
endif
endif
#################################
# Regression tests dependencies #
#################################
+10
View File
@@ -0,0 +1,10 @@
[wrap-file]
directory = zlib-1.2.11
source_url = https://zlib.net/fossils/zlib-1.2.11.tar.gz
source_filename = zlib-1.2.11.tar.gz
source_hash = c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1
patch_url = https://wrapdb.mesonbuild.com/v1/projects/zlib/1.2.11/3/get_zip
patch_filename = zlib-1.2.11-3-wrap.zip
patch_hash = f07dc491ab3d05daf00632a0591e2ae61b470615b5b73bcf9b3f061fff65cff0
+43 -42
View File
@@ -12,53 +12,53 @@ test_resources = gnome.compile_resources('soup-tests',
'soup-tests.gresource.xml',
gresource_bundle : true)
# ['name', is_parallel]
# ['name', is_parallel, extra_deps]
tests = [
['cache', true],
['chunk', true],
['chunk-io', true],
['coding', true],
['context', true],
['continue', true],
['cookies', true],
['date', true],
['forms', true],
['header-parsing', true],
['hsts', true],
['hsts-db', true],
['misc', true],
['multipart', true],
['no-ssl', true],
['ntlm', true],
['redirect', true],
['requester', true],
['resource', true],
['session', true],
['server-auth', true],
['server', true],
['sniffing', true],
['socket', true],
['ssl', true],
['streaming', true],
['timeout', true],
['tld', true],
['uri-parsing', true],
['websocket', true]
['cache', true, []],
['chunk', true, []],
['chunk-io', true, []],
['coding', true, []],
['context', true, []],
['continue', true, []],
['cookies', true, []],
['date', true, []],
['forms', true, []],
['header-parsing', true, []],
['hsts', true, []],
['hsts-db', true, []],
['misc', true, []],
['multipart', true, []],
['no-ssl', true, []],
['ntlm', true, []],
['redirect', true, []],
['requester', true, []],
['resource', true, []],
['session', true, []],
['server-auth', true, []],
['server', true, []],
['sniffing', true, []],
['socket', true, []],
['ssl', true, []],
['streaming', true, []],
['timeout', true, []],
['tld', true, []],
['uri-parsing', true, []],
['websocket', true, [libz_dep]]
]
if brotlidec_dep.found()
tests += [
['brotli-decompressor', true],
['brotli-decompressor', true, []],
]
endif
if have_apache
tests += [
['auth', false],
['connection', false],
['range', false],
['proxy', false],
['pull-api', false],
['auth', false, []],
['connection', false, []],
['range', false, []],
['proxy', false, []],
['pull-api', false, []],
]
configure_file(output : 'httpd.conf',
@@ -90,10 +90,10 @@ endif
if have_php_xmlrpc
tests += [
['xmlrpc-old-server', true, have_php_xmlrpc],
['xmlrpc-old', false, have_php_xmlrpc],
['xmlrpc-server', true, have_php_xmlrpc],
['xmlrpc', false, have_php_xmlrpc]
['xmlrpc-old-server', true, []],
['xmlrpc-old', false, []],
['xmlrpc-server', true, []],
['xmlrpc', false, []]
]
configure_file(input : 'xmlrpc-server.php',
@@ -113,10 +113,11 @@ env.set('MALLOC_PERTURB_', '')
foreach test: tests
test_name = '@0@-test'.format(test[0])
test_deps = [ libsoup_dep ] + test[2]
test_target = executable(test_name,
sources : [ test_name + '.c', test_resources ],
link_with : test_utils,
dependencies : libsoup_dep)
dependencies : test_deps)
# Increase the timeout as on some architectures the tests could be slower
# than the default 30 seconds.
test(test_name, test_target, env : env, is_parallel : test[1], timeout : 60)
+547 -9
View File
@@ -20,6 +20,8 @@
#include "test-utils.h"
#include <zlib.h>
typedef struct {
GSocket *listener;
gushort port;
@@ -35,6 +37,9 @@ typedef struct {
gboolean no_server;
GIOStream *raw_server;
gboolean enable_extensions;
gboolean disable_deflate_in_message;
GMutex mutex;
} Test;
@@ -100,15 +105,26 @@ direct_connection_complete (GObject *object,
GSocketConnection *conn;
SoupURI *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 = soup_uri_new ("http://127.0.0.1/");
test->client = soup_websocket_connection_new (G_IO_STREAM (conn), uri,
SOUP_WEBSOCKET_CONNECTION_CLIENT,
NULL, 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_with_extensions (G_IO_STREAM (conn), uri,
SOUP_WEBSOCKET_CONNECTION_CLIENT,
NULL, NULL,
extensions);
soup_uri_free (uri);
g_object_unref (conn);
}
@@ -122,6 +138,7 @@ got_connection (GSocket *listener,
GSocket *sock;
GSocketConnection *conn;
SoupURI *uri;
GList *extensions = NULL;
GError *error = NULL;
sock = g_socket_accept (listener, NULL, &error);
@@ -135,9 +152,19 @@ got_connection (GSocket *listener,
test->raw_server = G_IO_STREAM (conn);
else {
uri = soup_uri_new ("http://127.0.0.1/");
test->server = soup_websocket_connection_new (G_IO_STREAM (conn), uri,
SOUP_WEBSOCKET_CONNECTION_SERVER,
NULL, 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_with_extensions (G_IO_STREAM (conn), uri,
SOUP_WEBSOCKET_CONNECTION_SERVER,
NULL, NULL,
extensions);
soup_uri_free (uri);
g_object_unref (conn);
}
@@ -170,6 +197,14 @@ setup_direct_connection (Test *test,
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)
@@ -178,6 +213,14 @@ setup_half_direct_connection (Test *test,
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)
@@ -200,6 +243,8 @@ setup_soup_server (Test *test,
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);
@@ -217,11 +262,14 @@ client_connect (Test *test,
{
char *url;
if (!test->session)
test->session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
test->session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
if (test->enable_extensions)
soup_session_add_feature_by_type (test->session, SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER);
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,
@@ -263,6 +311,14 @@ setup_soup_connection (Test *test,
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)
@@ -323,7 +379,27 @@ 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
@@ -1040,6 +1116,78 @@ send_fragments_server_thread (gpointer user_data)
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);
return NULL;
}
static void
test_receive_fragmented (Test *test,
gconstpointer data)
@@ -1048,7 +1196,11 @@ test_receive_fragmented (Test *test,
GBytes *received = NULL;
GBytes *expect;
thread = g_thread_new ("fragment-thread", send_fragments_server_thread, test);
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);
@@ -1281,6 +1433,331 @@ test_client_context (Test *test,
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"
},
{ "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;
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);
soup_message_headers_append (msg->request_headers, "Sec-WebSocket-Extensions", deflate_negotiate_tests[i].client_extension);
result = soup_websocket_server_check_handshake_with_extensions (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_with_extensions (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);
if (deflate_negotiate_tests[i].expected_accepted_extension) {
const char *extension;
extension = soup_message_headers_get_one (msg->response_headers, "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_with_extensions (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_ptr_array_unref (supported_extensions);
}
static void
test_deflate_disabled_in_message_direct (Test *test,
gconstpointer unused)
{
SoupMessage *msg;
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_with_extensions (msg, NULL, NULL, supported_extensions);
g_assert_cmpstr (soup_message_headers_get_one (msg->request_headers, "Sec-WebSocket-Extensions"), ==, NULL);
g_assert_true (soup_websocket_server_check_handshake_with_extensions (msg, NULL, NULL, supported_extensions, &error));
g_assert_no_error (error);
g_assert_true (soup_websocket_server_process_handshake_with_extensions (msg, NULL, NULL, supported_extensions, &accepted_extensions));
g_assert_null (accepted_extensions);
g_assert_cmpstr (soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Extensions"), ==, NULL);
g_assert_true (soup_websocket_client_verify_handshake_with_extensions (msg, supported_extensions, &accepted_extensions, &error));
g_assert_no_error (error);
g_assert_null (accepted_extensions);
g_object_unref (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 (test->msg->request_headers, "Sec-WebSocket-Extensions"), ==, NULL);
g_assert_cmpstr (soup_message_headers_get_one (test->msg->response_headers, "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);
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);
}
int
main (int argc,
char *argv[])
@@ -1430,6 +1907,67 @@ main (int argc,
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,