mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2026-01-08 21:14:11 +00:00
Add support for clients connecting with the WebSocket protocol.
We do this by auto detecting the inbound http(s) 'GET' and probing for a well formulated WebSocket binary connection, such as used by the spice-html5 client. If detected, we implement a set of cover functions that abstract the read/write/writev functions, in a fashion similar to the SASL implementation. This includes a limited implementation of the WebSocket protocol, sufficient for our purposes. Signed-off-by: Jeremy White <jwhite@codeweavers.com> Acked-by: Frediano Ziglio <fziglio@redhat.com>
This commit is contained in:
parent
214736dce6
commit
cd2a317096
@ -177,6 +177,8 @@ libserver_la_SOURCES = \
|
||||
video-encoder.h \
|
||||
video-stream.c \
|
||||
video-stream.h \
|
||||
websocket.c \
|
||||
websocket.h \
|
||||
zlib-encoder.c \
|
||||
zlib-encoder.h \
|
||||
$(NULL)
|
||||
|
||||
@ -144,6 +144,8 @@ spice_server_sources = [
|
||||
'video-encoder.h',
|
||||
'video-stream.c',
|
||||
'video-stream.h',
|
||||
'websocket.c',
|
||||
'websocket.h',
|
||||
'zlib-encoder.c',
|
||||
'zlib-encoder.h',
|
||||
]
|
||||
|
||||
@ -39,6 +39,7 @@
|
||||
#include "red-common.h"
|
||||
#include "red-stream.h"
|
||||
#include "reds.h"
|
||||
#include "websocket.h"
|
||||
|
||||
// compatibility for *BSD systems
|
||||
#if !defined(TCP_CORK) && !defined(_WIN32)
|
||||
@ -77,6 +78,17 @@ typedef struct RedSASL {
|
||||
} RedSASL;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int closed;
|
||||
|
||||
websocket_frame_t read_frame;
|
||||
uint64_t write_remainder;
|
||||
|
||||
ssize_t (*raw_read)(RedStream *s, void *buf, size_t nbyte);
|
||||
ssize_t (*raw_write)(RedStream *s, const void *buf, size_t nbyte);
|
||||
ssize_t (*raw_writev)(RedStream *s, const struct iovec *iov, int iovcnt);
|
||||
} RedsWebSocket;
|
||||
|
||||
struct RedStreamPrivate {
|
||||
SSL *ssl;
|
||||
|
||||
@ -86,6 +98,8 @@ struct RedStreamPrivate {
|
||||
|
||||
AsyncRead async_read;
|
||||
|
||||
RedsWebSocket *ws;
|
||||
|
||||
/* life time of info:
|
||||
* allocated when creating RedStream.
|
||||
* deallocated when main_dispatcher handles the SPICE_CHANNEL_EVENT_DISCONNECTED
|
||||
@ -433,6 +447,8 @@ void red_stream_free(RedStream *s)
|
||||
SSL_free(s->priv->ssl);
|
||||
}
|
||||
|
||||
g_free(s->priv->ws);
|
||||
|
||||
red_stream_remove_watch(s);
|
||||
socket_close(s->socket);
|
||||
|
||||
@ -1155,3 +1171,105 @@ error:
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
static ssize_t stream_websocket_read(RedStream *s, void *buf, size_t size)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (s->priv->ws->closed)
|
||||
return 0;
|
||||
|
||||
rc = websocket_read((void *)s, buf, size, &s->priv->ws->read_frame,
|
||||
(websocket_read_cb_t) s->priv->ws->raw_read,
|
||||
(websocket_write_cb_t) s->priv->ws->raw_write);
|
||||
|
||||
if (rc == 0)
|
||||
s->priv->ws->closed = 1;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t stream_websocket_write(RedStream *s, const void *buf, size_t size)
|
||||
{
|
||||
if (s->priv->ws->closed) {
|
||||
errno = EPIPE;
|
||||
return -1;
|
||||
}
|
||||
return websocket_write((void *)s, buf, size, &s->priv->ws->write_remainder,
|
||||
(websocket_write_cb_t) s->priv->ws->raw_write);
|
||||
}
|
||||
|
||||
static ssize_t stream_websocket_writev(RedStream *s, const struct iovec *iov, int iovcnt)
|
||||
{
|
||||
if (s->priv->ws->closed) {
|
||||
errno = EPIPE;
|
||||
return -1;
|
||||
}
|
||||
return websocket_writev((void *)s, (struct iovec *) iov, iovcnt, &s->priv->ws->write_remainder,
|
||||
(websocket_writev_cb_t) s->priv->ws->raw_writev);
|
||||
}
|
||||
|
||||
/*
|
||||
If we detect that a newly opened stream appears to be using
|
||||
the WebSocket protocol, we will put in place cover functions
|
||||
that will speak WebSocket to the client, but allow the server
|
||||
to continue to use normal stream read/write/writev semantics.
|
||||
*/
|
||||
bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len)
|
||||
{
|
||||
char rbuf[4096];
|
||||
int rc;
|
||||
|
||||
if (stream->priv->ws) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(rbuf, buf, len);
|
||||
rc = stream->priv->read(stream, rbuf + len, sizeof(rbuf) - len - 1);
|
||||
if (rc <= 0) {
|
||||
return false;
|
||||
}
|
||||
len += rc;
|
||||
rbuf[len] = 0;
|
||||
|
||||
/* TODO: this has a theoretical flaw around packet buffering
|
||||
that is not likely to occur in practice. That is,
|
||||
to be fully correct, we should repeatedly read bytes until
|
||||
either we get the end of the GET header (\r\n\r\n), or until
|
||||
an amount of time has passed. Instead, we just read for
|
||||
16 bytes, and then read up to the sizeof rbuf. So if the
|
||||
GET request is only partially complete at this point we
|
||||
will fail.
|
||||
|
||||
A typical GET request is 520 bytes, and it's difficult to
|
||||
imagine a real world case where that will come in fragmented
|
||||
such that we trigger this failure. Further, the spice reds
|
||||
code has no real mechanism to do variable length/time based reads,
|
||||
so it seems wisest to live with this theoretical flaw.
|
||||
*/
|
||||
|
||||
if (websocket_is_start(rbuf)) {
|
||||
char outbuf[1024];
|
||||
|
||||
websocket_create_reply(rbuf, outbuf);
|
||||
rc = stream->priv->write(stream, outbuf, strlen(outbuf));
|
||||
if (rc == strlen(outbuf)) {
|
||||
stream->priv->ws = g_malloc0(sizeof(*stream->priv->ws));
|
||||
|
||||
stream->priv->ws->raw_read = stream->priv->read;
|
||||
stream->priv->ws->raw_write = stream->priv->write;
|
||||
|
||||
stream->priv->read = stream_websocket_read;
|
||||
stream->priv->write = stream_websocket_write;
|
||||
|
||||
if (stream->priv->writev) {
|
||||
stream->priv->ws->raw_writev = stream->priv->writev;
|
||||
stream->priv->writev = stream_websocket_writev;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -91,6 +91,8 @@ bool red_stream_set_auto_flush(RedStream *stream, bool auto_flush);
|
||||
*/
|
||||
void red_stream_flush(RedStream *stream);
|
||||
|
||||
bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len);
|
||||
|
||||
typedef enum {
|
||||
RED_SASL_ERROR_OK,
|
||||
RED_SASL_ERROR_GENERIC,
|
||||
|
||||
@ -2418,6 +2418,7 @@ static void reds_handle_link_error(void *opaque, int err)
|
||||
reds_link_free(link);
|
||||
}
|
||||
|
||||
static void reds_handle_new_link(RedLinkInfo *link);
|
||||
static void reds_handle_read_header_done(void *opaque)
|
||||
{
|
||||
RedLinkInfo *link = (RedLinkInfo *)opaque;
|
||||
@ -2460,6 +2461,18 @@ static void reds_handle_read_magic_done(void *opaque)
|
||||
const SpiceLinkHeader *header = &link->link_header;
|
||||
|
||||
if (header->magic != SPICE_MAGIC) {
|
||||
/* Attempt to detect and support a WebSocket connection,
|
||||
which will be proceeded by a variable length GET style request.
|
||||
We cannot know we are dealing with a WebSocket connection
|
||||
until we have read at least 3 bytes, and we will have to
|
||||
read many more bytes than are contained in a SpiceLinkHeader.
|
||||
So we may as well read a SpiceLinkHeader's worth of data, and if it's
|
||||
clear that a WebSocket connection was requested, we switch
|
||||
before proceeding further. */
|
||||
if (red_stream_is_websocket(link->stream, &header->magic, sizeof(header->magic))) {
|
||||
reds_handle_new_link(link);
|
||||
return;
|
||||
}
|
||||
reds_send_link_error(link, SPICE_LINK_ERR_INVALID_MAGIC);
|
||||
reds_link_free(link);
|
||||
return;
|
||||
|
||||
457
server/websocket.c
Normal file
457
server/websocket.c
Normal file
@ -0,0 +1,457 @@
|
||||
/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
/*
|
||||
Copyright (C) 2015 Jeremy White
|
||||
|
||||
This library 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.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
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/>.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <config.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#ifndef _WIN32
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <common/log.h>
|
||||
#include <common/mem.h>
|
||||
|
||||
#include "sys-socket.h"
|
||||
#include "websocket.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <shlwapi.h>
|
||||
#define strcasestr(haystack, needle) StrStrIA(haystack, needle)
|
||||
#endif
|
||||
|
||||
/* Constants / masks all from RFC 6455 */
|
||||
|
||||
#define FIN_FLAG 0x80
|
||||
#define TYPE_MASK 0x0F
|
||||
|
||||
#define CONTINUATION_FRAME 0x0
|
||||
#define TEXT_FRAME 0x1
|
||||
#define BINARY_FRAME 0x2
|
||||
#define CLOSE_FRAME 0x8
|
||||
#define PING_FRAME 0x9
|
||||
#define PONG_FRAME 0xA
|
||||
|
||||
#define LENGTH_MASK 0x7F
|
||||
#define LENGTH_16BIT 0x7E
|
||||
#define LENGTH_64BIT 0x7F
|
||||
|
||||
#define MASK_FLAG 0x80
|
||||
|
||||
#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
/* Perform a case insensitive search for needle in haystack.
|
||||
If found, return a pointer to the byte after the end of needle.
|
||||
Otherwise, return NULL */
|
||||
static const char *find_str(const char *haystack, const char *needle)
|
||||
{
|
||||
const char *s = strcasestr(haystack, needle);
|
||||
|
||||
if (s) {
|
||||
return s + strlen(needle);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Extract WebSocket style length. Returns 0 if not enough data present,
|
||||
Always updates the output 'used' variable to the number of bytes
|
||||
required to extract the length; useful for tracking where the
|
||||
mask will be.
|
||||
*/
|
||||
static uint64_t extract_length(const uint8_t *buf, int *used)
|
||||
{
|
||||
int i;
|
||||
uint64_t outlen = (*buf++) & LENGTH_MASK;
|
||||
|
||||
(*used)++;
|
||||
|
||||
switch (outlen) {
|
||||
case LENGTH_64BIT:
|
||||
*used += 8;
|
||||
outlen = 0;
|
||||
for (i = 56; i >= 0; i -= 8) {
|
||||
outlen |= (*buf++) << i;
|
||||
}
|
||||
break;
|
||||
|
||||
case LENGTH_16BIT:
|
||||
*used += 2;
|
||||
outlen = ((*buf) << 8) | *(buf + 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return outlen;
|
||||
}
|
||||
|
||||
static int frame_bytes_needed(websocket_frame_t *frame)
|
||||
{
|
||||
int needed = 2;
|
||||
if (frame->header_pos < needed) {
|
||||
return needed - frame->header_pos;
|
||||
}
|
||||
|
||||
switch (frame->header[1] & LENGTH_MASK) {
|
||||
case LENGTH_64BIT:
|
||||
needed += 8;
|
||||
break;
|
||||
case LENGTH_16BIT:
|
||||
needed += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (frame->header[1] & MASK_FLAG) {
|
||||
needed += 4;
|
||||
}
|
||||
|
||||
return needed - frame->header_pos;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate WebSocket style response key, based on the
|
||||
* original key sent to us
|
||||
* If non null, caller must free returned key string.
|
||||
*/
|
||||
static char *generate_reply_key(char *buf)
|
||||
{
|
||||
GChecksum *checksum;
|
||||
char *b64 = NULL;
|
||||
uint8_t *sha1;
|
||||
size_t sha1_size;
|
||||
const char *key;
|
||||
const char *p;
|
||||
char *k;
|
||||
|
||||
key = find_str(buf, "\nSec-WebSocket-Key:");
|
||||
if (key) {
|
||||
p = strchr(key, '\r');
|
||||
if (p) {
|
||||
k = g_strndup(key, p - key);
|
||||
k = g_strstrip(k);
|
||||
checksum = g_checksum_new(G_CHECKSUM_SHA1);
|
||||
g_checksum_update(checksum, (uint8_t *) k, strlen(k));
|
||||
g_checksum_update(checksum, (uint8_t *) WEBSOCKET_GUID, strlen(WEBSOCKET_GUID));
|
||||
g_free(k);
|
||||
|
||||
sha1_size = g_checksum_type_get_length(G_CHECKSUM_SHA1);
|
||||
sha1 = g_malloc(sha1_size);
|
||||
|
||||
g_checksum_get_digest(checksum, sha1, &sha1_size);
|
||||
|
||||
b64 = g_base64_encode(sha1, sha1_size);
|
||||
|
||||
g_checksum_free(checksum);
|
||||
g_free(sha1);
|
||||
}
|
||||
}
|
||||
|
||||
return b64;
|
||||
}
|
||||
|
||||
|
||||
static void websocket_clear_frame(websocket_frame_t *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
}
|
||||
|
||||
/* Extract a frame header of data from a set of data transmitted by
|
||||
a WebSocket client. */
|
||||
static void websocket_get_frame_header(websocket_frame_t *frame)
|
||||
{
|
||||
int fin;
|
||||
int used = 0;
|
||||
|
||||
if (frame_bytes_needed(frame) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
fin = frame->header[0] & FIN_FLAG;
|
||||
frame->type = frame->header[0] & TYPE_MASK;
|
||||
used++;
|
||||
|
||||
frame->masked = frame->header[1] & MASK_FLAG;
|
||||
|
||||
/* This is a Spice specific optimization. We don't really
|
||||
care about assembling frames fully, so we treat
|
||||
a frame in process as a finished frame and pass it along. */
|
||||
if (!fin && frame->type == CONTINUATION_FRAME) {
|
||||
frame->type = BINARY_FRAME;
|
||||
}
|
||||
|
||||
frame->expected_len = extract_length(frame->header + used, &used);
|
||||
|
||||
if (frame->masked) {
|
||||
memcpy(frame->mask, frame->header + used, 4);
|
||||
}
|
||||
|
||||
frame->relayed = 0;
|
||||
frame->frame_ready = 1;
|
||||
}
|
||||
|
||||
static int relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
|
||||
{
|
||||
int i;
|
||||
int n = MIN(size, frame->expected_len - frame->relayed);
|
||||
|
||||
if (frame->masked) {
|
||||
for (i = 0; i < n; i++, frame->relayed++) {
|
||||
*buf++ ^= frame->mask[frame->relayed % 4];
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
int websocket_read(void *opaque, uint8_t *buf, int size, websocket_frame_t *frame,
|
||||
websocket_read_cb_t read_cb,
|
||||
websocket_write_cb_t write_cb)
|
||||
{
|
||||
int n = 0;
|
||||
int rc;
|
||||
|
||||
while (size > 0) {
|
||||
// make sure we have a proper frame ready
|
||||
if (!frame->frame_ready) {
|
||||
rc = read_cb(opaque, frame->header + frame->header_pos, frame_bytes_needed(frame));
|
||||
if (rc <= 0) {
|
||||
goto read_error;
|
||||
}
|
||||
frame->header_pos += rc;
|
||||
|
||||
websocket_get_frame_header(frame);
|
||||
} else if (frame->type == CLOSE_FRAME) {
|
||||
websocket_ack_close(opaque, write_cb);
|
||||
websocket_clear_frame(frame);
|
||||
return 0;
|
||||
} else if (frame->type == BINARY_FRAME) {
|
||||
rc = read_cb(opaque, buf, MIN(size, frame->expected_len - frame->relayed));
|
||||
if (rc <= 0) {
|
||||
goto read_error;
|
||||
}
|
||||
|
||||
rc = relay_data(buf, rc, frame);
|
||||
n += rc;
|
||||
buf += rc;
|
||||
size -= rc;
|
||||
if (frame->relayed >= frame->expected_len) {
|
||||
websocket_clear_frame(frame);
|
||||
}
|
||||
} else {
|
||||
/* TODO - We don't handle PING at this point */
|
||||
spice_warning("Unexpected WebSocket frame.type %d. Failure now likely.", frame->type);
|
||||
websocket_clear_frame(frame);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
|
||||
read_error:
|
||||
if (n > 0 && rc == -1 && (errno == EINTR || errno == EAGAIN)) {
|
||||
return n;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int fill_header(uint8_t *header, uint64_t len)
|
||||
{
|
||||
int used = 0;
|
||||
int i;
|
||||
|
||||
header[0] = FIN_FLAG | BINARY_FRAME;
|
||||
used++;
|
||||
|
||||
header[1] = 0;
|
||||
used++;
|
||||
if (len > 65535) {
|
||||
header[1] |= LENGTH_64BIT;
|
||||
for (i = 9; i >= 2; i--) {
|
||||
header[i] = len & 0xFF;
|
||||
len >>= 8;
|
||||
}
|
||||
used += 8;
|
||||
} else if (len >= LENGTH_16BIT) {
|
||||
header[1] |= LENGTH_16BIT;
|
||||
header[2] = len >> 8;
|
||||
header[3] = len & 0xFF;
|
||||
used += 2;
|
||||
} else {
|
||||
header[1] |= len;
|
||||
}
|
||||
|
||||
return used;
|
||||
}
|
||||
|
||||
static void constrain_iov(struct iovec *iov, int iovcnt,
|
||||
struct iovec **iov_out, int *iov_out_cnt,
|
||||
uint64_t maxlen)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
*iov_out = iov;
|
||||
*iov_out_cnt = iovcnt;
|
||||
|
||||
for (i = 0; i < iovcnt && maxlen > 0; i++) {
|
||||
if (iov[i].iov_len > maxlen) {
|
||||
/* TODO - This code has never triggered afaik... */
|
||||
*iov_out_cnt = i + 1;
|
||||
*iov_out = g_malloc((*iov_out_cnt) * sizeof (**iov_out));
|
||||
for (j = 0; j < i; j++) {
|
||||
(*iov_out)[j].iov_base = iov[j].iov_base;
|
||||
(*iov_out)[j].iov_len = iov[j].iov_len;
|
||||
}
|
||||
(*iov_out)[j].iov_base = iov[j].iov_base;
|
||||
(*iov_out)[j].iov_len = maxlen;
|
||||
break;
|
||||
}
|
||||
maxlen -= iov[i].iov_len;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Write a WebSocket frame with the enclosed data out. */
|
||||
int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *remainder,
|
||||
websocket_writev_cb_t writev_cb)
|
||||
{
|
||||
uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
|
||||
uint64_t len;
|
||||
int rc = -1;
|
||||
struct iovec *iov_out;
|
||||
int iov_out_cnt;
|
||||
int i;
|
||||
int header_len;
|
||||
|
||||
if (*remainder > 0) {
|
||||
constrain_iov(iov, iovcnt, &iov_out, &iov_out_cnt, *remainder);
|
||||
rc = writev_cb(opaque, iov_out, iov_out_cnt);
|
||||
if (iov_out != iov) {
|
||||
g_free(iov_out);
|
||||
}
|
||||
if (rc <= 0) {
|
||||
return rc;
|
||||
}
|
||||
*remainder -= rc;
|
||||
return rc;
|
||||
}
|
||||
|
||||
iov_out_cnt = iovcnt + 1;
|
||||
iov_out = g_malloc(iov_out_cnt * sizeof(*iov_out));
|
||||
|
||||
for (i = 0, len = 0; i < iovcnt; i++) {
|
||||
len += iov[i].iov_len;
|
||||
iov_out[i + 1] = iov[i];
|
||||
}
|
||||
|
||||
memset(header, 0, sizeof(header));
|
||||
header_len = fill_header(header, len);
|
||||
iov_out[0].iov_len = header_len;
|
||||
iov_out[0].iov_base = header;
|
||||
rc = writev_cb(opaque, iov_out, iov_out_cnt);
|
||||
g_free(iov_out);
|
||||
if (rc <= 0) {
|
||||
return rc;
|
||||
}
|
||||
rc -= header_len;
|
||||
|
||||
spice_assert(rc >= 0);
|
||||
|
||||
/* Key point: if we did not write out all the data, remember how
|
||||
much more data the client is expecting, and write that data without
|
||||
a header of any kind the next time around */
|
||||
*remainder = len - rc;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int websocket_write(void *opaque, const uint8_t *buf, int len, uint64_t *remainder,
|
||||
websocket_write_cb_t write_cb)
|
||||
{
|
||||
uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
|
||||
int rc;
|
||||
int header_len;
|
||||
|
||||
if (*remainder == 0) {
|
||||
header_len = fill_header(header, len);
|
||||
rc = write_cb(opaque, header, header_len);
|
||||
if (rc <= 0) {
|
||||
return rc;
|
||||
}
|
||||
if (rc != header_len) {
|
||||
/* TODO - In theory, we can handle this case. In practice,
|
||||
it does not occur, and does not seem to be worth
|
||||
the code complexity */
|
||||
errno = EPIPE;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
len = MIN(*remainder, len);
|
||||
}
|
||||
|
||||
rc = write_cb(opaque, buf, len);
|
||||
if (rc <= 0) {
|
||||
*remainder = len;
|
||||
} else {
|
||||
*remainder = len - rc;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb)
|
||||
{
|
||||
unsigned char header[2];
|
||||
|
||||
header[0] = FIN_FLAG | CLOSE_FRAME;
|
||||
header[1] = 0;
|
||||
|
||||
write_cb(opaque, header, sizeof(header));
|
||||
}
|
||||
|
||||
bool websocket_is_start(char *buf)
|
||||
{
|
||||
if (strncmp(buf, "GET ", 4) == 0 &&
|
||||
// TODO strip, do not assume a single space
|
||||
find_str(buf, "\nSec-WebSocket-Protocol: binary") &&
|
||||
find_str(buf, "\nSec-WebSocket-Key:") &&
|
||||
g_str_has_suffix(buf, "\r\n\r\n")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void websocket_create_reply(char *buf, char *outbuf)
|
||||
{
|
||||
char *key;
|
||||
|
||||
key = generate_reply_key(buf);
|
||||
sprintf(outbuf, "HTTP/1.1 101 Switching Protocols\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Accept: %s\r\n"
|
||||
"Sec-WebSocket-Protocol: binary\r\n\r\n", key);
|
||||
g_free(key);
|
||||
}
|
||||
44
server/websocket.h
Normal file
44
server/websocket.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jeremy White
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
int masked;
|
||||
uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
|
||||
int header_pos;
|
||||
int frame_ready:1;
|
||||
uint8_t mask[4];
|
||||
uint64_t relayed;
|
||||
uint64_t expected_len;
|
||||
} websocket_frame_t;
|
||||
|
||||
typedef ssize_t (*websocket_read_cb_t)(void *opaque, void *buf, size_t nbyte);
|
||||
typedef ssize_t (*websocket_write_cb_t)(void *opaque, const void *buf, size_t nbyte);
|
||||
typedef ssize_t (*websocket_writev_cb_t)(void *opaque, struct iovec *iov, int iovcnt);
|
||||
|
||||
bool websocket_is_start(char *buf);
|
||||
void websocket_create_reply(char *buf, char *outbuf);
|
||||
int websocket_read(void *opaque, uint8_t *buf, int len, websocket_frame_t *frame,
|
||||
websocket_read_cb_t read_cb,
|
||||
websocket_write_cb_t write_cb);
|
||||
int websocket_write(void *opaque, const uint8_t *buf, int len, uint64_t *remainder,
|
||||
websocket_write_cb_t write_cb);
|
||||
int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *remainder,
|
||||
websocket_writev_cb_t writev_cb);
|
||||
void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
|
||||
Loading…
Reference in New Issue
Block a user