[libsoup] websockets: add WebSocket support



commit dfc36f5feefcbc20fe2f61682519ba5373b03a84
Author: Dan Winship <danw gnome org>
Date:   Sun Nov 30 10:26:23 2014 -0500

    websockets: add WebSocket support
    
    Add functions to create and parse WebSocket handshakes, and to
    communicate on a WebSocket connection.
    
    Based on code originally from the Cockpit project, and on earlier work
    by Lionel Landwerlin to merge that into libsoup.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=627738

 docs/reference/libsoup-2.4-docs.sgml    |    1 +
 docs/reference/libsoup-2.4-sections.txt |   48 +
 libsoup/Makefile.am                     |    4 +
 libsoup/libsoup-2.4.sym                 |   23 +
 libsoup/soup-types.h                    |   34 +-
 libsoup/soup-websocket-connection.c     | 1568 +++++++++++++++++++++++++++++++
 libsoup/soup-websocket-connection.h     |  109 +++
 libsoup/soup-websocket.c                |  543 +++++++++++
 libsoup/soup-websocket.h                |   93 ++
 libsoup/soup.h                          |    2 +
 po/POTFILES.in                          |    1 +
 tests/Makefile.am                       |    1 +
 tests/header-parsing.c                  |   63 ++
 tests/websocket-test.c                  |  692 ++++++++++++++
 14 files changed, 3166 insertions(+), 16 deletions(-)
---
diff --git a/docs/reference/libsoup-2.4-docs.sgml b/docs/reference/libsoup-2.4-docs.sgml
index ba33142..7e553ff 100644
--- a/docs/reference/libsoup-2.4-docs.sgml
+++ b/docs/reference/libsoup-2.4-docs.sgml
@@ -63,6 +63,7 @@
     <xi:include href="xml/soup-form.xml"/>
     <xi:include href="xml/soup-xmlrpc.xml"/>
     <xi:include href="xml/soup-value-utils.xml"/>
+    <xi:include href="xml/soup-websocket.xml"/>
   </chapter>
 
   <chapter>
diff --git a/docs/reference/libsoup-2.4-sections.txt b/docs/reference/libsoup-2.4-sections.txt
index f7fcd75..293c12f 100644
--- a/docs/reference/libsoup-2.4-sections.txt
+++ b/docs/reference/libsoup-2.4-sections.txt
@@ -1300,3 +1300,51 @@ SOUP_ENCODE_VERSION
 SOUP_VERSION_CUR_STABLE
 SOUP_VERSION_PREV_STABLE
 </SECTION>
+
+<SECTION>
+<FILE>soup-websocket</FILE>
+<TITLE>WebSockets</TITLE>
+<SUBSECTION>
+soup_websocket_client_prepare_handshake
+soup_websocket_client_verify_handshake
+<SUBSECTION>
+soup_websocket_server_check_handshake
+soup_websocket_server_process_handshake
+<SUBSECTION>
+SoupWebsocketConnection
+SoupWebsocketConnectionType
+soup_websocket_connection_new
+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
+SoupWebsocketState
+soup_websocket_connection_get_state
+SoupWebsocketDataType
+soup_websocket_connection_send_text
+soup_websocket_connection_send_binary
+SoupWebsocketCloseCode
+soup_websocket_connection_close
+soup_websocket_connection_get_close_code
+soup_websocket_connection_get_close_data
+<SUBSECTION>
+SoupWebsocketError
+SOUP_WEBSOCKET_ERROR
+<SUBSECTION Private>
+SoupWebsocketConnectionClass
+SoupWebsocketConnectionPrivate
+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_websocket_close_code_get_type
+soup_websocket_connection_get_type
+soup_websocket_connection_type_get_type
+soup_websocket_data_type_get_type
+soup_websocket_error_get_quark
+soup_websocket_error_get_type
+soup_websocket_state_get_type
+</SECTION>
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index 3c32152..584f24a 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -68,6 +68,8 @@ soup_headers =                        \
        soup-types.h            \
        soup-uri.h              \
        soup-value-utils.h      \
+       soup-websocket.h        \
+       soup-websocket-connection.h     \
        soup-xmlrpc.h
 
 libsoupinclude_HEADERS =       \
@@ -186,6 +188,8 @@ libsoup_2_4_la_SOURCES =            \
        soup-uri.c                      \
        soup-value-utils.c              \
        soup-version.c                  \
+       soup-websocket.c                \
+       soup-websocket-connection.c     \
        soup-xmlrpc.c
 
 # TLD rules
diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym
index 08892f8..4f52b84 100644
--- a/libsoup/libsoup-2.4.sym
+++ b/libsoup/libsoup-2.4.sym
@@ -513,6 +513,29 @@ soup_value_hash_lookup
 soup_value_hash_lookup_vals
 soup_value_hash_new
 soup_value_hash_new_with_vals
+soup_websocket_client_prepare_handshake
+soup_websocket_client_verify_handshake
+soup_websocket_close_code_get_type
+soup_websocket_connection_close
+soup_websocket_connection_get_close_code
+soup_websocket_connection_get_close_data
+soup_websocket_connection_get_connection_type
+soup_websocket_connection_get_io_stream
+soup_websocket_connection_get_origin
+soup_websocket_connection_get_protocol
+soup_websocket_connection_get_state
+soup_websocket_connection_get_type
+soup_websocket_connection_get_uri
+soup_websocket_connection_new
+soup_websocket_connection_send_binary
+soup_websocket_connection_send_text
+soup_websocket_connection_type_get_type
+soup_websocket_data_type_get_type
+soup_websocket_error_get_quark
+soup_websocket_error_get_type
+soup_websocket_server_check_handshake
+soup_websocket_server_process_handshake
+soup_websocket_state_get_type
 soup_xmlrpc_build_fault
 soup_xmlrpc_build_method_call
 soup_xmlrpc_build_method_response
diff --git a/libsoup/soup-types.h b/libsoup/soup-types.h
index 0776bdb..e020de7 100644
--- a/libsoup/soup-types.h
+++ b/libsoup/soup-types.h
@@ -13,22 +13,24 @@
 
 G_BEGIN_DECLS
 
-typedef struct _SoupAddress           SoupAddress;
-typedef struct _SoupAuth              SoupAuth;
-typedef struct _SoupAuthDomain        SoupAuthDomain;
-typedef struct _SoupCookie            SoupCookie;
-typedef struct _SoupCookieJar         SoupCookieJar;
-typedef struct _SoupDate              SoupDate;
-typedef struct _SoupMessage           SoupMessage;
-typedef struct _SoupRequest           SoupRequest;
-typedef struct _SoupRequestHTTP       SoupRequestHTTP;
-typedef struct _SoupServer            SoupServer;
-typedef struct _SoupSession           SoupSession;
-typedef struct _SoupSessionAsync      SoupSessionAsync;
-typedef struct _SoupSessionFeature    SoupSessionFeature;
-typedef struct _SoupSessionSync       SoupSessionSync;
-typedef struct _SoupSocket            SoupSocket;
-typedef struct _SoupURI               SoupURI;
+typedef struct _SoupAddress             SoupAddress;
+typedef struct _SoupAuth                SoupAuth;
+typedef struct _SoupAuthDomain          SoupAuthDomain;
+typedef struct _SoupCookie              SoupCookie;
+typedef struct _SoupCookieJar           SoupCookieJar;
+typedef struct _SoupDate                SoupDate;
+typedef struct _SoupMessage             SoupMessage;
+typedef struct _SoupRequest             SoupRequest;
+typedef struct _SoupRequestHTTP         SoupRequestHTTP;
+typedef struct _SoupServer              SoupServer;
+typedef struct _SoupSession             SoupSession;
+typedef struct _SoupSessionAsync        SoupSessionAsync;
+typedef struct _SoupSessionFeature      SoupSessionFeature;
+typedef struct _SoupSessionSync         SoupSessionSync;
+typedef struct _SoupSocket              SoupSocket;
+typedef struct _SoupURI                 SoupURI;
+typedef struct _SoupWebsocketConnection SoupWebsocketConnection;
+
 
 /*< private >*/
 typedef struct _SoupConnection        SoupConnection;
diff --git a/libsoup/soup-websocket-connection.c b/libsoup/soup-websocket-connection.c
new file mode 100644
index 0000000..d792637
--- /dev/null
+++ b/libsoup/soup-websocket-connection.c
@@ -0,0 +1,1568 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-websocket-connection.c: This file was originally part of Cockpit.
+ *
+ * Copyright 2013, 2014 Red Hat, Inc.
+ *
+ * Cockpit is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * Cockpit 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "soup-websocket-connection.h"
+#include "soup-enum-types.h"
+#include "soup-message.h"
+#include "soup-uri.h"
+
+/*
+ * SECTION:websocketconnection
+ * @title: SoupWebsocketConnection
+ * @short_description: A WebSocket connection
+ *
+ * A #SoupWebsocketConnection is a WebSocket connection to a peer.
+ * This API is modeled after the W3C API for interacting with
+ * WebSockets.
+ *
+ * The #SoupWebsocketConnection:state property will indicate the
+ * state of the connection.
+ *
+ * Use soup_websocket_connection_send() to send a message to the peer.
+ * When a message is received the #SoupWebsocketConnection::message
+ * signal will fire.
+ *
+ * The soup_websocket_connection_close() function will perform an
+ * orderly close of the connection. The
+ * #SoupWebsocketConnection::closed signal will fire once the
+ * connection closes, whether it was initiated by this side or the
+ * peer.
+ *
+ * Connect to the #SoupWebsocketConnection::closing signal to detect
+ * when either peer begins closing the connection.
+ */
+
+/**
+ * SoupWebsocketConnection:
+ *
+ * A class representing a WebSocket connection.
+ *
+ * Since: 2.50
+ */
+
+/**
+ * SoupWebsocketConnectionClass:
+ * @message: default handler for the #SoupWebsocketConnection::message signal
+ * @error: default handler for the #SoupWebsocketConnection::error signal
+ * @closing: the default handler for the #SoupWebsocketConnection:closing signal
+ * @closed: default handler for the #SoupWebsocketConnection::closed signal
+ *
+ * The abstract base class for #SoupWebsocketConnection
+ *
+ * Since: 2.50
+ */
+
+enum {
+       PROP_0,
+       PROP_IO_STREAM,
+       PROP_CONNECTION_TYPE,
+       PROP_URI,
+       PROP_ORIGIN,
+       PROP_PROTOCOL,
+       PROP_STATE,
+};
+
+enum {
+       MESSAGE,
+       ERROR,
+       CLOSING,
+       CLOSED,
+       NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+typedef struct {
+       GBytes *data;
+       gboolean last;
+       gsize sent;
+       gsize amount;
+} Frame;
+
+struct _SoupWebsocketConnectionPrivate {
+       GIOStream *io_stream;
+       SoupWebsocketConnectionType connection_type;
+       SoupURI *uri;
+       char *origin;
+       char *protocol;
+
+       gushort peer_close_code;
+       char *peer_close_data;
+       gboolean close_sent;
+       gboolean close_received;
+       gboolean dirty_close;
+       GSource *close_timeout;
+
+       GMainContext *main_context;
+
+       gboolean io_closing;
+       gboolean io_closed;
+
+       GPollableInputStream *input;
+       GSource *input_source;
+       GByteArray *incoming;
+
+       GPollableOutputStream *output;
+       GSource *output_source;
+       GQueue outgoing;
+
+       /* Current message being assembled */
+       guint8 message_opcode;
+       GByteArray *message_data;
+};
+
+#define MAX_PAYLOAD   128 * 1024
+
+G_DEFINE_TYPE (SoupWebsocketConnection, soup_websocket_connection, G_TYPE_OBJECT)
+
+typedef enum {
+       SOUP_WEBSOCKET_QUEUE_NORMAL = 0,
+       SOUP_WEBSOCKET_QUEUE_URGENT = 1 << 0,
+       SOUP_WEBSOCKET_QUEUE_LAST = 1 << 1,
+} SoupWebsocketQueueFlags;
+
+static void queue_frame (SoupWebsocketConnection *self, SoupWebsocketQueueFlags flags,
+                        gpointer data, gsize len, gsize amount);
+
+static void
+frame_free (gpointer data)
+{
+       Frame *frame = data;
+
+       if (frame) {
+               g_bytes_unref (frame->data);
+               g_slice_free (Frame, frame);
+       }
+}
+
+static void
+soup_websocket_connection_init (SoupWebsocketConnection *self)
+{
+       SoupWebsocketConnectionPrivate *pv;
+
+       pv = self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, SOUP_TYPE_WEBSOCKET_CONNECTION,
+                                                    SoupWebsocketConnectionPrivate);
+
+       pv->incoming = g_byte_array_sized_new (1024);
+       g_queue_init (&pv->outgoing);
+       pv->main_context = g_main_context_ref_thread_default ();
+}
+
+static void
+on_iostream_closed (GObject *source,
+                    GAsyncResult *result,
+                    gpointer user_data)
+{
+       SoupWebsocketConnection *self = user_data;
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+       GError *error = NULL;
+
+       /* We treat connection as closed even if close fails */
+       pv->io_closed = TRUE;
+       g_io_stream_close_finish (pv->io_stream, result, &error);
+
+       if (error) {
+               g_debug ("error closing web socket stream: %s", error->message);
+               if (!pv->dirty_close)
+                       g_signal_emit (self, signals[ERROR], 0, error);
+               pv->dirty_close = TRUE;
+               g_error_free (error);
+       }
+
+       g_assert (soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_CLOSED);
+       g_debug ("closed: completed io stream close");
+       g_signal_emit (self, signals[CLOSED], 0);
+
+       g_object_unref (self);
+}
+
+static void
+stop_input (SoupWebsocketConnection *self)
+{
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+
+       if (pv->input_source) {
+               g_debug ("stopping input source");
+               g_source_destroy (pv->input_source);
+               g_source_unref (pv->input_source);
+               pv->input_source = NULL;
+       }
+}
+
+static void
+stop_output (SoupWebsocketConnection *self)
+{
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+
+       if (pv->output_source) {
+               g_debug ("stopping output source");
+               g_source_destroy (pv->output_source);
+               g_source_unref (pv->output_source);
+               pv->output_source = NULL;
+       }
+}
+
+static void
+close_io_stop_timeout (SoupWebsocketConnection *self)
+{
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+
+       if (pv->close_timeout) {
+               g_source_destroy (pv->close_timeout);
+               g_source_unref (pv->close_timeout);
+               pv->close_timeout = NULL;
+       }
+}
+
+static void
+close_io_stream (SoupWebsocketConnection *self)
+{
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+
+       close_io_stop_timeout (self);
+
+       if (!pv->io_closing) {
+               stop_input (self);
+               stop_output (self);
+               pv->io_closing = TRUE;
+               g_debug ("closing io stream");
+               g_io_stream_close_async (pv->io_stream, G_PRIORITY_DEFAULT,
+                                        NULL, on_iostream_closed, g_object_ref (self));
+       }
+
+       g_object_notify (G_OBJECT (self), "state");
+}
+
+static void
+shutdown_wr_io_stream (SoupWebsocketConnection *self)
+{
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+       GSocket *socket;
+       GError *error = NULL;
+
+       stop_output (self);
+
+       if (G_IS_SOCKET_CONNECTION (pv->io_stream)) {
+               socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (pv->io_stream));
+               g_socket_shutdown (socket, FALSE, TRUE, &error);
+               if (error != NULL) {
+                       g_debug ("error shutting down io stream: %s", error->message);
+                       g_error_free (error);
+               }
+       }
+
+       g_object_notify (G_OBJECT (self), "state");
+}
+
+static gboolean
+on_timeout_close_io (gpointer user_data)
+{
+       SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (user_data);
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+
+       pv->close_timeout = 0;
+
+       g_debug ("peer did not close io when expected");
+       close_io_stream (self);
+
+       return FALSE;
+}
+
+static void
+close_io_after_timeout (SoupWebsocketConnection *self)
+{
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+       const int timeout = 5;
+
+       if (pv->close_timeout)
+               return;
+
+       g_debug ("waiting %d seconds for peer to close io", timeout);
+       pv->close_timeout = g_timeout_source_new_seconds (timeout);
+       g_source_set_callback (pv->close_timeout, on_timeout_close_io, self, NULL);
+       g_source_attach (pv->close_timeout, pv->main_context);
+}
+
+static void
+xor_with_mask (const guint8 *mask,
+              guint8 *data,
+              gsize len)
+{
+       gsize n;
+
+       /* Do the masking */
+       for (n = 0; n < len; n++)
+               data[n] ^= mask[n & 3];
+}
+
+static void
+send_message (SoupWebsocketConnection *self,
+             SoupWebsocketQueueFlags flags,
+             guint8 opcode,
+             const guint8 *data,
+             gsize length)
+{
+       gsize buffered_amount = length;
+       GByteArray *bytes;
+       gsize frame_len;
+       guint8 *outer;
+       guint8 *mask = 0;
+       guint8 *at;
+
+       bytes = g_byte_array_sized_new (14 + length);
+       outer = bytes->data;
+       outer[0] = 0x80 | opcode;
+
+       /* If control message, truncate payload */
+       if (opcode & 0x08) {
+               if (length > 125) {
+                       g_warning ("Truncating WebSocket control message payload");
+                       length = 125;
+               }
+
+               buffered_amount = 0;
+       }
+
+       if (length < 126) {
+               outer[1] = (0xFF & length); /* mask | 7-bit-len */
+               bytes->len = 2;
+       } else if (length < 65536) {
+               outer[1] = 126; /* mask | 16-bit-len */
+               outer[2] = (length >> 8) & 0xFF;
+               outer[3] = (length >> 0) & 0xFF;
+               bytes->len = 4;
+       } else {
+               outer[1] = 127; /* mask | 64-bit-len */
+               outer[2] = (length >> 56) & 0xFF;
+               outer[3] = (length >> 48) & 0xFF;
+               outer[4] = (length >> 40) & 0xFF;
+               outer[5] = (length >> 32) & 0xFF;
+               outer[6] = (length >> 24) & 0xFF;
+               outer[7] = (length >> 16) & 0xFF;
+               outer[8] = (length >> 8) & 0xFF;
+               outer[9] = (length >> 0) & 0xFF;
+               bytes->len = 10;
+       }
+
+       /* The server side doesn't need to mask, so we don't. There's
+        * probably a client somewhere that's not expecting it.
+        */
+       if (self->pv->connection_type == SOUP_WEBSOCKET_CONNECTION_CLIENT) {
+               outer[1] |= 0x80;
+               mask = outer + bytes->len;
+               * ((guint32 *)mask) = g_random_int ();
+               bytes->len += 4;
+       }
+
+       at = bytes->data + bytes->len;
+       g_byte_array_append (bytes, data, length);
+
+       if (self->pv->connection_type == SOUP_WEBSOCKET_CONNECTION_CLIENT)
+               xor_with_mask (mask, at, length);
+
+       frame_len = bytes->len;
+       queue_frame (self, flags, g_byte_array_free (bytes, FALSE),
+                    frame_len, buffered_amount);
+       g_debug ("queued %d frame of len %u", (int)opcode, (guint)frame_len);
+}
+
+static void
+send_close (SoupWebsocketConnection *self,
+           SoupWebsocketQueueFlags flags,
+           gushort code,
+           const char *reason)
+{
+       /* Note that send_message truncates as expected */
+       char buffer[128];
+       gsize len = 0;
+
+       if (code != 0) {
+               buffer[len++] = code >> 8;
+               buffer[len++] = code & 0xFF;
+               if (reason)
+                       len += g_strlcpy (buffer + len, reason, sizeof (buffer) - len);
+       }
+
+       send_message (self, flags, 0x08, (guint8 *)buffer, len);
+       self->pv->close_sent = TRUE;
+}
+
+static void
+emit_error_and_close (SoupWebsocketConnection *self,
+                     GError *error,
+                     gboolean prejudice)
+{
+       gboolean ignore = FALSE;
+       gushort code;
+
+       if (soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_CLOSED) {
+               g_error_free (error);
+               return;
+       }
+
+       if (error && error->domain == SOUP_WEBSOCKET_ERROR)
+               code = error->code;
+       else
+               code = SOUP_WEBSOCKET_CLOSE_GOING_AWAY;
+
+       self->pv->dirty_close = TRUE;
+       g_signal_emit (self, signals[ERROR], 0, error);
+       g_error_free (error);
+
+       /* If already closing, just ignore this stuff */
+       switch (soup_websocket_connection_get_state (self)) {
+       case SOUP_WEBSOCKET_STATE_CLOSED:
+               ignore = TRUE;
+               break;
+       case SOUP_WEBSOCKET_STATE_CLOSING:
+               ignore = !prejudice;
+               break;
+       default:
+               break;
+       }
+
+       if (ignore) {
+               g_debug ("already closing/closed, ignoring error");
+       } else if (prejudice) {
+               g_debug ("forcing close due to error");
+               close_io_stream (self);
+       } else {
+               g_debug ("requesting close due to error");
+               send_close (self, SOUP_WEBSOCKET_QUEUE_URGENT | SOUP_WEBSOCKET_QUEUE_LAST, code, NULL);
+       }
+}
+
+static void
+protocol_error_and_close_full (SoupWebsocketConnection *self,
+                               gboolean prejudice)
+{
+       GError *error;
+
+       error = g_error_new_literal (SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR,
+                                    self->pv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ?
+                                    "Received invalid WebSocket response from the client" :
+                                    "Received invalid WebSocket response from the server");
+       emit_error_and_close (self, error, prejudice);
+}
+
+static void
+protocol_error_and_close (SoupWebsocketConnection *self)
+{
+       protocol_error_and_close_full (self, FALSE);
+}
+
+static void
+bad_data_error_and_close (SoupWebsocketConnection *self)
+{
+       GError *error;
+
+       error = g_error_new_literal (SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_CLOSE_BAD_DATA,
+                                    self->pv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ?
+                                    "Received invalid WebSocket data from the client" :
+                                    "Received invalid WebSocket data from the server");
+       emit_error_and_close (self, error, FALSE);
+}
+
+static void
+too_big_error_and_close (SoupWebsocketConnection *self,
+                         gsize payload_len)
+{
+       GError *error;
+
+       error = g_error_new_literal (SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_CLOSE_TOO_BIG,
+                                    self->pv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ?
+                                    "Received extremely large WebSocket data from the client" :
+                                    "Received extremely large WebSocket data from the server");
+       g_debug ("%s is trying to frame of size %" G_GUINT64_FORMAT " or greater, but max supported size is 
128KiB",
+                self->pv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ? "server" : "client",
+                payload_len);
+       emit_error_and_close (self, error, TRUE);
+
+       /* The input is in an invalid state now */
+       stop_input (self);
+}
+
+static void
+receive_close (SoupWebsocketConnection *self,
+              const guint8 *data,
+              gsize len)
+{
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+
+       pv->peer_close_code = 0;
+       g_free (pv->peer_close_data);
+       pv->peer_close_data = NULL;
+       pv->close_received = TRUE;
+
+       /* Store the code/data payload */
+       if (len >= 2) {
+               pv->peer_close_code = (guint16)data[0] << 8 | data[1];
+       }
+       if (len > 2) {
+               data += 2;
+               len -= 2;
+               if (g_utf8_validate ((char *)data, len, NULL))
+                       pv->peer_close_data = g_strndup ((char *)data, len);
+               else
+                       g_debug ("received non-UTF8 close data: %d '%.*s' %d", (int)len, (int)len, (char 
*)data, (int)data[0]);
+       }
+
+       /* Once we receive close response on server, close immediately */
+       if (pv->close_sent) {
+               shutdown_wr_io_stream (self);
+               if (pv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER)
+                       close_io_stream (self);
+       } else {
+               /* Send back the response */
+               soup_websocket_connection_close (self, pv->peer_close_code, NULL);
+       }
+}
+
+static void
+receive_ping (SoupWebsocketConnection *self,
+                      const guint8 *data,
+                      gsize len)
+{
+       /* Send back a pong with same data */
+       g_debug ("received ping, responding");
+       send_message (self, SOUP_WEBSOCKET_QUEUE_URGENT, 0x0A, data, len);
+}
+
+static void
+process_contents (SoupWebsocketConnection *self,
+                 gboolean control,
+                 gboolean fin,
+                 guint8 opcode,
+                 gconstpointer payload,
+                 gsize payload_len)
+{
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+       GBytes *message;
+
+       if (control) {
+               /* Control frames must never be fragmented */
+               if (!fin) {
+                       g_debug ("received fragmented control frame");
+                       protocol_error_and_close (self);
+                       return;
+               }
+
+               g_debug ("received control frame %d with %d payload", (int)opcode, (int)payload_len);
+
+               switch (opcode) {
+               case 0x08:
+                       receive_close (self, payload, payload_len);
+                       break;
+               case 0x09:
+                       receive_ping (self, payload, payload_len);
+                       break;
+               case 0x0A:
+                       break;
+               default:
+                       g_debug ("received unsupported control frame: %d", (int)opcode);
+                       break;
+               }
+       } else if (pv->close_received) {
+               g_debug ("received message after close was received");
+       } else {
+               /* A message frame */
+
+               if (!fin && opcode) {
+                       /* Initial fragment of a message */
+                       if (pv->message_data) {
+                               g_debug ("received out of order inital message fragment");
+                               protocol_error_and_close (self);
+                               return;
+                       }
+                       g_debug ("received inital fragment frame %d with %d payload", (int)opcode, 
(int)payload_len);
+               } else if (!fin && !opcode) {
+                       /* Middle fragment of a message */
+                       if (!pv->message_data) {
+                               g_debug ("received out of order middle message fragment");
+                               protocol_error_and_close (self);
+                               return;
+                       }
+                       g_debug ("received middle fragment frame with %d payload", (int)payload_len);
+               } else if (fin && !opcode) {
+                       /* Last fragment of a message */
+                       if (!pv->message_data) {
+                               g_debug ("received out of order ending message fragment");
+                               protocol_error_and_close (self);
+                               return;
+                       }
+                       g_debug ("received last fragment frame with %d payload", (int)payload_len);
+               } else {
+                       /* An unfragmented message */
+                       g_assert (opcode != 0);
+                       if (pv->message_data) {
+                               g_debug ("received unfragmented message when fragment was expected");
+                               protocol_error_and_close (self);
+                               return;
+                       }
+                       g_debug ("received frame %d with %d payload", (int)opcode, (int)payload_len);
+               }
+
+               if (opcode) {
+                       pv->message_opcode = opcode;
+                       pv->message_data = g_byte_array_sized_new (payload_len);
+               }
+
+               switch (pv->message_opcode) {
+               case 0x01:
+                       if (!g_utf8_validate ((char *)payload, payload_len, NULL)) {
+                               g_debug ("received invalid non-UTF8 text data");
+
+                               /* Discard the entire message */
+                               g_byte_array_unref (pv->message_data);
+                               pv->message_data = NULL;
+                               pv->message_opcode = 0;
+
+                               bad_data_error_and_close (self);
+                               return;
+                       }
+                       /* fall through */
+               case 0x02:
+                       g_byte_array_append (pv->message_data, payload, payload_len);
+                       break;
+               default:
+                       g_debug ("received unknown data frame: %d", (int)opcode);
+                       break;
+               }
+
+               /* Actually deliver the message? */
+               if (fin) {
+                       /* Always null terminate, as a convenience */
+                       g_byte_array_append (pv->message_data, (guchar *)"\0", 1);
+
+                       /* But don't include the null terminator in the byte count */
+                       pv->message_data->len--;
+
+                       opcode = pv->message_opcode;
+                       message = g_byte_array_free_to_bytes (pv->message_data);
+                       pv->message_data = NULL;
+                       pv->message_opcode = 0;
+                       g_debug ("message: delivering %d with %d length",
+                                (int)opcode, (int)g_bytes_get_size (message));
+                       g_signal_emit (self, signals[MESSAGE], 0, (int)opcode, message);
+                       g_bytes_unref (message);
+               }
+       }
+}
+
+static gboolean
+process_frame (SoupWebsocketConnection *self)
+{
+       guint8 *header;
+       guint8 *payload;
+       guint64 payload_len;
+       guint8 *mask;
+       gboolean fin;
+       gboolean control;
+       gboolean masked;
+       guint8 opcode;
+       gsize len;
+       gsize at;
+
+       len = self->pv->incoming->len;
+       if (len < 2)
+               return FALSE; /* need more data */
+
+       header = self->pv->incoming->data;
+       fin = ((header[0] & 0x80) != 0);
+       control = header[0] & 0x08;
+       opcode = header[0] & 0x0f;
+       masked = ((header[1] & 0x80) != 0);
+
+       switch (header[1] & 0x7f) {
+       case 126:
+               at = 4;
+               if (len < at)
+                       return FALSE; /* need more data */
+               payload_len = (((guint16)header[2] << 8) |
+                              ((guint16)header[3] << 0));
+               break;
+       case 127:
+               at = 10;
+               if (len < at)
+                       return FALSE; /* need more data */
+               payload_len = (((guint64)header[2] << 56) |
+                              ((guint64)header[3] << 48) |
+                              ((guint64)header[4] << 40) |
+                              ((guint64)header[5] << 32) |
+                              ((guint64)header[6] << 24) |
+                              ((guint64)header[7] << 16) |
+                              ((guint64)header[8] << 8) |
+                              ((guint64)header[9] << 0));
+               break;
+       default:
+               payload_len = header[1] & 0x7f;
+               at = 2;
+               break;
+       }
+
+       /* Safety valve */
+       if (payload_len >= MAX_PAYLOAD) {
+               too_big_error_and_close (self, payload_len);
+               return FALSE;
+       }
+
+       if (len < at + payload_len)
+               return FALSE; /* need more data */
+
+       payload = header + at;
+
+       if (masked) {
+               mask = header + at;
+               payload += 4;
+               at += 4;
+
+               if (len < at + payload_len)
+                       return FALSE; /* need more data */
+
+               xor_with_mask (mask, payload, payload_len);
+       }
+
+       /* 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);
+
+       /* Move past the parsed frame */
+       g_byte_array_remove_range (self->pv->incoming, 0, at + payload_len);
+       return TRUE;
+}
+
+static void
+process_incoming (SoupWebsocketConnection *self)
+{
+       while (process_frame (self))
+               ;
+}
+
+static gboolean
+on_web_socket_input (GObject *pollable_stream,
+                    gpointer user_data)
+{
+       SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (user_data);
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+       GError *error = NULL;
+       gboolean end = FALSE;
+       gssize count;
+       gsize len;
+
+       do {
+               len = pv->incoming->len;
+               g_byte_array_set_size (pv->incoming, len + 1024);
+
+               count = g_pollable_input_stream_read_nonblocking (pv->input,
+                                                                 pv->incoming->data + len,
+                                                                 1024, NULL, &error);
+
+               if (count < 0) {
+                       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+                               g_error_free (error);
+                               count = 0;
+                       } else {
+                               emit_error_and_close (self, error, TRUE);
+                               return TRUE;
+                       }
+               } else if (count == 0) {
+                       end = TRUE;
+               }
+
+               pv->incoming->len = len + count;
+       } while (count > 0);
+
+       process_incoming (self);
+
+       if (end) {
+               if (!pv->close_sent || !pv->close_received) {
+                       pv->dirty_close = TRUE;
+                       g_debug ("connection unexpectedly closed by peer");
+               } else {
+                       g_debug ("peer has closed socket");
+               }
+
+               close_io_stream (self);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+on_web_socket_output (GObject *pollable_stream,
+                     gpointer user_data)
+{
+       SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (user_data);
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+       const guint8 *data;
+       GError *error = NULL;
+       Frame *frame;
+       gssize count;
+       gsize len;
+
+       frame = g_queue_peek_head (&pv->outgoing);
+
+       /* No more frames to send */
+       if (frame == NULL) {
+               stop_output (self);
+               return TRUE;
+       }
+
+       data = g_bytes_get_data (frame->data, &len);
+       g_assert (len > 0);
+       g_assert (len > frame->sent);
+
+       count = g_pollable_output_stream_write_nonblocking (pv->output,
+                                                           data + frame->sent,
+                                                           len - frame->sent,
+                                                           NULL, &error);
+
+       if (count < 0) {
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+                       g_clear_error (&error);
+                       count = 0;
+               } else {
+                       emit_error_and_close (self, error, TRUE);
+                       return FALSE;
+               }
+       }
+
+       frame->sent += count;
+       if (frame->sent >= len) {
+               g_debug ("sent frame");
+               g_queue_pop_head (&pv->outgoing);
+
+               if (frame->last) {
+                       if (pv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER) {
+                               close_io_stream (self);
+                       } else {
+                               shutdown_wr_io_stream (self);
+                               close_io_after_timeout (self);
+                       }
+               }
+               frame_free (frame);
+       }
+
+       return TRUE;
+}
+
+static void
+start_output (SoupWebsocketConnection *self)
+{
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+
+       if (pv->output_source)
+               return;
+
+       g_debug ("starting output source");
+       pv->output_source = g_pollable_output_stream_create_source (pv->output, NULL);
+       g_source_set_callback (pv->output_source, (GSourceFunc)on_web_socket_output, self, NULL);
+       g_source_attach (pv->output_source, pv->main_context);
+}
+
+static void
+queue_frame (SoupWebsocketConnection *self,
+            SoupWebsocketQueueFlags flags,
+            gpointer data,
+            gsize len,
+            gsize amount)
+{
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+       Frame *frame;
+       Frame *prev;
+
+       g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
+       g_return_if_fail (pv->close_sent == FALSE);
+       g_return_if_fail (data != NULL);
+       g_return_if_fail (len > 0);
+
+       frame = g_slice_new0 (Frame);
+       frame->data = g_bytes_new_take (data, len);
+       frame->amount = amount;
+       frame->last = (flags & SOUP_WEBSOCKET_QUEUE_LAST) ? TRUE : FALSE;
+
+       /* If urgent put at front of queue */
+       if (flags & SOUP_WEBSOCKET_QUEUE_URGENT) {
+               /* But we can't interrupt a message already partially sent */
+               prev = g_queue_pop_head (&pv->outgoing);
+               if (prev == NULL) {
+                       g_queue_push_head (&pv->outgoing, frame);
+               } else if (prev->sent > 0) {
+                       g_queue_push_head (&pv->outgoing, frame);
+                       g_queue_push_head (&pv->outgoing, prev);
+               } else {
+                       g_queue_push_head (&pv->outgoing, prev);
+                       g_queue_push_head (&pv->outgoing, frame);
+               }
+       } else {
+               g_queue_push_tail (&pv->outgoing, frame);
+       }
+
+       start_output (self);
+}
+
+static void
+soup_websocket_connection_constructed (GObject *object)
+{
+       SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (object);
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+       GInputStream *is;
+       GOutputStream *os;
+
+       G_OBJECT_CLASS (soup_websocket_connection_parent_class)->constructed (object);
+
+       g_return_if_fail (pv->io_stream != NULL);
+
+       is = g_io_stream_get_input_stream (pv->io_stream);
+       g_return_if_fail (G_IS_POLLABLE_INPUT_STREAM (is));
+       pv->input = G_POLLABLE_INPUT_STREAM (is);
+       g_return_if_fail (g_pollable_input_stream_can_poll (pv->input));
+
+       os = g_io_stream_get_output_stream (pv->io_stream);
+       g_return_if_fail (G_IS_POLLABLE_OUTPUT_STREAM (os));
+       pv->output = G_POLLABLE_OUTPUT_STREAM (os);
+       g_return_if_fail (g_pollable_output_stream_can_poll (pv->output));
+
+       pv->input_source = g_pollable_input_stream_create_source (pv->input, NULL);
+       g_source_set_callback (pv->input_source, (GSourceFunc)on_web_socket_input, self, NULL);
+       g_source_attach (pv->input_source, pv->main_context);
+}
+
+static void
+soup_websocket_connection_get_property (GObject *object,
+                                       guint prop_id,
+                                       GValue *value,
+                                       GParamSpec *pspec)
+{
+       SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (object);
+
+       switch (prop_id) {
+       case PROP_IO_STREAM:
+               g_value_set_object (value, soup_websocket_connection_get_io_stream (self));
+               break;
+
+       case PROP_CONNECTION_TYPE:
+               g_value_set_enum (value, soup_websocket_connection_get_connection_type (self));
+               break;
+
+       case PROP_URI:
+               g_value_set_boxed (value, soup_websocket_connection_get_uri (self));
+               break;
+
+       case PROP_ORIGIN:
+               g_value_set_string (value, soup_websocket_connection_get_origin (self));
+               break;
+
+       case PROP_PROTOCOL:
+               g_value_set_string (value, soup_websocket_connection_get_protocol (self));
+               break;
+
+       case PROP_STATE:
+               g_value_set_enum (value, soup_websocket_connection_get_state (self));
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+soup_websocket_connection_set_property (GObject *object,
+                                       guint prop_id,
+                                       const GValue *value,
+                                       GParamSpec *pspec)
+{
+       SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (object);
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+
+       switch (prop_id) {
+       case PROP_IO_STREAM:
+               g_return_if_fail (pv->io_stream == NULL);
+               pv->io_stream = g_value_dup_object (value);
+               break;
+
+       case PROP_CONNECTION_TYPE:
+               pv->connection_type = g_value_get_enum (value);
+               break;
+
+       case PROP_URI:
+               g_return_if_fail (pv->uri == NULL);
+               pv->uri = g_value_dup_boxed (value);
+               break;
+
+       case PROP_ORIGIN:
+               g_return_if_fail (pv->origin == NULL);
+               pv->origin = g_value_dup_string (value);
+               break;
+
+       case PROP_PROTOCOL:
+               g_return_if_fail (pv->protocol == NULL);
+               pv->protocol = g_value_dup_string (value);
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+soup_websocket_connection_dispose (GObject *object)
+{
+       SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (object);
+
+       self->pv->dirty_close = TRUE;
+       close_io_stream (self);
+
+       G_OBJECT_CLASS (soup_websocket_connection_parent_class)->dispose (object);
+}
+
+static void
+soup_websocket_connection_finalize (GObject *object)
+{
+       SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (object);
+       SoupWebsocketConnectionPrivate *pv = self->pv;
+
+       g_free (pv->peer_close_data);
+
+       g_main_context_unref (pv->main_context);
+
+       if (pv->incoming)
+               g_byte_array_free (pv->incoming, TRUE);
+       while (!g_queue_is_empty (&pv->outgoing))
+               frame_free (g_queue_pop_head (&pv->outgoing));
+
+       g_clear_object (&pv->io_stream);
+       g_assert (!pv->input_source);
+       g_assert (!pv->output_source);
+       g_assert (pv->io_closing);
+       g_assert (pv->io_closed);
+       g_assert (!pv->close_timeout);
+
+       if (pv->message_data)
+               g_byte_array_free (pv->message_data, TRUE);
+
+       if (pv->uri)
+               soup_uri_free (pv->uri);
+       g_free (pv->origin);
+       g_free (pv->protocol);
+
+       G_OBJECT_CLASS (soup_websocket_connection_parent_class)->finalize (object);
+}
+
+static void
+soup_websocket_connection_class_init (SoupWebsocketConnectionClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+       g_type_class_add_private (klass, sizeof (SoupWebsocketConnectionPrivate));
+
+       gobject_class->constructed = soup_websocket_connection_constructed;
+       gobject_class->get_property = soup_websocket_connection_get_property;
+       gobject_class->set_property = soup_websocket_connection_set_property;
+       gobject_class->dispose = soup_websocket_connection_dispose;
+       gobject_class->finalize = soup_websocket_connection_finalize;
+
+       /**
+        * SoupWebsocketConnection:io-stream:
+        *
+        * The underlying IO stream the WebSocket is communicating
+        * over.
+        *
+        * The input and output streams must be pollable streams.
+        *
+        * Since: 2.50
+        */
+       g_object_class_install_property (gobject_class, PROP_IO_STREAM,
+                                        g_param_spec_object ("io-stream",
+                                                             "I/O Stream",
+                                                             "Underlying I/O stream",
+                                                             G_TYPE_IO_STREAM,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       /**
+        * SoupWebsocketConnection:connection-type:
+        *
+        * The type of connection (client/server).
+        *
+        * Since: 2.50
+        */
+       g_object_class_install_property (gobject_class, PROP_CONNECTION_TYPE,
+                                        g_param_spec_enum ("connection-type",
+                                                           "Connection type",
+                                                           "Connection type (client/server)",
+                                                           SOUP_TYPE_WEBSOCKET_CONNECTION_TYPE,
+                                                           SOUP_WEBSOCKET_CONNECTION_UNKNOWN,
+                                                           G_PARAM_READWRITE |
+                                                           G_PARAM_CONSTRUCT_ONLY |
+                                                           G_PARAM_STATIC_STRINGS));
+
+       /**
+        * SoupWebsocketConnection:uri:
+        *
+        * The URI of the WebSocket.
+        *
+        * For servers this represents the address of the WebSocket,
+        * and for clients it is the address connected to.
+        *
+        * Since: 2.50
+        */
+       g_object_class_install_property (gobject_class, PROP_URI,
+                                        g_param_spec_boxed ("uri",
+                                                            "URI",
+                                                            "The WebSocket URI",
+                                                            SOUP_TYPE_URI,
+                                                            G_PARAM_READWRITE |
+                                                            G_PARAM_CONSTRUCT_ONLY |
+                                                            G_PARAM_STATIC_STRINGS));
+
+       /**
+        * SoupWebsocketConnection:origin:
+        *
+        * The client's Origin.
+        *
+        * Since: 2.50
+        */
+       g_object_class_install_property (gobject_class, PROP_ORIGIN,
+                                        g_param_spec_string ("origin",
+                                                             "Origin",
+                                                             "The WebSocket origin",
+                                                             NULL,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       /**
+        * SoupWebsocketConnection:protocol:
+        *
+        * The chosen protocol, or %NULL if a protocol was not agreed
+        * upon.
+        *
+        * Since: 2.50
+        */
+       g_object_class_install_property (gobject_class, PROP_PROTOCOL,
+                                        g_param_spec_string ("protocol",
+                                                             "Protocol",
+                                                             "The chosen WebSocket protocol",
+                                                             NULL,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       /**
+        * SoupWebsocketConnection:state:
+        *
+        * The current state of the WebSocket.
+        *
+        * Since: 2.50
+        */
+       g_object_class_install_property (gobject_class, PROP_STATE,
+                                        g_param_spec_enum ("state",
+                                                           "State",
+                                                           "State ",
+                                                           SOUP_TYPE_WEBSOCKET_STATE,
+                                                           SOUP_WEBSOCKET_STATE_OPEN,
+                                                           G_PARAM_READABLE |
+                                                           G_PARAM_STATIC_STRINGS));
+
+       /**
+        * SoupWebsocketConnection::message:
+        * @self: the WebSocket
+        * @type: the type of message contents
+        * @message: the message data
+        *
+        * Emitted when we receive a message from the peer.
+        *
+        * As a convenience, the @message data will always be
+        * NUL-terminated, but the NUL byte will not be included in
+        * the length count.
+        *
+        * Since: 2.50
+        */
+       signals[MESSAGE] = g_signal_new ("message",
+                                        SOUP_TYPE_WEBSOCKET_CONNECTION,
+                                        G_SIGNAL_RUN_FIRST,
+                                        G_STRUCT_OFFSET (SoupWebsocketConnectionClass, message),
+                                        NULL, NULL, g_cclosure_marshal_generic,
+                                        G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_BYTES);
+
+       /**
+        * SoupWebsocketConnection::error:
+        * @self: the WebSocket
+        * @error: the error that occured
+        *
+        * Emitted when an error occurred on the WebSocket. This may
+        * be fired multiple times. Fatal errors will be followed by
+        * the #SoupWebsocketConnection::closed signal being emitted.
+        *
+        * Since: 2.50
+        */
+       signals[ERROR] = g_signal_new ("error",
+                                      SOUP_TYPE_WEBSOCKET_CONNECTION,
+                                      G_SIGNAL_RUN_FIRST,
+                                      G_STRUCT_OFFSET (SoupWebsocketConnectionClass, error),
+                                      NULL, NULL, g_cclosure_marshal_generic,
+                                      G_TYPE_NONE, 1, G_TYPE_ERROR);
+
+       /**
+        * SoupWebsocketConnection::closing:
+        * @self: the WebSocket
+        *
+        * This signal will be emitted during an orderly close.
+        *
+        * Since: 2.50
+        */
+       signals[CLOSING] = g_signal_new ("closing",
+                                        SOUP_TYPE_WEBSOCKET_CONNECTION,
+                                        G_SIGNAL_RUN_LAST,
+                                        G_STRUCT_OFFSET (SoupWebsocketConnectionClass, closing),
+                                        NULL, NULL, g_cclosure_marshal_generic,
+                                        G_TYPE_NONE, 0);
+
+       /**
+        * SoupWebsocketConnection::closed:
+        * @self: the WebSocket
+        *
+        * Emitted when the connection has completely closed, either
+        * due to an orderly close from the peer, one initiated via
+        * soup_websocket_connection_close() or a fatal error
+        * condition that caused a close.
+        *
+        * This signal will be emitted once.
+        *
+        * Since: 2.50
+        */
+       signals[CLOSED] = g_signal_new ("closed",
+                                       SOUP_TYPE_WEBSOCKET_CONNECTION,
+                                       G_SIGNAL_RUN_FIRST,
+                                       G_STRUCT_OFFSET (SoupWebsocketConnectionClass, closed),
+                                       NULL, NULL, g_cclosure_marshal_generic,
+                                       G_TYPE_NONE, 0);
+}
+
+/**
+ * soup_websocket_connection_new:
+ * @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
+ *
+ * Creates a #SoupWebsocketConnection on @stream. This should be
+ * called after completing the handshake to begin using the WebSocket
+ * protocol.
+ *
+ * Returns: a new #SoupWebsocketConnection
+ *
+ * Since: 2.50
+ */
+SoupWebsocketConnection *
+soup_websocket_connection_new (GIOStream                    *stream,
+                              SoupURI                      *uri,
+                              SoupWebsocketConnectionType   type,
+                              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);
+}
+
+/**
+ * soup_websocket_connection_get_io_stream:
+ * @self: the WebSocket
+ *
+ * Get the I/O stream the WebSocket is communicating over.
+ *
+ * Returns: (transfer none): the WebSocket's I/O stream.
+ *
+ * Since: 2.50
+ */
+GIOStream *
+soup_websocket_connection_get_io_stream (SoupWebsocketConnection *self)
+{
+       g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
+
+       return self->pv->io_stream;
+}
+
+/**
+ * soup_websocket_connection_get_connection_type:
+ * @self: the WebSocket
+ *
+ * Get the connection type (client/server) of the connection.
+ *
+ * Returns: the connection type
+ *
+ * Since: 2.50
+ */
+SoupWebsocketConnectionType
+soup_websocket_connection_get_connection_type (SoupWebsocketConnection *self)
+{
+       g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), SOUP_WEBSOCKET_CONNECTION_UNKNOWN);
+
+       return self->pv->connection_type;
+}
+
+/**
+ * soup_websocket_connection_get_uri:
+ * @self: the WebSocket
+ *
+ * Get the URI of the WebSocket.
+ *
+ * For servers this represents the address of the WebSocket, and
+ * for clients it is the address connected to.
+ *
+ * Returns: (transfer none): the URI
+ *
+ * Since: 2.50
+ */
+SoupURI *
+soup_websocket_connection_get_uri (SoupWebsocketConnection *self)
+{
+       g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
+
+       return self->pv->uri;
+}
+
+/**
+ * soup_websocket_connection_get_origin:
+ * @self: the WebSocket
+ *
+ * Get the origin of the WebSocket.
+ *
+ * Returns: (nullable): the origin, or %NULL
+ *
+ * Since: 2.50
+ */
+const char *
+soup_websocket_connection_get_origin (SoupWebsocketConnection *self)
+{
+       g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
+
+       return self->pv->origin;
+}
+
+/**
+ * soup_websocket_connection_get_protocol:
+ * @self: the WebSocket
+ *
+ * Get the protocol chosen via negotiation with the peer.
+ *
+ * Returns: (nullable): the chosen protocol, or %NULL
+ *
+ * Since: 2.50
+ */
+const char *
+soup_websocket_connection_get_protocol (SoupWebsocketConnection *self)
+{
+       g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
+
+       return self->pv->protocol;
+}
+
+/**
+ * soup_websocket_connection_get_state:
+ * @self: the WebSocket
+ *
+ * Get the current state of the WebSocket.
+ *
+ * Returns: the state
+ *
+ * Since: 2.50
+ */
+SoupWebsocketState
+soup_websocket_connection_get_state (SoupWebsocketConnection *self)
+{
+       g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), 0);
+
+       if (self->pv->io_closed)
+               return SOUP_WEBSOCKET_STATE_CLOSED;
+       else if (self->pv->io_closing || self->pv->close_sent)
+               return SOUP_WEBSOCKET_STATE_CLOSING;
+       else
+               return SOUP_WEBSOCKET_STATE_OPEN;
+}
+
+/**
+ * soup_websocket_connection_get_close_code:
+ * @self: the WebSocket
+ *
+ * Get the close code received from the WebSocket peer.
+ *
+ * This only becomes valid once the WebSocket is in the
+ * %SOUP_WEBSOCKET_STATE_CLOSED state. The value will often be in the
+ * #SoupWebsocketCloseCode enumeration, but may also be an application
+ * defined close code.
+ *
+ * Returns: the close code or zero.
+ *
+ * Since: 2.50
+ */
+gushort
+soup_websocket_connection_get_close_code (SoupWebsocketConnection *self)
+{
+       g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), 0);
+
+       return self->pv->peer_close_code;
+}
+
+/**
+ * soup_websocket_connection_get_close_data:
+ * @self: the WebSocket
+ *
+ * Get the close data received from the WebSocket peer.
+ *
+ * This only becomes valid once the WebSocket is in the
+ * %SOUP_WEBSOCKET_STATE_CLOSED state. The data may be freed once
+ * the main loop is run, so copy it if you need to keep it around.
+ *
+ * Returns: the close data or %NULL
+ *
+ * Since: 2.50
+ */
+const char *
+soup_websocket_connection_get_close_data (SoupWebsocketConnection *self)
+{
+       g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
+
+       return self->pv->peer_close_data;
+}
+
+/**
+ * soup_websocket_connection_send_text:
+ * @self: the WebSocket
+ * @text: the message contents
+ *
+ * Send a text (UTF-8) message to the peer.
+ *
+ * The message is queued to be sent and will be sent when the main loop
+ * is run.
+ *
+ * Since: 2.50
+ */
+void
+soup_websocket_connection_send_text (SoupWebsocketConnection *self,
+                                    const char *text)
+{
+       gsize length;
+
+       g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
+       g_return_if_fail (soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_OPEN);
+       g_return_if_fail (text != NULL);
+
+       length = strlen (text);
+       g_return_if_fail (g_utf8_validate (text, length, NULL));
+
+       send_message (self, SOUP_WEBSOCKET_QUEUE_NORMAL, 0x01, (const guint8 *) text, length);
+}
+
+/**
+ * soup_websocket_connection_send_binary:
+ * @self: the WebSocket
+ * @data: (array length=length) (element-type guint8): the message contents
+ * @length: the length of @data
+ *
+ * Send a binary message to the peer.
+ *
+ * The message is queued to be sent and will be sent when the main loop
+ * is run.
+ *
+ * Since: 2.50
+ */
+void
+soup_websocket_connection_send_binary (SoupWebsocketConnection *self,
+                                      gconstpointer data,
+                                      gsize length)
+{
+       g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
+       g_return_if_fail (soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_OPEN);
+       g_return_if_fail (data != NULL);
+
+       send_message (self, SOUP_WEBSOCKET_QUEUE_NORMAL, 0x02, data, length);
+}
+
+/**
+ * soup_websocket_connection_close:
+ * @self: the WebSocket
+ * @code: close code
+ * @data: (allow-none): close data
+ *
+ * Close the connection in an orderly fashion.
+ *
+ * Note that until the #SoupWebsocketConnection::closed signal fires, the connection
+ * is not yet completely closed. The close message is not even sent until the
+ * main loop runs.
+ *
+ * The @code and @data are sent to the peer along with the close request.
+ * Note that the @data must be UTF-8 valid.
+ *
+ * Since: 2.50
+ */
+void
+soup_websocket_connection_close (SoupWebsocketConnection *self,
+                                gushort code,
+                                const char *data)
+{
+       SoupWebsocketQueueFlags flags;
+       SoupWebsocketConnectionPrivate *pv;
+
+       g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
+       pv = self->pv;
+       g_return_if_fail (!pv->close_sent);
+
+       g_return_if_fail (code != SOUP_WEBSOCKET_CLOSE_NO_STATUS &&
+                         code != SOUP_WEBSOCKET_CLOSE_ABNORMAL &&
+                         code != SOUP_WEBSOCKET_CLOSE_TLS_HANDSHAKE);
+       if (pv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER)
+               g_return_if_fail (code != SOUP_WEBSOCKET_CLOSE_NO_EXTENSION);
+       else
+               g_return_if_fail (code != SOUP_WEBSOCKET_CLOSE_SERVER_ERROR);
+
+       g_signal_emit (self, signals[CLOSING], 0);
+
+       if (pv->close_received)
+               g_debug ("responding to close request");
+
+       flags = 0;
+       if (pv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER && pv->close_received)
+               flags |= SOUP_WEBSOCKET_QUEUE_LAST;
+       send_close (self, flags, code, data);
+       close_io_after_timeout (self);
+}
diff --git a/libsoup/soup-websocket-connection.h b/libsoup/soup-websocket-connection.h
new file mode 100644
index 0000000..2f6af00
--- /dev/null
+++ b/libsoup/soup-websocket-connection.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-websocket-connection.h: This file was originally part of Cockpit.
+ *
+ * Copyright 2013, 2014 Red Hat, Inc.
+ *
+ * Cockpit is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * Cockpit 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __SOUP_WEBSOCKET_CONNECTION_H__
+#define __SOUP_WEBSOCKET_CONNECTION_H__
+
+#include <libsoup/soup-types.h>
+#include <libsoup/soup-websocket.h>
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_WEBSOCKET_CONNECTION         (soup_websocket_connection_get_type ())
+#define SOUP_WEBSOCKET_CONNECTION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
SOUP_TYPE_WEBSOCKET_CONNECTION, SoupWebsocketConnection))
+#define SOUP_IS_WEBSOCKET_CONNECTION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
SOUP_TYPE_WEBSOCKET_CONNECTION))
+#define SOUP_WEBSOCKET_CONNECTION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), 
SOUP_TYPE_WEBSOCKET_CONNECTION, SoupWebsocketConnectionClass))
+#define SOUP_WEBSOCKET_CONNECTION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
SOUP_TYPE_WEBSOCKET_CONNECTION, SoupWebsocketConnectionClass))
+#define SOUP_IS_WEBSOCKET_CONNECTION_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
SOUP_TYPE_WEBSOCKET_CONNECTION))
+
+typedef struct _SoupWebsocketConnectionPrivate  SoupWebsocketConnectionPrivate;
+
+struct _SoupWebsocketConnection {
+       GObject parent;
+
+       /*< private >*/
+       SoupWebsocketConnectionPrivate *pv;
+};
+
+typedef struct {
+       GObjectClass parent;
+
+       /* signals */
+       void      (* message)     (SoupWebsocketConnection *self,
+                                  SoupWebsocketDataType type,
+                                  GBytes *message);
+
+       void      (* error)       (SoupWebsocketConnection *self,
+                                  GError *error);
+
+       void      (* closing)     (SoupWebsocketConnection *self);
+
+       void      (* closed)      (SoupWebsocketConnection *self);
+} SoupWebsocketConnectionClass;
+
+SOUP_AVAILABLE_IN_2_50
+GType soup_websocket_connection_get_type (void) G_GNUC_CONST;
+
+SoupWebsocketConnection *soup_websocket_connection_new (GIOStream                    *stream,
+                                                       SoupURI                      *uri,
+                                                       SoupWebsocketConnectionType   type,
+                                                       const char                   *origin,
+                                                       const char                   *protocol);
+
+SOUP_AVAILABLE_IN_2_50
+GIOStream *         soup_websocket_connection_get_io_stream  (SoupWebsocketConnection *self);
+
+SOUP_AVAILABLE_IN_2_50
+SoupWebsocketConnectionType soup_websocket_connection_get_connection_type (SoupWebsocketConnection *self);
+
+SOUP_AVAILABLE_IN_2_50
+SoupURI *           soup_websocket_connection_get_uri        (SoupWebsocketConnection *self);
+
+SOUP_AVAILABLE_IN_2_50
+const char *        soup_websocket_connection_get_origin     (SoupWebsocketConnection *self);
+
+SOUP_AVAILABLE_IN_2_50
+const char *        soup_websocket_connection_get_protocol   (SoupWebsocketConnection *self);
+
+SOUP_AVAILABLE_IN_2_50
+SoupWebsocketState  soup_websocket_connection_get_state      (SoupWebsocketConnection *self);
+
+SOUP_AVAILABLE_IN_2_50
+gushort             soup_websocket_connection_get_close_code (SoupWebsocketConnection *self);
+
+SOUP_AVAILABLE_IN_2_50
+const char *        soup_websocket_connection_get_close_data (SoupWebsocketConnection *self);
+
+SOUP_AVAILABLE_IN_2_50
+void                soup_websocket_connection_send_text      (SoupWebsocketConnection *self,
+                                                             const char *text);
+SOUP_AVAILABLE_IN_2_50
+void                soup_websocket_connection_send_binary    (SoupWebsocketConnection *self,
+                                                             gconstpointer data,
+                                                             gsize length);
+
+SOUP_AVAILABLE_IN_2_50
+void                soup_websocket_connection_close          (SoupWebsocketConnection *self,
+                                                             gushort code,
+                                                             const char *data);
+
+G_END_DECLS
+
+#endif /* __SOUP_WEBSOCKET_CONNECTION_H__ */
diff --git a/libsoup/soup-websocket.c b/libsoup/soup-websocket.c
new file mode 100644
index 0000000..e04030f
--- /dev/null
+++ b/libsoup/soup-websocket.c
@@ -0,0 +1,543 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-websocket.c: This file was originally part of Cockpit.
+ *
+ * Copyright 2013, 2014 Red Hat, Inc.
+ *
+ * Cockpit is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * Cockpit 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include "soup-websocket.h"
+#include "soup-headers.h"
+#include "soup-message.h"
+
+/**
+ * SECTION:soup-websocket
+ * @short_description: The WebSocket Protocol
+ *
+ * #SoupWebsocketConnection provides support for the <ulink
+ * url="http://tools.ietf.org/html/rfc6455";>WebSocket</ulink> protocol.
+ *
+ * soup_websocket_client_prepare_handshake() and
+ * soup_websocket_client_verify_handshake() are low-level functions
+ * for handling the client side of the WebSocket handshake.
+ * soup_websocket_server_process_handshake() is the low-level function
+ * for handling the server side.
+ *
+ * After completing a handshake, you can wrap the underlying
+ * #GIOStream in a #SoupWebsocketConnection, which handles the details
+ * of WebSocket communication. You can then use
+ * soup_websocket_connection_send_text() and
+ * soup_websocket_connection_send_binary() to send data, and the
+ * #SoupWebsocketConnection::message signal to receive data.
+ * (#SoupWebsocketConnection currently only supports asynchronous
+ * I/O.)
+ *
+ * Since: 2.50
+ */
+
+/**
+ * SOUP_WEBSOCKET_ERROR:
+ *
+ * A #GError domain for WebSocket-related errors. Used with
+ * #SoupWebsocketError.
+ *
+ * Since: 2.50
+ */
+
+/**
+ * SoupWebsocketError:
+ * @SOUP_WEBSOCKET_ERROR_FAILED: a generic error
+ * @SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET: attempted to handshake with a
+ *   server that does not appear to understand WebSockets.
+ * @SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE: the WebSocket handshake failed
+ *   because some detail was invalid (eg, incorrect accept key).
+ * @SOUP_WEBSOCKET_ERROR_BAD_ORIGIN: the WebSocket handshake failed
+ *   because the "Origin" header was not an allowed value.
+ *
+ * WebSocket-related errors.
+ *
+ * Since: 2.50
+ */
+
+/**
+ * SoupWebsocketConnectionType:
+ * @SOUP_WEBSOCKET_CONNECTION_UNKNOWN: unknown/invalid connection
+ * @SOUP_WEBSOCKET_CONNECTION_CLIENT: a client-side connection
+ * @SOUP_WEBSOCKET_CONNECTION_SERVER: a server-side connection
+ *
+ * The type of a #SoupWebsocketConnection.
+ *
+ * Since: 2.50
+ */
+
+/**
+ * SoupWebsocketDataType:
+ * @SOUP_WEBSOCKET_DATA_TEXT: UTF-8 text
+ * @SOUP_WEBSOCKET_DATA_BINARY: binary data
+ *
+ * The type of data contained in a #SoupWebsocketConnection::message
+ * signal.
+ *
+ * Since: 2.50
+ */
+
+/**
+ * SoupWebsocketCloseCode:
+ * @SOUP_WEBSOCKET_CLOSE_NORMAL: a normal, non-error close
+ * @SOUP_WEBSOCKET_CLOSE_GOING_AWAY: the client/server is going away
+ * @SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR: a protocol error occurred
+ * @SOUP_WEBSOCKET_CLOSE_UNSUPPORTED_DATA: the endpoint received data
+ *   of a type that it does not support.
+ * @SOUP_WEBSOCKET_CLOSE_NO_STATUS: reserved value indicating that
+ *   no close code was present; must not be sent.
+ * @SOUP_WEBSOCKET_CLOSE_ABNORMAL: reserved value indicating that
+ *   the connection was closed abnormally; must not be sent.
+ * @SOUP_WEBSOCKET_CLOSE_BAD_DATA: the endpoint received data that
+ *   was invalid (eg, non-UTF-8 data in a text message).
+ * @SOUP_WEBSOCKET_CLOSE_POLICY_VIOLATION: generic error code
+ *   indicating some sort of policy violation.
+ * @SOUP_WEBSOCKET_CLOSE_TOO_BIG: the endpoint received a message
+ *   that is too big to process.
+ * @SOUP_WEBSOCKET_CLOSE_NO_EXTENSION: the client is closing the
+ *   connection because the server failed to negotiate a required
+ *   extension.
+ * @SOUP_WEBSOCKET_CLOSE_SERVER_ERROR: the server is closing the
+ *   connection because it was unable to fulfill the request.
+ * @SOUP_WEBSOCKET_CLOSE_TLS_HANDSHAKE: reserved value indicating that
+ *   the TLS handshake failed; must not be sent.
+ *
+ * Pre-defined close codes that can be passed to
+ * soup_websocket_connection_close() or received from
+ * soup_websocket_connection_get_close_code(). (However, other codes
+ * are also allowed.)
+ *
+ * Since: 2.50
+ */
+
+/**
+ * SoupWebsocketState:
+ * @SOUP_WEBSOCKET_STATE_OPEN: the connection is ready to send messages
+ * @SOUP_WEBSOCKET_STATE_CLOSING: the connection is in the process of
+ *   closing down; messages may be received, but not sent
+ * @SOUP_WEBSOCKET_STATE_CLOSED: the connection is completely closed down
+ *
+ * The state of the WebSocket connection.
+ *
+ * Since: 2.50
+ */
+
+GQuark
+soup_websocket_error_get_quark (void)
+{
+       return g_quark_from_static_string ("web-socket-error-quark");
+}
+
+static gboolean
+validate_key (const char *key)
+{
+       guchar buf[18];
+       int state = 0;
+       guint save = 0;
+
+       /* The spec requires us to check that the key is "a
+        * base64-encoded value that, when decoded, is 16 bytes in
+        * length".
+        */
+       if (strlen (key) != 24)
+               return FALSE;
+       if (g_base64_decode_step (key, 24, buf, &state, &save) != 16)
+               return FALSE;
+       return TRUE;
+}
+
+static char *
+compute_accept_key (const char *key)
+{
+       gsize digest_len = 20;
+       guchar digest[digest_len];
+       GChecksum *checksum;
+
+       if (!key)
+               return NULL;
+
+       checksum = g_checksum_new (G_CHECKSUM_SHA1);
+       g_return_val_if_fail (checksum != NULL, NULL);
+
+       g_checksum_update (checksum, (guchar *)key, -1);
+
+       /* magic from: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 */
+       g_checksum_update (checksum, (guchar *)"258EAFA5-E914-47DA-95CA-C5AB0DC85B11", -1);
+
+       g_checksum_get_digest (checksum, digest, &digest_len);
+       g_checksum_free (checksum);
+
+       g_assert (digest_len == 20);
+
+       return g_base64_encode (digest, digest_len);
+}
+
+static gboolean
+choose_subprotocol (SoupMessage  *msg,
+                   const char  **server_protocols,
+                   const char  **chosen_protocol)
+{
+       const char *client_protocols_str;
+       char **client_protocols;
+       int i, j;
+
+       if (chosen_protocol)
+               *chosen_protocol = NULL;
+
+       if (!server_protocols)
+               return TRUE;
+
+       client_protocols_str = soup_message_headers_get_one (msg->request_headers,
+                                                            "Sec-Websocket-Protocol");
+       if (!client_protocols_str)
+               return TRUE;
+
+       client_protocols = g_strsplit_set (client_protocols_str, ", ", -1);
+       if (!client_protocols || !client_protocols[0]) {
+               g_strfreev (client_protocols);
+               return TRUE;
+       }
+
+       for (i = 0; server_protocols[i] != NULL; i++) {
+               for (j = 0; client_protocols[j] != NULL; j++) {
+                       if (g_str_equal (server_protocols[i], client_protocols[j])) {
+                               g_strfreev (client_protocols);
+                               if (chosen_protocol)
+                                       *chosen_protocol = server_protocols[i];
+                               return TRUE;
+                       }
+               }
+       }
+
+       g_strfreev (client_protocols);
+       return FALSE;
+}
+
+/**
+ * soup_websocket_client_prepare_handshake:
+ * @msg: a #SoupMessage
+ * @origin: (allow-none): the "Origin" header to set
+ * @protocols: (allow-none) (array zero-terminated=1): list of
+ *   protocols to offer
+ *
+ * Adds the necessary headers to @msg to request a WebSocket
+ * handshake. The message body and non-WebSocket-related headers are
+ * not modified.
+ *
+ * Since: 2.50
+ */
+void
+soup_websocket_client_prepare_handshake (SoupMessage  *msg,
+                                        const char   *origin,
+                                        char        **protocols)
+{
+       guint32 raw[4];
+       char *key;
+
+       soup_message_headers_replace (msg->request_headers, "Upgrade", "websocket");
+       soup_message_headers_append (msg->request_headers, "Connection", "Upgrade");
+
+       raw[0] = g_random_int ();
+       raw[1] = g_random_int ();
+       raw[2] = g_random_int ();
+       raw[3] = g_random_int ();
+       key = g_base64_encode ((const guchar *)raw, sizeof (raw));
+       soup_message_headers_replace (msg->request_headers, "Sec-WebSocket-Key", key);
+       g_free (key);
+
+       soup_message_headers_replace (msg->request_headers, "Sec-WebSocket-Version", "13");
+
+       if (origin)
+               soup_message_headers_replace (msg->request_headers, "Origin", origin);
+
+       if (protocols) {
+               char *protocols_str;
+
+               protocols_str = g_strjoinv (", ", protocols);
+               soup_message_headers_replace (msg->request_headers,
+                                             "Sec-WebSocket-Protocol", protocols_str);
+               g_free (protocols_str);
+       }
+}
+
+/**
+ * soup_websocket_server_check_handshake:
+ * @msg: #SoupMessage containing the client side of a WebSocket handshake
+ * @origin: (allow-none): expected Origin header
+ * @protocols: (allow-none) (array zero-terminated=1): allowed WebSocket
+ *   protocols.
+ * @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.
+ *
+ * Returns: %TRUE if @msg contained a valid WebSocket handshake,
+ *   %FALSE and an error if not.
+ *
+ * Since: 2.50
+ */
+gboolean
+soup_websocket_server_check_handshake (SoupMessage  *msg,
+                                      const char   *expected_origin,
+                                      char        **protocols,
+                                      GError      **error)
+{
+       const char *origin;
+       const char *key;
+
+       if (msg->method != SOUP_METHOD_GET) {
+               g_set_error_literal (error,
+                                    SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET,
+                                    _("WebSocket handshake expected"));
+               return FALSE;
+       }
+
+       if (!soup_message_headers_header_equals (msg->request_headers, "Upgrade", "websocket") ||
+           !soup_message_headers_header_contains (msg->request_headers, "Connection", "upgrade")) {
+               g_set_error_literal (error,
+                                    SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET,
+                                    _("WebSocket handshake expected"));
+               return FALSE;
+       }
+
+       if (!soup_message_headers_header_equals (msg->request_headers, "Sec-WebSocket-Version", "13")) {
+               g_set_error_literal (error,
+                                    SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
+                                    _("Unsupported WebSocket version"));
+               return FALSE;
+       }
+
+       key = soup_message_headers_get_one (msg->request_headers, "Sec-WebSocket-Key");
+       if (key == NULL || !validate_key (key)) {
+               g_set_error_literal (error,
+                                    SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
+                                    _("Invalid WebSocket key"));
+               return FALSE;
+       }
+
+       if (expected_origin) {
+               origin = soup_message_headers_get_one (msg->request_headers, "Origin");
+               if (!origin || g_ascii_strcasecmp (origin, expected_origin) != 0) {
+                       g_set_error (error,
+                                    SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_ERROR_BAD_ORIGIN,
+                                    _("Incorrect WebSocket \"%s\" header"), "Origin");
+                       return FALSE;
+               }
+       }
+
+       if (!choose_subprotocol (msg, (const char **) protocols, NULL)) {
+               g_set_error_literal (error,
+                                    SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
+                                    _("Unsupported WebSocket subprotocol"));
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+#define RESPONSE_FORBIDDEN "<html><head><title>400 Forbidden</title></head>\r\n" \
+       "<body>Received invalid WebSocket request</body></html>\r\n"
+
+static void
+respond_handshake_forbidden (SoupMessage *msg)
+{
+       soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+       soup_message_headers_append (msg->response_headers, "Connection", "close");
+       soup_message_set_response (msg, "text/html", SOUP_MEMORY_COPY,
+                                  RESPONSE_FORBIDDEN, strlen (RESPONSE_FORBIDDEN));
+}
+
+#define RESPONSE_BAD "<html><head><title>400 Bad Request</title></head>\r\n" \
+       "<body>Received invalid WebSocket request: %s</body></html>\r\n"
+
+static void
+respond_handshake_bad (SoupMessage *msg, const char *why)
+{
+       char *text;
+
+       text = g_strdup_printf (RESPONSE_BAD, why);
+       soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
+       soup_message_headers_append (msg->response_headers, "Connection", "close");
+       soup_message_set_response (msg, "text/html", SOUP_MEMORY_TAKE,
+                                  text, strlen (text));
+}
+
+/**
+ * soup_websocket_server_process_handshake:
+ * @msg: #SoupMessage containing the client side of a WebSocket handshake
+ * @origin: (allow-none): expected Origin header
+ * @protocols: (allow-none) (array zero-terminated=1): allowed WebSocket
+ *   protocols.
+ * @error: return location for a #GError
+ *
+ * Examines the method and request headers in @msg and (assuming @msg
+ * contains a valid handshake request), fills in the handshake
+ * response.
+ *
+ * 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.
+ *
+ * Returns: %TRUE if @msg contained a valid WebSocket handshake
+ *   request and was updated to contain a handshake response. %FALSE
+ *   and an error if not.
+ *
+ * Since: 2.50
+ */
+gboolean
+soup_websocket_server_process_handshake (SoupMessage  *msg,
+                                        const char   *expected_origin,
+                                        char        **protocols)
+{
+       const char *chosen_protocol = NULL;
+       const char *key;
+       char *accept_key;
+       GError *error = NULL;
+
+       if (!soup_websocket_server_check_handshake (msg, expected_origin, protocols, &error)) {
+               if (g_error_matches (error,
+                                    SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_ERROR_BAD_ORIGIN))
+                       respond_handshake_forbidden (msg);
+               else
+                       respond_handshake_bad (msg, error->message);
+               g_error_free (error);
+               return FALSE;
+       }
+
+       soup_message_set_status (msg, SOUP_STATUS_SWITCHING_PROTOCOLS);
+       soup_message_headers_replace (msg->response_headers, "Upgrade", "websocket");
+       soup_message_headers_append (msg->response_headers, "Connection", "Upgrade");
+
+       key = soup_message_headers_get_one (msg->request_headers, "Sec-WebSocket-Key");
+       accept_key = compute_accept_key (key);
+       soup_message_headers_append (msg->response_headers, "Sec-WebSocket-Accept", accept_key);
+       g_free (accept_key);
+
+       choose_subprotocol (msg, (const char **) protocols, &chosen_protocol);
+       if (chosen_protocol)
+               soup_message_headers_append (msg->response_headers, "Sec-WebSocket-Protocol", 
chosen_protocol);
+
+       return TRUE;
+}
+
+/**
+ * soup_websocket_client_verify_handshake:
+ * @msg: #SoupMessage containing both client and server sides of a
+ *   WebSocket handshake
+ * @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).
+ *
+ * Returns: %TRUE if @msg contains a completed valid WebSocket
+ *   handshake, %FALSE and an error if not.
+ *
+ * Since: 2.50
+ */
+gboolean
+soup_websocket_client_verify_handshake (SoupMessage  *msg,
+                                       GError      **error)
+{
+       const char *protocol, *request_protocols, *extensions, *accept_key;
+       char *expected_accept_key;
+       gboolean key_ok;
+
+       if (msg->status_code == SOUP_STATUS_BAD_REQUEST) {
+               g_set_error_literal (error,
+                                    SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
+                                    _("Server rejected WebSocket handshake"));
+               return FALSE;
+       }
+
+       if (msg->status_code != SOUP_STATUS_SWITCHING_PROTOCOLS) {
+               g_set_error_literal (error,
+                                    SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET,
+                                    _("Server ignored WebSocket handshake"));
+               return FALSE;
+       }
+
+       if (!soup_message_headers_header_equals (msg->response_headers, "Upgrade", "websocket") ||
+           !soup_message_headers_header_contains (msg->response_headers, "Connection", "upgrade")) {
+               g_set_error_literal (error,
+                                    SOUP_WEBSOCKET_ERROR,
+                                    SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET,
+                                    _("Server ignored WebSocket handshake"));
+               return FALSE;
+       }
+
+       protocol = soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Protocol");
+       if (protocol) {
+               request_protocols = soup_message_headers_get_one (msg->request_headers, 
"Sec-WebSocket-Protocol");
+               if (!request_protocols ||
+                   !soup_header_contains (request_protocols, protocol)) {
+                       g_set_error_literal (error,
+                                            SOUP_WEBSOCKET_ERROR,
+                                            SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
+                                            _("Server requested unsupported protocol"));
+                       return FALSE;
+               }
+       }
+
+       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;
+       }
+
+       accept_key = soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Accept");
+       expected_accept_key = compute_accept_key (soup_message_headers_get_one (msg->request_headers, 
"Sec-WebSocket-Key"));
+       key_ok = (accept_key && expected_accept_key &&
+                 !g_ascii_strcasecmp (accept_key, expected_accept_key));
+       g_free (expected_accept_key);
+       if (!key_ok) {
+               g_set_error (error,
+                            SOUP_WEBSOCKET_ERROR,
+                            SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
+                            _("Server returned incorrect \"%s\" key"),
+                            "Sec-WebSocket-Accept");
+               return FALSE;
+       }
+
+       return TRUE;
+}
diff --git a/libsoup/soup-websocket.h b/libsoup/soup-websocket.h
new file mode 100644
index 0000000..910b9ae
--- /dev/null
+++ b/libsoup/soup-websocket.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-websocket.h: This file was originally part of Cockpit.
+ *
+ * Copyright 2013, 2014 Red Hat, Inc.
+ *
+ * Cockpit is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * Cockpit 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __SOUP_WEBSOCKET_H__
+#define __SOUP_WEBSOCKET_H__
+
+#include <libsoup/soup-types.h>
+
+G_BEGIN_DECLS
+
+#define SOUP_WEBSOCKET_ERROR (soup_websocket_error_get_quark ())
+SOUP_AVAILABLE_IN_2_50
+GQuark soup_websocket_error_get_quark (void) G_GNUC_CONST;
+
+typedef enum {
+       SOUP_WEBSOCKET_ERROR_FAILED,
+       SOUP_WEBSOCKET_ERROR_NOT_WEBSOCKET,
+       SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE,
+       SOUP_WEBSOCKET_ERROR_BAD_ORIGIN,
+} SoupWebsocketError;
+
+typedef enum {
+       SOUP_WEBSOCKET_CONNECTION_UNKNOWN,
+       SOUP_WEBSOCKET_CONNECTION_CLIENT,
+       SOUP_WEBSOCKET_CONNECTION_SERVER
+} SoupWebsocketConnectionType;
+
+typedef enum {
+       SOUP_WEBSOCKET_DATA_TEXT = 0x01,
+       SOUP_WEBSOCKET_DATA_BINARY = 0x02,
+} SoupWebsocketDataType;
+
+typedef enum {
+       SOUP_WEBSOCKET_CLOSE_NORMAL = 1000,
+       SOUP_WEBSOCKET_CLOSE_GOING_AWAY = 1001,
+       SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR = 1002,
+       SOUP_WEBSOCKET_CLOSE_UNSUPPORTED_DATA = 1003,
+       SOUP_WEBSOCKET_CLOSE_NO_STATUS = 1005,
+       SOUP_WEBSOCKET_CLOSE_ABNORMAL = 1006,
+       SOUP_WEBSOCKET_CLOSE_BAD_DATA = 1007,
+       SOUP_WEBSOCKET_CLOSE_POLICY_VIOLATION = 1008,
+       SOUP_WEBSOCKET_CLOSE_TOO_BIG = 1009,
+       SOUP_WEBSOCKET_CLOSE_NO_EXTENSION = 1010,
+       SOUP_WEBSOCKET_CLOSE_SERVER_ERROR = 1011,
+       SOUP_WEBSOCKET_CLOSE_TLS_HANDSHAKE = 1015,
+} SoupWebsocketCloseCode;
+
+typedef enum {
+       SOUP_WEBSOCKET_STATE_OPEN = 1,
+       SOUP_WEBSOCKET_STATE_CLOSING = 2,
+       SOUP_WEBSOCKET_STATE_CLOSED = 3,
+} SoupWebsocketState;
+
+SOUP_AVAILABLE_IN_2_50
+void     soup_websocket_client_prepare_handshake (SoupMessage  *msg,
+                                                 const char   *origin,
+                                                 char        **protocols);
+
+SOUP_AVAILABLE_IN_2_50
+gboolean soup_websocket_client_verify_handshake  (SoupMessage  *msg,
+                                                 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_50
+gboolean soup_websocket_server_process_handshake (SoupMessage  *msg,
+                                                 const char   *origin,
+                                                 char        **protocols);
+
+G_END_DECLS
+
+#endif /* __SOUP_WEBSOCKET_H__ */
diff --git a/libsoup/soup.h b/libsoup/soup.h
index 82a2632..7106cc5 100644
--- a/libsoup/soup.h
+++ b/libsoup/soup.h
@@ -50,6 +50,8 @@ extern "C" {
 #include <libsoup/soup-uri.h>
 #include <libsoup/soup-value-utils.h>
 #include <libsoup/soup-version.h>
+#include <libsoup/soup-websocket.h>
+#include <libsoup/soup-websocket-connection.h>
 #include <libsoup/soup-xmlrpc.h>
 
 #ifdef __cplusplus
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ece9e95..3cd20bf 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -8,4 +8,5 @@ libsoup/soup-request.c
 libsoup/soup-server.c
 libsoup/soup-session.c
 libsoup/soup-socket.c
+libsoup/soup-websocket.c
 libsoup/soup-tld.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index a8b9d01..662ab79 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -41,6 +41,7 @@ test_programs =                       \
        timeout-test            \
        tld-test                \
        uri-parsing             \
+       websocket-test          \
        xmlrpc-server-test      \
        xmlrpc-test
 
diff --git a/tests/header-parsing.c b/tests/header-parsing.c
index fdc7885..7bea1e9 100644
--- a/tests/header-parsing.c
+++ b/tests/header-parsing.c
@@ -608,6 +608,30 @@ static struct ResponseTest {
            { NULL } }
        },
 
+       /********************************/
+       /*** VALID CONTINUE RESPONSES ***/
+       /********************************/
+
+       /* Tests from Cockpit project */
+
+       { "Response w/ 101 Switching Protocols + spaces after new line", NULL,
+         "HTTP/1.0 101 Switching Protocols\r\n  \r\n", 38,
+         SOUP_HTTP_1_0, SOUP_STATUS_SWITCHING_PROTOCOLS, "Switching Protocols",
+         { { NULL } }
+       },
+
+       { "Response w/ 101 Switching Protocols missing \\r + spaces", NULL,
+         "HTTP/1.0  101  Switching Protocols\r\n  \r\n", 40,
+         SOUP_HTTP_1_0, SOUP_STATUS_SWITCHING_PROTOCOLS, "Switching Protocols",
+         { { NULL } }
+       },
+
+       { "Response w/ 101 Switching Protocols + spaces after & before new line", NULL,
+         "HTTP/1.1  101  Switching Protocols  \r\n  \r\n", 42,
+         SOUP_HTTP_1_1, SOUP_STATUS_SWITCHING_PROTOCOLS, "Switching Protocols",
+         { { NULL } }
+       },
+
        /*************************/
        /*** INVALID RESPONSES ***/
        /*************************/
@@ -689,6 +713,45 @@ static struct ResponseTest {
          -1, 0, NULL,
          { { NULL } }
        },
+
+       /* Failing test from Cockpit */
+
+       { "Partial response stops after HTTP/", NULL,
+         "HTTP/", -1,
+         -1, 0, NULL,
+         { { NULL } }
+       },
+
+       { "Space before HTTP/", NULL,
+         " HTTP/1.0 101 Switching Protocols\r\n  ", -1,
+         -1, 0, NULL,
+         { { NULL } }
+       },
+
+       { "Missing reason", NULL,
+         "HTTP/1.0  101\r\n  ", -1,
+         -1, 0, NULL,
+         { { NULL } }
+       },
+
+       { "Response code containing alphabetic character", NULL,
+         "HTTP/1.1  1A01  Switching Protocols  \r\n  ", -1,
+         -1, 0, NULL,
+         { { NULL } }
+       },
+
+       { "TESTONE\\r\\n", NULL,
+         "TESTONE\r\n  ", -1,
+         -1, 0, NULL,
+         { { NULL } }
+       },
+
+       { "Response w/ 3 headers truncated", NULL,
+         "HTTP/1.0 200 ok\r\nHeader1: value3\r\nHeader2:  field\r\nHead3:  Anothe", -1,
+         -1, 0, NULL,
+         { { NULL }
+         }
+       },
 };
 static const int num_resptests = G_N_ELEMENTS (resptests);
 
diff --git a/tests/websocket-test.c b/tests/websocket-test.c
new file mode 100644
index 0000000..cc9bdcd
--- /dev/null
+++ b/tests/websocket-test.c
@@ -0,0 +1,692 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This file was originally part of Cockpit.
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * Cockpit is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * Cockpit 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "test-utils.h"
+
+#include <sys/socket.h>
+
+typedef struct {
+       GSocket *listener;
+       gushort port;
+
+       SoupWebsocketConnection *client;
+       SoupWebsocketConnection *server;
+
+       gboolean no_server;
+       GIOStream *raw_server;
+
+       GMutex mutex;
+} Test;
+
+#define WAIT_UNTIL(cond)                                       \
+       G_STMT_START                                            \
+       while (!(cond)) g_main_context_iteration (NULL, TRUE);  \
+       G_STMT_END
+
+static void
+on_error_not_reached (SoupWebsocketConnection *ws,
+                      GError *error,
+                      gpointer user_data)
+{
+       /* At this point we know this will fail, but is informative */
+       g_assert_no_error (error);
+}
+
+static void
+on_error_copy (SoupWebsocketConnection *ws,
+               GError *error,
+               gpointer user_data)
+{
+       GError **copy = user_data;
+       g_assert (*copy == NULL);
+       *copy = g_error_copy (error);
+}
+
+static void
+setup_listener (Test *test)
+{
+       GSocketAddress *addr;
+       GError *error = NULL;
+
+       test->listener = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                                      G_SOCKET_TYPE_STREAM,
+                                      G_SOCKET_PROTOCOL_TCP,
+                                      &error);
+       g_assert_no_error (error);
+
+       addr = g_inet_socket_address_new_from_string ("127.0.0.1", 0);
+       g_assert_no_error (error);
+
+       g_socket_bind (test->listener, addr, TRUE, &error);
+       g_assert_no_error (error);
+       g_object_unref (addr);
+
+       addr = g_socket_get_local_address (test->listener, &error);
+       g_assert_no_error (error);
+
+       test->port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
+       g_object_unref (addr);
+
+       g_socket_listen (test->listener, &error);
+       g_assert_no_error (error);
+}
+
+static void
+direct_connection_complete (GObject *object,
+                           GAsyncResult *result,
+                           gpointer user_data)
+{
+       Test *test = user_data;
+       GSocketConnection *conn;
+       SoupURI *uri;
+       GError *error = 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);
+       soup_uri_free (uri);
+}
+
+static gboolean
+got_connection (GSocket *listener,
+               GIOCondition cond,
+               gpointer user_data)
+{
+       Test *test = user_data;
+       GSocket *sock;
+       GSocketConnection *conn;
+       SoupURI *uri;
+       GError *error = NULL;
+
+       sock = g_socket_accept (listener, NULL, &error);
+       g_assert_no_error (error);
+
+       conn = g_socket_connection_factory_create_connection (sock);
+       g_assert (conn != NULL);
+       g_object_unref (sock);
+
+       if (test->no_server)
+               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);
+               soup_uri_free (uri);
+       }
+
+       return FALSE;
+}
+
+static void
+setup_direct_connection (Test *test,
+                        gconstpointer data)
+{
+       GSocketClient *client;
+       GSource *listen_source;
+
+       setup_listener (test);
+
+       client = g_socket_client_new ();
+       g_socket_client_connect_to_host_async (client, "127.0.0.1", test->port,
+                                              NULL, direct_connection_complete, test);
+
+       listen_source = g_socket_create_source (test->listener, G_IO_IN, NULL);
+       g_source_set_callback (listen_source, (GSourceFunc) got_connection, test, NULL);
+       g_source_attach (listen_source, NULL);
+
+       while (test->client == NULL || (test->server == NULL && !test->no_server))
+               g_main_context_iteration (NULL, TRUE);
+       
+       g_source_destroy (listen_source);
+       g_source_unref (listen_source);
+       g_object_unref (client);
+}
+
+static void
+setup_half_direct_connection (Test *test,
+                             gconstpointer data)
+{
+       test->no_server = TRUE;
+       setup_direct_connection (test, data);
+}
+
+static void
+teardown_direct_connection (Test *test,
+                           gconstpointer data)
+{
+       g_clear_object (&test->listener);
+       g_clear_object (&test->client);
+       g_clear_object (&test->server);
+       g_clear_object (&test->raw_server);
+}
+
+static void
+on_text_message (SoupWebsocketConnection *ws,
+                 SoupWebsocketDataType type,
+                 GBytes *message,
+                 gpointer user_data)
+{
+       GBytes **receive = user_data;
+
+       g_assert_cmpint (type, ==, SOUP_WEBSOCKET_DATA_TEXT);
+       g_assert (*receive == NULL);
+       g_assert (message != NULL);
+
+       *receive = g_bytes_ref (message);
+}
+
+static void
+on_close_set_flag (SoupWebsocketConnection *ws,
+                   gpointer user_data)
+{
+       gboolean *flag = user_data;
+
+       g_assert (*flag == FALSE);
+
+       *flag = TRUE;
+}
+
+
+#define TEST_STRING "this is a test"
+
+static void
+test_send_client_to_server (Test *test,
+                            gconstpointer data)
+{
+       GBytes *received = NULL;
+       const char *contents;
+       gsize len;
+
+       g_signal_connect (test->server, "message", G_CALLBACK (on_text_message), &received);
+
+       soup_websocket_connection_send_text (test->client, TEST_STRING);
+
+       WAIT_UNTIL (received != NULL);
+
+       /* Received messages should be null terminated (outside of len) */
+       contents = g_bytes_get_data (received, &len);
+       g_assert_cmpstr (contents, ==, TEST_STRING);
+       g_assert_cmpint (len, ==, strlen (TEST_STRING));
+
+       g_bytes_unref (received);
+}
+
+static void
+test_send_server_to_client (Test *test,
+                            gconstpointer data)
+{
+       GBytes *received = NULL;
+       const char *contents;
+       gsize len;
+
+       g_signal_connect (test->client, "message", G_CALLBACK (on_text_message), &received);
+
+       soup_websocket_connection_send_text (test->server, TEST_STRING);
+
+       WAIT_UNTIL (received != NULL);
+
+       /* Received messages should be null terminated (outside of len) */
+       contents = g_bytes_get_data (received, &len);
+       g_assert_cmpstr (contents, ==, TEST_STRING);
+       g_assert_cmpint (len, ==, strlen (TEST_STRING));
+
+       g_bytes_unref (received);
+}
+
+static void
+test_send_big_packets (Test *test,
+                       gconstpointer data)
+{
+       GBytes *sent = NULL;
+       GBytes *received = NULL;
+
+       g_signal_connect (test->client, "message", G_CALLBACK (on_text_message), &received);
+
+       sent = g_bytes_new_take (g_strnfill (400, '!'), 400);
+       soup_websocket_connection_send_text (test->server, g_bytes_get_data (sent, NULL));
+       WAIT_UNTIL (received != NULL);
+       g_assert (g_bytes_equal (sent, received));
+       g_bytes_unref (sent);
+       g_bytes_unref (received);
+       received = NULL;
+
+       sent = g_bytes_new_take (g_strnfill (100 * 1000, '?'), 100 * 1000);
+       soup_websocket_connection_send_text (test->server, g_bytes_get_data (sent, NULL));
+       WAIT_UNTIL (received != NULL);
+       g_assert (g_bytes_equal (sent, received));
+       g_bytes_unref (sent);
+       g_bytes_unref (received);
+}
+
+static void
+test_send_bad_data (Test *test,
+                    gconstpointer unused)
+{
+       GError *error = NULL;
+       GIOStream *io;
+       gsize written;
+       const char *frame;
+
+       g_signal_handlers_disconnect_by_func (test->server, on_error_not_reached, NULL);
+       g_signal_connect (test->server, "error", G_CALLBACK (on_error_copy), &error);
+
+       io = soup_websocket_connection_get_io_stream (test->client);
+
+       /* Bad UTF-8 frame */
+       frame = "\x81\x04\xEE\xEE\xEE\xEE";
+       if (!g_output_stream_write_all (g_io_stream_get_output_stream (io),
+                                       frame, 6, &written, NULL, NULL))
+               g_assert_not_reached ();
+       g_assert_cmpuint (written, ==, 6);
+
+       WAIT_UNTIL (error != NULL);
+       g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_BAD_DATA);
+       g_clear_error (&error);
+
+       WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED);
+
+       g_assert_cmpuint (soup_websocket_connection_get_close_code (test->client), ==, 
SOUP_WEBSOCKET_CLOSE_BAD_DATA);
+}
+
+static const char *negotiate_client_protocols[] = { "bbb", "ccc", NULL };
+static const char *negotiate_server_protocols[] = { "aaa", "bbb", "ccc", NULL };
+static const char *negotiated_protocol = "bbb";
+
+static void
+test_protocol_negotiate_direct (Test *test,
+                               gconstpointer unused)
+{
+       SoupMessage *msg;
+       gboolean ok;
+       const char *protocol;
+       GError *error = NULL;
+
+       msg = soup_message_new ("GET", "http://127.0.0.1";);
+       soup_websocket_client_prepare_handshake (msg, NULL,
+                                                (char **) negotiate_client_protocols);
+
+       ok = soup_websocket_server_check_handshake (msg, NULL,
+                                                   (char **) negotiate_server_protocols,
+                                                   &error);
+       g_assert_no_error (error);
+       g_assert_true (ok);
+
+       ok = soup_websocket_server_process_handshake (msg, NULL,
+                                                     (char **) negotiate_server_protocols);
+       g_assert_true (ok);
+
+       protocol = soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Protocol");
+       g_assert_cmpstr (protocol, ==, negotiated_protocol);
+
+       ok = soup_websocket_client_verify_handshake (msg, &error);
+       g_assert_no_error (error);
+       g_assert_true (ok);
+
+       g_object_unref (msg);
+}
+
+static const char *mismatch_client_protocols[] = { "ddd", NULL };
+static const char *mismatch_server_protocols[] = { "aaa", "bbb", "ccc", NULL };
+
+static void
+test_protocol_mismatch_direct (Test *test,
+                              gconstpointer unused)
+{
+       SoupMessage *msg;
+       gboolean ok;
+       const char *protocol;
+       GError *error = NULL;
+
+       msg = soup_message_new ("GET", "http://127.0.0.1";);
+       soup_websocket_client_prepare_handshake (msg, NULL,
+                                                (char **) mismatch_client_protocols);
+
+       ok = soup_websocket_server_check_handshake (msg, NULL,
+                                                   (char **) mismatch_server_protocols,
+                                                   &error);
+       g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE);
+       g_clear_error (&error);
+       g_assert_false (ok);
+
+       ok = soup_websocket_server_process_handshake (msg, NULL,
+                                                     (char **) mismatch_server_protocols);
+       g_assert_false (ok);
+       soup_test_assert_message_status (msg, SOUP_STATUS_BAD_REQUEST);
+
+       protocol = soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Protocol");
+       g_assert_cmpstr (protocol, ==, NULL);
+
+       ok = soup_websocket_client_verify_handshake (msg, &error);
+       g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_ERROR_BAD_HANDSHAKE);
+       g_clear_error (&error);
+       g_assert_false (ok);
+
+       g_object_unref (msg);
+}
+
+static const char *all_protocols[] = { "aaa", "bbb", "ccc", NULL };
+
+static void
+test_protocol_server_any_direct (Test *test,
+                                gconstpointer unused)
+{
+       SoupMessage *msg;
+       gboolean ok;
+       const char *protocol;
+       GError *error = NULL;
+
+       msg = soup_message_new ("GET", "http://127.0.0.1";);
+       soup_websocket_client_prepare_handshake (msg, NULL, (char **) all_protocols);
+
+       ok = soup_websocket_server_check_handshake (msg, NULL, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_true (ok);
+
+       ok = soup_websocket_server_process_handshake (msg, NULL, NULL);
+       g_assert_true (ok);
+
+       protocol = soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Protocol");
+       g_assert_cmpstr (protocol, ==, NULL);
+
+       ok = soup_websocket_client_verify_handshake (msg, &error);
+       g_assert_no_error (error);
+       g_assert_true (ok);
+
+       g_object_unref (msg);
+}
+
+static void
+test_protocol_client_any_direct (Test *test,
+                                gconstpointer unused)
+{
+       SoupMessage *msg;
+       gboolean ok;
+       const char *protocol;
+       GError *error = NULL;
+
+       msg = soup_message_new ("GET", "http://127.0.0.1";);
+       soup_websocket_client_prepare_handshake (msg, NULL, NULL);
+
+       ok = soup_websocket_server_check_handshake (msg, NULL, (char **) all_protocols, &error);
+       g_assert_no_error (error);
+       g_assert_true (ok);
+
+       ok = soup_websocket_server_process_handshake (msg, NULL, (char **) all_protocols);
+       g_assert_true (ok);
+
+       protocol = soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Protocol");
+       g_assert_cmpstr (protocol, ==, NULL);
+
+       ok = soup_websocket_client_verify_handshake (msg, &error);
+       g_assert_no_error (error);
+       g_assert_true (ok);
+
+       g_object_unref (msg);
+}
+
+static void
+test_close_clean_client (Test *test,
+                         gconstpointer data)
+{
+       gboolean close_event_client = FALSE;
+       gboolean close_event_server = FALSE;
+
+       g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event_client);
+       g_signal_connect (test->server, "closed", G_CALLBACK (on_close_set_flag), &close_event_server);
+
+       soup_websocket_connection_close (test->client, SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "give me a reason");
+       g_assert_cmpint (soup_websocket_connection_get_state (test->client), ==, 
SOUP_WEBSOCKET_STATE_CLOSING);
+
+       WAIT_UNTIL (soup_websocket_connection_get_state (test->server) == SOUP_WEBSOCKET_STATE_CLOSED);
+       WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED);
+
+       g_assert (close_event_client);
+       g_assert (close_event_server);
+
+       g_assert_cmpint (soup_websocket_connection_get_close_code (test->client), ==, 
SOUP_WEBSOCKET_CLOSE_GOING_AWAY);
+       g_assert_cmpint (soup_websocket_connection_get_close_code (test->server), ==, 
SOUP_WEBSOCKET_CLOSE_GOING_AWAY);
+       g_assert_cmpstr (soup_websocket_connection_get_close_data (test->server), ==, "give me a reason");
+}
+
+static void
+test_close_clean_server (Test *test,
+                         gconstpointer data)
+{
+       gboolean close_event_client = FALSE;
+       gboolean close_event_server = FALSE;
+
+       g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event_client);
+       g_signal_connect (test->server, "closed", G_CALLBACK (on_close_set_flag), &close_event_server);
+
+       soup_websocket_connection_close (test->server, SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "another reason");
+       g_assert_cmpint (soup_websocket_connection_get_state (test->server), ==, 
SOUP_WEBSOCKET_STATE_CLOSING);
+
+       WAIT_UNTIL (soup_websocket_connection_get_state (test->server) == SOUP_WEBSOCKET_STATE_CLOSED);
+       WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED);
+
+       g_assert (close_event_client);
+       g_assert (close_event_server);
+
+       g_assert_cmpint (soup_websocket_connection_get_close_code (test->server), ==, 
SOUP_WEBSOCKET_CLOSE_GOING_AWAY);
+       g_assert_cmpint (soup_websocket_connection_get_close_code (test->client), ==, 
SOUP_WEBSOCKET_CLOSE_GOING_AWAY);
+       g_assert_cmpstr (soup_websocket_connection_get_close_data (test->client), ==, "another reason");
+}
+
+static gboolean
+on_closing_send_message (SoupWebsocketConnection *ws,
+                         gpointer data)
+{
+       GBytes *message = data;
+
+       soup_websocket_connection_send_text (ws, g_bytes_get_data (message, NULL));
+       g_signal_handlers_disconnect_by_func (ws, on_closing_send_message, data);
+       return TRUE;
+}
+
+static void
+test_message_after_closing (Test *test,
+                            gconstpointer data)
+{
+       gboolean close_event_client = FALSE;
+       gboolean close_event_server = FALSE;
+       GBytes *received = NULL;
+       GBytes *message;
+
+       message = g_bytes_new_static ("another test because", strlen ("another test because"));
+       g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event_client);
+       g_signal_connect (test->client, "message", G_CALLBACK (on_text_message), &received);
+       g_signal_connect (test->server, "closed", G_CALLBACK (on_close_set_flag), &close_event_server);
+       g_signal_connect (test->server, "closing", G_CALLBACK (on_closing_send_message), message);
+
+       soup_websocket_connection_close (test->client, SOUP_WEBSOCKET_CLOSE_GOING_AWAY, "another reason");
+       g_assert_cmpint (soup_websocket_connection_get_state (test->client), ==, 
SOUP_WEBSOCKET_STATE_CLOSING);
+
+       WAIT_UNTIL (soup_websocket_connection_get_state (test->server) == SOUP_WEBSOCKET_STATE_CLOSED);
+       WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED);
+
+       g_assert (close_event_client);
+       g_assert (close_event_server);
+
+       g_assert (received != NULL);
+       g_assert (g_bytes_equal (message, received));
+
+       g_bytes_unref (received);
+       g_bytes_unref (message);
+}
+
+static gpointer
+timeout_server_thread (gpointer user_data)
+{
+       Test *test = user_data;
+       GError *error = NULL;
+
+       /* don't close until the client has timed out */
+       g_mutex_lock (&test->mutex);
+       g_mutex_unlock (&test->mutex);
+
+       g_io_stream_close (test->raw_server, NULL, &error);
+       g_assert_no_error (error);
+
+       return NULL;
+}
+
+static void
+test_close_after_timeout (Test *test,
+                         gconstpointer data)
+{
+       gboolean close_event = FALSE;
+       GThread *thread;
+
+       g_mutex_lock (&test->mutex);
+
+       /* Note that no real server is around in this test, so no close happens */
+       thread = g_thread_new ("timeout-thread", timeout_server_thread, test);
+
+       g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event);
+       g_signal_connect (test->client, "error", G_CALLBACK (on_error_not_reached), NULL);
+
+       /* Now try and close things */
+       soup_websocket_connection_close (test->client, 0, NULL);
+       g_assert_cmpint (soup_websocket_connection_get_state (test->client), ==, 
SOUP_WEBSOCKET_STATE_CLOSING);
+
+       WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED);
+
+       g_assert (close_event == TRUE);
+
+       /* Now actually close the server side stream */
+       g_mutex_unlock (&test->mutex);
+       g_thread_join (thread);
+}
+
+static gpointer
+send_fragments_server_thread (gpointer user_data)
+{
+       Test *test = user_data;
+       gsize written;
+       const char fragments[] = "\x01\x04""one "   /* !fin | opcode */
+               "\x00\x04""two "   /* !fin | no opcode */
+               "\x80\x05""three"; /* fin  | no opcode */
+       GError *error = NULL;
+
+       g_output_stream_write_all (g_io_stream_get_output_stream (test->raw_server),
+                                  fragments, sizeof (fragments) -1, &written, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_cmpuint (written, ==, sizeof (fragments) - 1);
+       g_io_stream_close (test->raw_server, NULL, &error);
+       g_assert_no_error (error);
+
+       return NULL;
+}
+
+static void
+test_receive_fragmented (Test *test,
+                        gconstpointer data)
+{
+       GThread *thread;
+       GBytes *received = NULL;
+       GBytes *expect;
+
+       thread = g_thread_new ("fragment-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);
+
+       WAIT_UNTIL (received != NULL);
+       expect = g_bytes_new ("one two three", 13);
+       g_assert (g_bytes_equal (expect, received));
+       g_bytes_unref (expect);
+       g_bytes_unref (received);
+
+       g_thread_join (thread);
+}
+
+int
+main (int argc,
+      char *argv[])
+{
+       int ret;
+
+       test_init (argc, argv, NULL);
+
+       g_test_add ("/websocket/direct/send-client-to-server", Test, NULL,
+                   setup_direct_connection,
+                   test_send_client_to_server,
+                   teardown_direct_connection);
+       g_test_add ("/websocket/direct/send-server-to-client", Test, NULL,
+                   setup_direct_connection,
+                   test_send_server_to_client,
+                   teardown_direct_connection);
+       g_test_add ("/websocket/direct/send-big-packets", Test, NULL,
+                   setup_direct_connection,
+                   test_send_big_packets,
+                   teardown_direct_connection);
+       g_test_add ("/websocket/direct/send-bad-data", Test, NULL,
+                   setup_direct_connection,
+                   test_send_bad_data,
+                   teardown_direct_connection);
+       g_test_add ("/websocket/direct/close-clean-client", Test, NULL,
+                   setup_direct_connection,
+                   test_close_clean_client,
+                   teardown_direct_connection);
+       g_test_add ("/websocket/direct/close-clean-server", Test, NULL,
+                   setup_direct_connection,
+                   test_close_clean_server,
+                   teardown_direct_connection);
+       g_test_add ("/websocket/direct/message-after-closing", Test, NULL,
+                   setup_direct_connection,
+                   test_message_after_closing,
+                   teardown_direct_connection);
+
+       g_test_add ("/websocket/direct/protocol-negotiate", Test, NULL, NULL,
+                   test_protocol_negotiate_direct,
+                   NULL);
+       g_test_add ("/websocket/direct/protocol-mismatch", Test, NULL, NULL,
+                   test_protocol_mismatch_direct,
+                   NULL);
+       g_test_add ("/websocket/direct/protocol-server-any", Test, NULL, NULL,
+                   test_protocol_server_any_direct,
+                   NULL);
+       g_test_add ("/websocket/direct/protocol-client-any", Test, NULL, NULL,
+                   test_protocol_client_any_direct,
+                   NULL);
+
+       g_test_add ("/websocket/direct/receive-fragmented", Test, NULL,
+                   setup_half_direct_connection,
+                   test_receive_fragmented,
+                   teardown_direct_connection);
+
+       if (g_test_slow ()) {
+               g_test_add ("/websocket/direct/close-after-timeout", Test, NULL,
+                           setup_half_direct_connection,
+                           test_close_after_timeout,
+                           teardown_direct_connection);
+       }
+
+       ret = g_test_run ();
+
+       test_cleanup ();
+       return ret;
+}


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