[libsoup/carlosgc/permessage-deflate: 3/3] WebSockets: add support for permessage-deflate extension



commit e2746794c6311cc47d975fc20a4ac82aeaf431cb
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Thu Jul 4 09:31:39 2019 +0200

    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.

 docs/reference/libsoup-2.4-sections.txt            |  44 ++
 docs/reference/meson.build                         |   1 +
 libsoup/meson.build                                |   8 +
 libsoup/soup-message-private.h                     |   2 +
 libsoup/soup-message.c                             |  17 +
 libsoup/soup-server.c                              | 157 +++++-
 libsoup/soup-server.h                              |   9 +
 libsoup/soup-session.c                             |  45 +-
 libsoup/soup-types.h                               |   2 +-
 libsoup/soup-websocket-connection.c                | 164 +++++-
 libsoup/soup-websocket-connection.h                |  10 +
 libsoup/soup-websocket-extension-deflate.c         | 503 +++++++++++++++++++
 libsoup/soup-websocket-extension-deflate.h         |  49 ++
 libsoup/soup-websocket-extension-manager-private.h |  30 ++
 libsoup/soup-websocket-extension-manager.c         | 187 +++++++
 libsoup/soup-websocket-extension-manager.h         |  50 ++
 libsoup/soup-websocket-extension.c                 | 223 +++++++++
 libsoup/soup-websocket-extension.h                 | 100 ++++
 libsoup/soup-websocket.c                           | 476 +++++++++++++++++-
 libsoup/soup-websocket.h                           |  24 +
 libsoup/soup.h                                     |   3 +
 meson.build                                        |  15 +
 subprojects/zlib.wrap                              |  10 +
 tests/meson.build                                  |  85 ++--
 tests/websocket-test.c                             | 555 ++++++++++++++++++++-
 25 files changed, 2671 insertions(+), 98 deletions(-)
---
diff --git a/docs/reference/libsoup-2.4-sections.txt b/docs/reference/libsoup-2.4-sections.txt
index ec95d20e..b36a16e9 100644
--- a/docs/reference/libsoup-2.4-sections.txt
+++ b/docs/reference/libsoup-2.4-sections.txt
@@ -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
diff --git a/docs/reference/meson.build b/docs/reference/meson.build
index 86c5bda9..89413822 100644
--- a/docs/reference/meson.build
+++ b/docs/reference/meson.build
@@ -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'
 ]
 
diff --git a/libsoup/meson.build b/libsoup/meson.build
index 845e233f..73bb1188 100644
--- a/libsoup/meson.build
+++ b/libsoup/meson.build
@@ -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),
diff --git a/libsoup/soup-message-private.h b/libsoup/soup-message-private.h
index dcca1603..dd345bd1 100644
--- a/libsoup/soup-message-private.h
+++ b/libsoup/soup-message-private.h
@@ -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);
 
diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c
index da6a3716..f61f58c2 100644
--- a/libsoup/soup-message.c
+++ b/libsoup/soup-message.c
@@ -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)
 {
diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c
index 58d9574f..0bb30626 100644
--- a/libsoup/soup-server.c
+++ b/libsoup/soup-server.c
@@ -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;
+                }
+        }
+}
diff --git a/libsoup/soup-server.h b/libsoup/soup-server.h
index f04e9eb1..1dc6cafe 100644
--- a/libsoup/soup-server.h
+++ b/libsoup/soup-server.h
@@ -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,
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index 5ecae857..465cb469 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -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,
diff --git a/libsoup/soup-types.h b/libsoup/soup-types.h
index ed593390..9e3d5788 100644
--- a/libsoup/soup-types.h
+++ b/libsoup/soup-types.h
@@ -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;
diff --git a/libsoup/soup-websocket-connection.c b/libsoup/soup-websocket-connection.c
index 32404e3b..50e67fd6 100644
--- a/libsoup/soup-websocket-connection.c
+++ b/libsoup/soup-websocket-connection.c
@@ -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 g_object_new (SOUP_TYPE_WEBSOCKET_CONNECTION,
-                            "io-stream", stream,
-                            "uri", uri,
-                            "connection-type", type,
-                            "origin", origin,
-                            "protocol", protocol,
-                            NULL);
+       return soup_websocket_connection_new_with_extensions (stream, uri, type, origin, 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
diff --git a/libsoup/soup-websocket-connection.h b/libsoup/soup-websocket-connection.h
index d761c424..f82d723a 100644
--- a/libsoup/soup-websocket-connection.h
+++ b/libsoup/soup-websocket-connection.h
@@ -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);
 
diff --git a/libsoup/soup-websocket-extension-deflate.c b/libsoup/soup-websocket-extension-deflate.c
new file mode 100644
index 00000000..01605af6
--- /dev/null
+++ b/libsoup/soup-websocket-extension-deflate.c
@@ -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).
+ *
+ * It's 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;
+}
diff --git a/libsoup/soup-websocket-extension-deflate.h b/libsoup/soup-websocket-extension-deflate.h
new file mode 100644
index 00000000..e353965d
--- /dev/null
+++ b/libsoup/soup-websocket-extension-deflate.h
@@ -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__ */
diff --git a/libsoup/soup-websocket-extension-manager-private.h 
b/libsoup/soup-websocket-extension-manager-private.h
new file mode 100644
index 00000000..b7ff618d
--- /dev/null
+++ b/libsoup/soup-websocket-extension-manager-private.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__ */
diff --git a/libsoup/soup-websocket-extension-manager.c b/libsoup/soup-websocket-extension-manager.c
new file mode 100644
index 00000000..b647fe23
--- /dev/null
+++ b/libsoup/soup-websocket-extension-manager.c
@@ -0,0 +1,187 @@
+/* -*- 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);
+
+#ifdef HAVE_ZLIB
+        /* Use permessage-deflate extension by default */
+        soup_session_feature_add_feature (SOUP_SESSION_FEATURE (manager), 
SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE);
+#endif
+}
+
+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;
+
+#ifndef HAVE_ZLIB
+        if (g_type_is_a (type, SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE))
+                return FALSE;
+#endif
+
+        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;
+}
diff --git a/libsoup/soup-websocket-extension-manager.h b/libsoup/soup-websocket-extension-manager.h
new file mode 100644
index 00000000..280a5b39
--- /dev/null
+++ b/libsoup/soup-websocket-extension-manager.h
@@ -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__ */
diff --git a/libsoup/soup-websocket-extension.c b/libsoup/soup-websocket-extension.c
new file mode 100644
index 00000000..781e4427
--- /dev/null
+++ b/libsoup/soup-websocket-extension.c
@@ -0,0 +1,223 @@
+/* -*- 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
+ * @process_incoming_message: called to process the payload data of a message
+ *    after it's received
+ *
+ * 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. It should start
+ * with ';' since this will be appended to the extension name. 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. It should start
+ * with ';' since this will be appended to the extension name. 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 should 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);
+}
diff --git a/libsoup/soup-websocket-extension.h b/libsoup/soup-websocket-extension.h
new file mode 100644
index 00000000..4461cfa7
--- /dev/null
+++ b/libsoup/soup-websocket-extension.h
@@ -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__ */
diff --git a/libsoup/soup-websocket.c b/libsoup/soup-websocket.c
index 5038041d..d7011b49 100644
--- a/libsoup/soup-websocket.c
+++ b/libsoup/soup-websocket.c
@@ -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");
diff --git a/libsoup/soup-websocket.h b/libsoup/soup-websocket.h
index 20584982..c5dd31ab 100644
--- a/libsoup/soup-websocket.h
+++ b/libsoup/soup-websocket.h
@@ -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
 
diff --git a/libsoup/soup.h b/libsoup/soup.h
index 46ca6acf..48b75f0c 100644
--- a/libsoup/soup.h
+++ b/libsoup/soup.h
@@ -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>
 
diff --git a/meson.build b/meson.build
index 93a6b860..02c20ee5 100644
--- a/meson.build
+++ b/meson.build
@@ -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 #
 #################################
diff --git a/subprojects/zlib.wrap b/subprojects/zlib.wrap
new file mode 100644
index 00000000..6aff13ff
--- /dev/null
+++ b/subprojects/zlib.wrap
@@ -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
diff --git a/tests/meson.build b/tests/meson.build
index ba78944c..e6742b45 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -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)
diff --git a/tests/websocket-test.c b/tests/websocket-test.c
index 89e329dd..fe13b6f4 100644
--- a/tests/websocket-test.c
+++ b/tests/websocket-test.c
@@ -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,330 @@ 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; client_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; server_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; client_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);
+
+        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 +1906,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,



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]