Websocket en-/decoding

- state is fully kept at connection, since curl_ws_send() and
  curl_ws_rec() have lifetime beyond usual transfers
- no more limit on frame sizes

Reported-by: simplerobot on github
Fixes #10962
Closes #10999
This commit is contained in:
Stefan Eissing 2023-04-18 15:02:34 +02:00 committed by Daniel Stenberg
parent 3f0b81c112
commit 930c00c259
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
8 changed files with 883 additions and 506 deletions

View File

@ -1223,6 +1223,26 @@ CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen,
return CURLE_OK;
}
#ifdef USE_WEBSOCKETS
CURLcode Curl_connect_only_attach(struct Curl_easy *data)
{
curl_socket_t sfd;
CURLcode result;
struct connectdata *c = NULL;
result = easy_connection(data, &sfd, &c);
if(result)
return result;
if(!data->conn)
/* on first invoke, the transfer has been detached from the connection and
needs to be reattached */
Curl_attach_connection(data, c);
return CURLE_OK;
}
#endif /* USE_WEBSOCKETS */
/*
* Sends data over the connected socket.
*

View File

@ -30,6 +30,10 @@
CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer,
size_t buflen, ssize_t *n);
#ifdef USE_WEBSOCKETS
CURLcode Curl_connect_only_attach(struct Curl_easy *data);
#endif
#ifdef CURLDEBUG
CURL_EXTERN CURLcode curl_easy_perform_ev(struct Curl_easy *easy);
#endif

View File

@ -212,10 +212,6 @@ struct HTTP {
HTTPSEND_BODY /* sending body */
} sending;
#ifdef USE_WEBSOCKETS
struct websocket ws;
#endif
#ifndef CURL_DISABLE_HTTP
void *h2_ctx; /* HTTP/2 implementation context */
void *h3_ctx; /* HTTP/3 implementation context */

View File

@ -271,10 +271,8 @@ static CURLcode chop_write(struct Curl_easy *data,
if(type & CLIENTWRITE_BODY) {
#ifdef USE_WEBSOCKETS
if(conn->handler->protocol & (CURLPROTO_WS|CURLPROTO_WSS)) {
struct HTTP *ws = data->req.p.http;
writebody = Curl_ws_writecb;
ws->ws.data = data;
writebody_ptr = ws;
writebody_ptr = data;
}
else
#endif

View File

@ -1030,7 +1030,7 @@ struct connectdata {
struct mqtt_conn mqtt;
#endif
#ifdef USE_WEBSOCKETS
struct ws_conn ws;
struct websocket *ws;
#endif
} proto;

1284
lib/ws.c

File diff suppressed because it is too large Load Diff

View File

@ -33,28 +33,44 @@
#define REQTYPE struct dynbuf
#endif
/* this is the largest single fragment size we support */
#define MAX_WS_SIZE 65535
/* part of 'struct HTTP', when used in the 'struct SingleRequest' in the
Curl_easy struct */
struct websocket {
bool contfragment; /* set TRUE if the previous fragment sent was not final */
unsigned char mask[4]; /* 32 bit mask for this connection */
struct Curl_easy *data; /* used for write callback handling */
struct dynbuf buf;
size_t usedbuf; /* number of leading bytes in 'buf' the most recent complete
websocket frame uses */
struct curl_ws_frame frame; /* the struct used for frame state */
size_t stillblen; /* number of bytes left in the buffer to deliver in
the next curl_ws_recv() call */
const char *stillb; /* the stillblen pending bytes are here */
curl_off_t sleft; /* outstanding number of payload bytes left to send */
unsigned int xori; /* xor index */
/* 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_conn {
struct dynbuf early; /* data already read when switching to ws */
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;
};
/* 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 */
bool 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 */
};
CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req);

View File

@ -67,6 +67,20 @@ static int recv_pong(CURL *curl, const char *exected_payload)
return (int)result;
}
static int recv_any(CURL *curl)
{
size_t rlen;
struct curl_ws_frame *meta;
char buffer[256];
CURLcode result = curl_ws_recv(curl, buffer, sizeof(buffer), &rlen, &meta);
if(result)
return result;
fprintf(stderr, "recv_any: got %u bytes rflags %x\n", (int)rlen,
meta->flags);
return 0;
}
/* just close the connection */
static void websocket_close(CURL *curl)
{
@ -82,6 +96,7 @@ static void websocket(CURL *curl)
int i = 0;
fprintf(stderr, "ws: websocket() starts\n");
do {
recv_any(curl);
fprintf(stderr, "Send ping\n");
if(ping(curl, "foobar"))
return;