diff options
Diffstat (limited to 'libs/libcurl/src/ws.c')
-rw-r--r-- | libs/libcurl/src/ws.c | 143 |
1 files changed, 96 insertions, 47 deletions
diff --git a/libs/libcurl/src/ws.c b/libs/libcurl/src/ws.c index 1d641dfdb0..691710db06 100644 --- a/libs/libcurl/src/ws.c +++ b/libs/libcurl/src/ws.c @@ -27,10 +27,11 @@ #if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP)
#include "urldata.h"
+#include "url.h"
#include "bufq.h"
-#include "dynbuf.h"
+#include "curlx/dynbuf.h"
#include "rand.h"
-#include "curl_base64.h"
+#include "curlx/base64.h"
#include "connect.h"
#include "sendf.h"
#include "multiif.h"
@@ -38,8 +39,8 @@ #include "easyif.h"
#include "transfer.h"
#include "select.h"
-#include "nonblock.h"
-#include "strparse.h"
+#include "curlx/nonblock.h"
+#include "curlx/strparse.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -76,6 +77,50 @@ #define WS_CHUNK_SIZE 65535
#define WS_CHUNK_COUNT 2
+
+/* a client-side WS frame decoder, parsing frame headers and
+ * payload, keeping track of current position and stats */
+enum ws_dec_state {
+ WS_DEC_INIT,
+ WS_DEC_HEAD,
+ WS_DEC_PAYLOAD
+};
+
+struct ws_decoder {
+ int frame_age; /* zero */
+ int frame_flags; /* See the CURLWS_* defines */
+ curl_off_t payload_offset; /* the offset parsing is at */
+ curl_off_t payload_len;
+ unsigned char head[10];
+ int head_len, head_total;
+ enum ws_dec_state state;
+ int cont_flags;
+};
+
+/* a client-side WS frame encoder, generating frame headers and
+ * converting payloads, tracking remaining data in current frame */
+struct ws_encoder {
+ curl_off_t payload_len; /* payload length of current frame */
+ curl_off_t payload_remain; /* remaining payload of current */
+ unsigned int xori; /* xor index */
+ unsigned char mask[4]; /* 32-bit mask for this connection */
+ unsigned char firstbyte; /* first byte of frame we encode */
+ BIT(contfragment); /* set TRUE if the previous fragment sent was not final */
+};
+
+/* A websocket connection with en- and decoder that treat frames
+ * and keep track of boundaries. */
+struct websocket {
+ struct Curl_easy *data; /* used for write callback handling */
+ struct ws_decoder dec; /* decode of we frames */
+ struct ws_encoder enc; /* decode of we frames */
+ struct bufq recvbuf; /* raw data from the server */
+ struct bufq sendbuf; /* raw data to be sent to the server */
+ struct curl_ws_frame frame; /* the current WS FRAME received */
+ size_t sendbuf_payload; /* number of payload bytes in sendbuf */
+};
+
+
static const char *ws_frame_name_of_op(unsigned char firstbyte)
{
switch(firstbyte & WSBIT_OPCODE_MASK) {
@@ -502,10 +547,12 @@ static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen, struct ws_cw_dec_ctx *ctx = user_data;
struct Curl_easy *data = ctx->data;
struct websocket *ws = ctx->ws;
+ bool auto_pong = !data->set.ws_no_auto_pong;
curl_off_t remain = (payload_len - (payload_offset + buflen));
(void)frame_age;
- if((frame_flags & CURLWS_PING) && !remain) {
+
+ if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
/* auto-respond to PINGs, only works for single-frame payloads atm */
size_t bytes;
infof(data, "WS: auto-respond to PING with a PONG");
@@ -539,7 +586,7 @@ static CURLcode ws_cw_write(struct Curl_easy *data, if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);
- ws = data->conn->proto.ws;
+ ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
failf(data, "WS: not a websocket transfer");
return CURLE_FAILED_INIT;
@@ -776,18 +823,18 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) {
/* The request MUST contain an |Upgrade| header field whose value
MUST include the "websocket" keyword. */
- "Upgrade:", "websocket"
+ "Upgrade", "websocket"
},
{
/* The request MUST contain a |Connection| header field whose value
MUST include the "Upgrade" token. */
- "Connection:", "Upgrade",
+ "Connection", "Upgrade",
},
{
/* The request MUST include a header field with the name
|Sec-WebSocket-Version|. The value of this header field MUST be
13. */
- "Sec-WebSocket-Version:", "13",
+ "Sec-WebSocket-Version", "13",
},
{
/* The request MUST include a header field with the name
@@ -795,7 +842,7 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) consisting of a randomly selected 16-byte value that has been
base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be
selected randomly for each connection. */
- "Sec-WebSocket-Key:", NULL,
+ "Sec-WebSocket-Key", NULL,
}
};
heads[3].val = &keyval[0];
@@ -804,7 +851,7 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) result = Curl_rand(data, (unsigned char *)rand, sizeof(rand));
if(result)
return result;
- result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen);
+ result = curlx_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen);
if(result)
return result;
DEBUGASSERT(randlen < sizeof(keyval));
@@ -815,15 +862,25 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) strcpy(keyval, randstr);
free(randstr);
for(i = 0; !result && (i < CURL_ARRAYSIZE(heads)); i++) {
- if(!Curl_checkheaders(data, STRCONST(heads[i].name))) {
- result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name,
- heads[i].val);
+ if(!Curl_checkheaders(data, heads[i].name, strlen(heads[i].name))) {
+ result = curlx_dyn_addf(req, "%s: %s\r\n", heads[i].name,
+ heads[i].val);
}
}
k->upgr101 = UPGR101_WS;
return result;
}
+static void ws_conn_dtor(void *key, size_t klen, void *entry)
+{
+ struct websocket *ws = entry;
+ (void)key;
+ (void)klen;
+ Curl_bufq_free(&ws->recvbuf);
+ Curl_bufq_free(&ws->sendbuf);
+ free(ws);
+}
+
/*
* 'nread' is number of bytes of websocket data already in the buffer at
* 'mem'.
@@ -837,19 +894,18 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, CURLcode result;
DEBUGASSERT(data->conn);
- ws = data->conn->proto.ws;
+ ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
size_t chunk_size = WS_CHUNK_SIZE;
ws = calloc(1, sizeof(*ws));
if(!ws)
return CURLE_OUT_OF_MEMORY;
- data->conn->proto.ws = ws;
#ifdef DEBUGBUILD
{
const char *p = getenv("CURL_WS_CHUNK_SIZE");
if(p) {
curl_off_t l;
- if(!Curl_str_number(&p, &l, 1*1024*1024))
+ if(!curlx_str_number(&p, &l, 1*1024*1024))
chunk_size = (size_t)l;
}
}
@@ -861,6 +917,10 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, BUFQ_OPT_SOFT_LIMIT);
ws_dec_init(&ws->dec);
ws_enc_init(&ws->enc);
+ result = Curl_conn_meta_set(data->conn, CURL_META_PROTO_WS_CONN,
+ ws, ws_conn_dtor);
+ if(result)
+ return result;
}
else {
Curl_bufq_reset(&ws->recvbuf);
@@ -949,6 +1009,8 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen, CURLcode *err)
{
struct ws_collect *ctx = userp;
+ struct Curl_easy *data = ctx->data;
+ bool auto_pong = !data->set.ws_no_auto_pong;
size_t nwritten;
curl_off_t remain = (payload_len - (payload_offset + buflen));
@@ -960,7 +1022,7 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen, ctx->payload_len = payload_len;
}
- if((frame_flags & CURLWS_PING) && !remain) {
+ if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
/* auto-respond to PINGs, only works for single-frame payloads atm */
size_t bytes;
infof(ctx->data, "WS: auto-respond to PING with a PONG");
@@ -1027,7 +1089,7 @@ CURL_EXTERN CURLcode curl_ws_recv(CURL *d, void *buffer, return CURLE_BAD_FUNCTION_ARGUMENT;
}
}
- ws = conn->proto.ws;
+ ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
failf(data, "connection is not setup for websocket");
return CURLE_BAD_FUNCTION_ARGUMENT;
@@ -1102,7 +1164,7 @@ static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, const char *p = getenv("CURL_WS_CHUNK_EAGAIN");
if(p) {
curl_off_t l;
- if(!Curl_str_number(&p, &l, 1*1024*1024))
+ if(!curlx_str_number(&p, &l, 1*1024*1024))
chunk_egain = (size_t)l;
}
#endif
@@ -1191,9 +1253,10 @@ static CURLcode ws_send_raw_blocking(CURL *d, struct websocket *ws, static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer,
size_t buflen, size_t *pnwritten)
{
- struct websocket *ws = data->conn->proto.ws;
+ struct websocket *ws;
CURLcode result;
+ ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
failf(data, "Not a websocket transfer");
return CURLE_SEND_ERROR;
@@ -1249,12 +1312,12 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg, result = CURLE_SEND_ERROR;
goto out;
}
- if(!data->conn->proto.ws) {
+ ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
+ if(!ws) {
failf(data, "Not a websocket transfer");
result = CURLE_SEND_ERROR;
goto out;
}
- ws = data->conn->proto.ws;
if(data->set.ws_raw_mode) {
/* In raw mode, we write directly to the connection */
@@ -1362,15 +1425,6 @@ out: return result;
}
-static void ws_free(struct connectdata *conn)
-{
- if(conn && conn->proto.ws) {
- Curl_bufq_free(&conn->proto.ws->recvbuf);
- Curl_bufq_free(&conn->proto.ws->sendbuf);
- Curl_safefree(conn->proto.ws);
- }
-}
-
static CURLcode ws_setup_conn(struct Curl_easy *data,
struct connectdata *conn)
{
@@ -1383,24 +1437,19 @@ static CURLcode ws_setup_conn(struct Curl_easy *data, }
-static CURLcode ws_disconnect(struct Curl_easy *data,
- struct connectdata *conn,
- bool dead_connection)
-{
- (void)data;
- (void)dead_connection;
- ws_free(conn);
- return CURLE_OK;
-}
-
CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *d)
{
/* we only return something for websocket, called from within the callback
when not using raw mode */
struct Curl_easy *data = d;
- if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->conn &&
- data->conn->proto.ws && !data->set.ws_raw_mode)
- return &data->conn->proto.ws->frame;
+ if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) &&
+ data->conn && !data->set.ws_raw_mode) {
+ struct websocket *ws;
+ ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
+ if(ws)
+ return &ws->frame;
+
+ }
return NULL;
}
@@ -1417,7 +1466,7 @@ const struct Curl_handler Curl_handler_ws = { Curl_http_getsock_do, /* doing_getsock */
ZERO_NULL, /* domore_getsock */
ZERO_NULL, /* perform_getsock */
- ws_disconnect, /* disconnect */
+ ZERO_NULL, /* disconnect */
Curl_http_write_resp, /* write_resp */
Curl_http_write_resp_hd, /* write_resp_hd */
ZERO_NULL, /* connection_check */
@@ -1444,7 +1493,7 @@ const struct Curl_handler Curl_handler_wss = { Curl_http_getsock_do, /* doing_getsock */
ZERO_NULL, /* domore_getsock */
ZERO_NULL, /* perform_getsock */
- ws_disconnect, /* disconnect */
+ ZERO_NULL, /* disconnect */
Curl_http_write_resp, /* write_resp */
Curl_http_write_resp_hd, /* write_resp_hd */
ZERO_NULL, /* connection_check */
|