diff options
Diffstat (limited to 'libs/libcurl/src/ws.c')
-rw-r--r-- | libs/libcurl/src/ws.c | 271 |
1 files changed, 170 insertions, 101 deletions
diff --git a/libs/libcurl/src/ws.c b/libs/libcurl/src/ws.c index 92e9eb5635..1d641dfdb0 100644 --- a/libs/libcurl/src/ws.c +++ b/libs/libcurl/src/ws.c @@ -39,6 +39,7 @@ #include "transfer.h"
#include "select.h"
#include "nonblock.h"
+#include "strparse.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -46,16 +47,26 @@ #include "memdebug.h"
-#define WSBIT_FIN 0x80
-#define WSBIT_RSV1 0x40
-#define WSBIT_RSV2 0x20
-#define WSBIT_RSV3 0x10
+/***
+ RFC 6455 Section 5.2
+
+ 0 1 2 3 4 5 6 7
+ +-+-+-+-+-------+
+ |F|R|R|R| opcode|
+ |I|S|S|S| (4) |
+ |N|V|V|V| |
+ | |1|2|3| |
+*/
+#define WSBIT_FIN (0x80)
+#define WSBIT_RSV1 (0x40)
+#define WSBIT_RSV2 (0x20)
+#define WSBIT_RSV3 (0x10)
#define WSBIT_RSV_MASK (WSBIT_RSV1 | WSBIT_RSV2 | WSBIT_RSV3)
-#define WSBIT_OPCODE_CONT 0
-#define WSBIT_OPCODE_TEXT (1)
-#define WSBIT_OPCODE_BIN (2)
-#define WSBIT_OPCODE_CLOSE (8)
-#define WSBIT_OPCODE_PING (9)
+#define WSBIT_OPCODE_CONT (0x0)
+#define WSBIT_OPCODE_TEXT (0x1)
+#define WSBIT_OPCODE_BIN (0x2)
+#define WSBIT_OPCODE_CLOSE (0x8)
+#define WSBIT_OPCODE_PING (0x9)
#define WSBIT_OPCODE_PONG (0xa)
#define WSBIT_OPCODE_MASK (0xf)
@@ -65,58 +76,120 @@ #define WS_CHUNK_SIZE 65535
#define WS_CHUNK_COUNT 2
-struct ws_frame_meta {
- char proto_opcode;
- int flags;
- const char *name;
-};
-
-static struct ws_frame_meta WS_FRAMES[] = {
- { WSBIT_OPCODE_CONT, CURLWS_CONT, "CONT" },
- { WSBIT_OPCODE_TEXT, CURLWS_TEXT, "TEXT" },
- { WSBIT_OPCODE_BIN, CURLWS_BINARY, "BIN" },
- { WSBIT_OPCODE_CLOSE, CURLWS_CLOSE, "CLOSE" },
- { WSBIT_OPCODE_PING, CURLWS_PING, "PING" },
- { WSBIT_OPCODE_PONG, CURLWS_PONG, "PONG" },
-};
-
-static const char *ws_frame_name_of_op(unsigned char proto_opcode)
+static const char *ws_frame_name_of_op(unsigned char firstbyte)
{
- unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK;
- size_t i;
- for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) {
- if(WS_FRAMES[i].proto_opcode == opcode)
- return WS_FRAMES[i].name;
+ switch(firstbyte & WSBIT_OPCODE_MASK) {
+ case WSBIT_OPCODE_CONT:
+ return "CONT";
+ case WSBIT_OPCODE_TEXT:
+ return "TEXT";
+ case WSBIT_OPCODE_BIN:
+ return "BIN";
+ case WSBIT_OPCODE_CLOSE:
+ return "CLOSE";
+ case WSBIT_OPCODE_PING:
+ return "PING";
+ case WSBIT_OPCODE_PONG:
+ return "PONG";
+ default:
+ return "???";
}
- return "???";
}
-static int ws_frame_op2flags(unsigned char proto_opcode)
+static int ws_frame_firstbyte2flags(struct Curl_easy *data,
+ unsigned char firstbyte, int cont_flags)
{
- unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK;
- size_t i;
- for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) {
- if(WS_FRAMES[i].proto_opcode == opcode)
- return WS_FRAMES[i].flags;
+ switch(firstbyte) {
+ case WSBIT_OPCODE_CONT:
+ /* continuation of a previous fragment: restore stored flags */
+ return cont_flags | CURLWS_CONT;
+ case (WSBIT_OPCODE_CONT | WSBIT_FIN):
+ /* continuation of a previous fragment: restore stored flags */
+ return cont_flags & ~CURLWS_CONT;
+ case WSBIT_OPCODE_TEXT:
+ return CURLWS_TEXT | CURLWS_CONT;
+ case (WSBIT_OPCODE_TEXT | WSBIT_FIN):
+ return CURLWS_TEXT;
+ case WSBIT_OPCODE_BIN:
+ return CURLWS_BINARY | CURLWS_CONT;
+ case (WSBIT_OPCODE_BIN | WSBIT_FIN):
+ return CURLWS_BINARY;
+ case (WSBIT_OPCODE_CLOSE | WSBIT_FIN):
+ return CURLWS_CLOSE;
+ case (WSBIT_OPCODE_PING | WSBIT_FIN):
+ return CURLWS_PING;
+ case (WSBIT_OPCODE_PONG | WSBIT_FIN):
+ return CURLWS_PONG;
+ default:
+ if(firstbyte & WSBIT_RSV_MASK) {
+ failf(data, "WS: unknown reserved bit: %x",
+ firstbyte & WSBIT_RSV_MASK);
+ }
+ else {
+ failf(data, "WS: unknown opcode: %x",
+ firstbyte & WSBIT_OPCODE_MASK);
+ }
+ return 0;
}
- return 0;
}
-static unsigned char ws_frame_flags2op(int flags)
+static unsigned char ws_frame_flags2firstbyte(struct Curl_easy *data,
+ unsigned int flags,
+ bool contfragment,
+ CURLcode *err)
{
- size_t i;
- for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) {
- if(WS_FRAMES[i].flags & flags)
- return (unsigned char)WS_FRAMES[i].proto_opcode;
+ switch(flags & ~CURLWS_OFFSET) {
+ case 0:
+ if(contfragment) {
+ infof(data, "WS: no flags given; interpreting as continuation "
+ "fragment for compatibility");
+ return (WSBIT_OPCODE_CONT | WSBIT_FIN);
+ }
+ failf(data, "WS: no flags given");
+ *err = CURLE_BAD_FUNCTION_ARGUMENT;
+ return 0xff;
+ case CURLWS_CONT:
+ if(contfragment) {
+ infof(data, "WS: setting CURLWS_CONT flag without message type is "
+ "supported for compatibility but highly discouraged");
+ return WSBIT_OPCODE_CONT;
+ }
+ failf(data, "WS: No ongoing fragmented message to continue");
+ *err = CURLE_BAD_FUNCTION_ARGUMENT;
+ return 0xff;
+ case CURLWS_TEXT:
+ return contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN)
+ : (WSBIT_OPCODE_TEXT | WSBIT_FIN);
+ case (CURLWS_TEXT | CURLWS_CONT):
+ return contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_TEXT;
+ case CURLWS_BINARY:
+ return contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN)
+ : (WSBIT_OPCODE_BIN | WSBIT_FIN);
+ case (CURLWS_BINARY | CURLWS_CONT):
+ return contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_BIN;
+ case CURLWS_CLOSE:
+ return WSBIT_OPCODE_CLOSE | WSBIT_FIN;
+ case (CURLWS_CLOSE | CURLWS_CONT):
+ failf(data, "WS: CLOSE frame must not be fragmented");
+ *err = CURLE_BAD_FUNCTION_ARGUMENT;
+ return 0xff;
+ case CURLWS_PING:
+ return WSBIT_OPCODE_PING | WSBIT_FIN;
+ case (CURLWS_PING | CURLWS_CONT):
+ failf(data, "WS: PING frame must not be fragmented");
+ *err = CURLE_BAD_FUNCTION_ARGUMENT;
+ return 0xff;
+ case CURLWS_PONG:
+ return WSBIT_OPCODE_PONG | WSBIT_FIN;
+ case (CURLWS_PONG | CURLWS_CONT):
+ failf(data, "WS: PONG frame must not be fragmented");
+ *err = CURLE_BAD_FUNCTION_ARGUMENT;
+ return 0xff;
+ default:
+ failf(data, "WS: unknown flags: %x", flags);
+ *err = CURLE_SEND_ERROR;
+ return 0xff;
}
- return 0;
-}
-
-/* No extensions are supported. If any of the RSV bits are set, we must fail */
-static bool ws_frame_rsv_supported(int flags)
-{
- unsigned char reserved_bits = flags & WSBIT_RSV_MASK;
- return reserved_bits == 0;
}
static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data,
@@ -158,6 +231,16 @@ typedef ssize_t ws_write_payload(const unsigned char *buf, size_t buflen, void *userp,
CURLcode *err);
+static void ws_dec_next_frame(struct ws_decoder *dec)
+{
+ dec->frame_age = 0;
+ dec->frame_flags = 0;
+ dec->payload_offset = 0;
+ dec->payload_len = 0;
+ dec->head_len = dec->head_total = 0;
+ dec->state = WS_DEC_INIT;
+ /* dec->cont_flags must be carried over to next frame */
+}
static void ws_dec_reset(struct ws_decoder *dec)
{
@@ -167,6 +250,7 @@ static void ws_dec_reset(struct ws_decoder *dec) dec->payload_len = 0;
dec->head_len = dec->head_total = 0;
dec->state = WS_DEC_INIT;
+ dec->cont_flags = 0;
}
static void ws_dec_init(struct ws_decoder *dec)
@@ -186,20 +270,21 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, dec->head[0] = *inbuf;
Curl_bufq_skip(inraw, 1);
- if(!ws_frame_rsv_supported(dec->head[0])) {
- failf(data, "WS: unknown reserved bit in frame header: %x",
- dec->head[0] & WSBIT_RSV_MASK);
+ dec->frame_flags = ws_frame_firstbyte2flags(data, dec->head[0],
+ dec->cont_flags);
+ if(!dec->frame_flags) {
+ failf(data, "WS: invalid first byte: %x", dec->head[0]);
ws_dec_reset(dec);
return CURLE_RECV_ERROR;
}
- dec->frame_flags = ws_frame_op2flags(dec->head[0]);
- if(!dec->frame_flags) {
- failf(data, "WS: unknown opcode: %x",
- dec->head[0] & WSBIT_OPCODE_MASK);
- ws_dec_reset(dec);
- return CURLE_RECV_ERROR;
+ if(dec->frame_flags & CURLWS_CONT) {
+ dec->cont_flags = dec->frame_flags;
}
+ else {
+ dec->cont_flags = 0;
+ }
+
dec->head_len = 1;
/* ws_dec_info(dec, data, "seeing opcode"); */
continue;
@@ -320,7 +405,7 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec, switch(dec->state) {
case WS_DEC_INIT:
- ws_dec_reset(dec);
+ ws_dec_next_frame(dec);
dec->state = WS_DEC_HEAD;
FALLTHROUGH();
case WS_DEC_HEAD:
@@ -369,11 +454,13 @@ static void update_meta(struct websocket *ws, curl_off_t payload_len,
size_t cur_len)
{
+ curl_off_t bytesleft = (payload_len - payload_offset - cur_len);
+
ws->frame.age = frame_age;
ws->frame.flags = frame_flags;
ws->frame.offset = payload_offset;
ws->frame.len = cur_len;
- ws->frame.bytesleft = (payload_len - payload_offset - cur_len);
+ ws->frame.bytesleft = bytesleft;
}
/* WebSockets decoding client writer */
@@ -511,10 +598,8 @@ static const struct Curl_cwtype ws_cw_decode = { static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data,
const char *msg)
{
- infof(data, "WS-ENC: %s [%s%s%s payload=%" FMT_OFF_T "/%" FMT_OFF_T "]",
+ infof(data, "WS-ENC: %s [%s%s payload=%" FMT_OFF_T "/%" FMT_OFF_T "]",
msg, ws_frame_name_of_op(enc->firstbyte),
- (enc->firstbyte & WSBIT_OPCODE_MASK) == WSBIT_OPCODE_CONT ?
- " CONT" : "",
(enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN",
enc->payload_len - enc->payload_remain, enc->payload_len);
}
@@ -562,7 +647,6 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data, CURLcode *err)
{
unsigned char firstbyte = 0;
- unsigned char opcode;
unsigned char head[14];
size_t hlen;
ssize_t n;
@@ -582,32 +666,16 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data, return -1;
}
- opcode = ws_frame_flags2op((int)flags & ~CURLWS_CONT);
- if(!opcode) {
- failf(data, "WS: provided flags not recognized '%x'", flags);
- *err = CURLE_SEND_ERROR;
+ firstbyte = ws_frame_flags2firstbyte(data, flags, enc->contfragment, err);
+ if(*err) {
+ failf(data, "WS: provided flags not valid: %x", flags);
return -1;
}
- if(!(flags & CURLWS_CONT)) {
- if(!enc->contfragment)
- /* not marked as continuing, this is the final fragment */
- firstbyte |= WSBIT_FIN | opcode;
- else
- /* marked as continuing, this is the final fragment; set CONT
- opcode and FIN bit */
- firstbyte |= WSBIT_FIN | WSBIT_OPCODE_CONT;
-
- enc->contfragment = FALSE;
- }
- else if(enc->contfragment) {
- /* the previous fragment was not a final one and this is not either, keep a
- CONT opcode and no FIN bit */
- firstbyte |= WSBIT_OPCODE_CONT;
- }
- else {
- firstbyte = opcode;
- enc->contfragment = TRUE;
+ /* fragmentation only applies to data frames (text/binary);
+ * control frames (close/ping/pong) do not affect the CONT status */
+ if(flags & (CURLWS_TEXT | CURLWS_BINARY)) {
+ enc->contfragment = (flags & CURLWS_CONT) ? (bit)TRUE : (bit)FALSE;
}
head[0] = enc->firstbyte = firstbyte;
@@ -746,7 +814,7 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) }
strcpy(keyval, randstr);
free(randstr);
- for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) {
+ 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);
@@ -778,12 +846,11 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, data->conn->proto.ws = ws;
#ifdef DEBUGBUILD
{
- char *p = getenv("CURL_WS_CHUNK_SIZE");
+ const char *p = getenv("CURL_WS_CHUNK_SIZE");
if(p) {
- long l = strtol(p, NULL, 10);
- if(l > 0 && l <= (1*1024*1024)) {
+ curl_off_t l;
+ if(!Curl_str_number(&p, &l, 1*1024*1024))
chunk_size = (size_t)l;
- }
}
}
#endif
@@ -854,7 +921,7 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, else { /* !connect_only */
/* And pass any additional data to the writers */
if(nread) {
- result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)mem, nread);
+ result = Curl_client_write(data, CLIENTWRITE_BODY, mem, nread);
}
}
k->upgr101 = UPGR101_RECEIVED;
@@ -1032,12 +1099,11 @@ static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, /* Simulate a blocking send after this chunk has been sent */
bool eagain_next = FALSE;
size_t chunk_egain = 0;
- char *p = getenv("CURL_WS_CHUNK_EAGAIN");
+ const char *p = getenv("CURL_WS_CHUNK_EAGAIN");
if(p) {
- long l = strtol(p, NULL, 10);
- if(l > 0 && l <= (1*1024*1024)) {
+ curl_off_t l;
+ if(!Curl_str_number(&p, &l, 1*1024*1024))
chunk_egain = (size_t)l;
- }
}
#endif
@@ -1051,7 +1117,7 @@ static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, }
#endif
if(blocking) {
- result = ws_send_raw_blocking(data, ws, (char *)out, outlen);
+ result = ws_send_raw_blocking(data, ws, (const char *)out, outlen);
n = result ? 0 : outlen;
}
else if(data->set.connect_only || Curl_is_in_callback(data))
@@ -1309,7 +1375,10 @@ static CURLcode ws_setup_conn(struct Curl_easy *data, struct connectdata *conn)
{
/* WebSockets is 1.1 only (for now) */
- data->state.httpwant = CURL_HTTP_VERSION_1_1;
+ data->state.http_neg.accept_09 = FALSE;
+ data->state.http_neg.only_10 = FALSE;
+ data->state.http_neg.wanted = CURL_HTTP_V1x;
+ data->state.http_neg.allowed = CURL_HTTP_V1x;
return Curl_http_setup_conn(data, conn);
}
|