diff options
Diffstat (limited to 'libs/libcurl/src/ws.c')
-rw-r--r-- | libs/libcurl/src/ws.c | 1501 |
1 files changed, 744 insertions, 757 deletions
diff --git a/libs/libcurl/src/ws.c b/libs/libcurl/src/ws.c index a673446625..bc18e61767 100644 --- a/libs/libcurl/src/ws.c +++ b/libs/libcurl/src/ws.c @@ -1,757 +1,744 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" -#include <curl/curl.h> - -#ifdef USE_WEBSOCKETS - -#include "urldata.h" -#include "dynbuf.h" -#include "rand.h" -#include "curl_base64.h" -#include "sendf.h" -#include "multiif.h" -#include "ws.h" -#include "easyif.h" -#include "transfer.h" -#include "nonblock.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -struct wsfield { - const char *name; - const char *val; -}; - -CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req) -{ - unsigned int i; - CURLcode result = CURLE_OK; - unsigned char rand[16]; - char *randstr; - size_t randlen; - char keyval[40]; - struct SingleRequest *k = &data->req; - struct wsfield heads[]= { - { - /* The request MUST contain an |Upgrade| header field whose value - MUST include the "websocket" keyword. */ - "Upgrade:", "websocket" - }, - { - /* The request MUST contain a |Connection| header field whose value - MUST include the "Upgrade" token. */ - "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", - }, - { - /* The request MUST include a header field with the name - |Sec-WebSocket-Key|. The value of this header field MUST be a nonce - 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, - } - }; - heads[3].val = &keyval[0]; - - /* 16 bytes random */ - result = Curl_rand(data, (unsigned char *)rand, sizeof(rand)); - if(result) - return result; - result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen); - if(result) - return result; - DEBUGASSERT(randlen < sizeof(keyval)); - if(randlen >= sizeof(keyval)) - return CURLE_FAILED_INIT; - strcpy(keyval, randstr); - free(randstr); - for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) { - if(!Curl_checkheaders(data, STRCONST(heads[i].name))) { -#ifdef USE_HYPER - char field[128]; - msnprintf(field, sizeof(field), "%s %s", heads[i].name, - heads[i].val); - result = Curl_hyper_header(data, req, field); -#else - (void)data; - result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name, - heads[i].val); -#endif - } - } - k->upgr101 = UPGR101_WS; - Curl_dyn_init(&data->req.p.http->ws.buf, MAX_WS_SIZE * 2); - return result; -} - -CURLcode Curl_ws_accept(struct Curl_easy *data) -{ - struct SingleRequest *k = &data->req; - struct HTTP *ws = data->req.p.http; - struct connectdata *conn = data->conn; - struct websocket *wsp = &data->req.p.http->ws; - CURLcode result; - - /* Verify the Sec-WebSocket-Accept response. - - The sent value is the base64 encoded version of a SHA-1 hash done on the - |Sec-WebSocket-Key| header field concatenated with - the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11". - */ - - /* If the response includes a |Sec-WebSocket-Extensions| header field and - this header field indicates the use of an extension that was not present - in the client's handshake (the server has indicated an extension not - requested by the client), the client MUST Fail the WebSocket Connection. - */ - - /* If the response includes a |Sec-WebSocket-Protocol| header field - and this header field indicates the use of a subprotocol that was - not present in the client's handshake (the server has indicated a - subprotocol not requested by the client), the client MUST Fail - the WebSocket Connection. */ - - /* 4 bytes random */ - result = Curl_rand(data, (unsigned char *)&ws->ws.mask, sizeof(ws->ws.mask)); - if(result) - return result; - - infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x", - ws->ws.mask[0], ws->ws.mask[1], ws->ws.mask[2], ws->ws.mask[3]); - k->upgr101 = UPGR101_RECEIVED; - - if(data->set.connect_only) - /* switch off non-blocking sockets */ - (void)curlx_nonblock(conn->sock[FIRSTSOCKET], FALSE); - - wsp->oleft = 0; - return result; -} - -#define WSBIT_FIN 0x80 -#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_PONG (0xa) -#define WSBIT_OPCODE_MASK (0xf) - -#define WSBIT_MASK 0x80 - -/* remove the spent bytes from the beginning of the buffer as that part has - now been delivered to the application */ -static void ws_decode_clear(struct Curl_easy *data) -{ - struct websocket *wsp = &data->req.p.http->ws; - size_t spent = wsp->usedbuf; - size_t len = Curl_dyn_len(&wsp->buf); - size_t keep = len - spent; - DEBUGASSERT(len >= spent); - Curl_dyn_tail(&wsp->buf, keep); -} - -/* ws_decode() decodes a binary frame into structured WebSocket data, - - wpkt - the incoming raw data. If NULL, work on the already buffered data. - ilen - the size of the provided data, perhaps too little, perhaps too much - out - stored pointed to extracted data - olen - stored length of the extracted data - oleft - number of unread bytes pending to that belongs to this frame - more - if there is more data in there - flags - stored bitmask about the frame - - Returns CURLE_AGAIN if there is only a partial frame in the buffer. Then it - stores the first part in the ->extra buffer to be used in the next call - when more data is provided. -*/ - -static CURLcode ws_decode(struct Curl_easy *data, - unsigned char *wpkt, size_t ilen, - unsigned char **out, size_t *olen, - curl_off_t *oleft, - bool *more, - unsigned int *flags) -{ - bool fin; - unsigned char opcode; - curl_off_t total; - size_t dataindex = 2; - curl_off_t plen; /* size of data in the buffer */ - curl_off_t payloadsize; - struct websocket *wsp = &data->req.p.http->ws; - unsigned char *p; - CURLcode result; - - *olen = 0; - - /* add the incoming bytes, if any */ - if(wpkt) { - result = Curl_dyn_addn(&wsp->buf, wpkt, ilen); - if(result) - return result; - } - - plen = Curl_dyn_len(&wsp->buf); - if(plen < 2) { - /* the smallest possible frame is two bytes */ - infof(data, "WS: plen == %u, EAGAIN", (int)plen); - return CURLE_AGAIN; - } - - p = Curl_dyn_uptr(&wsp->buf); - - fin = p[0] & WSBIT_FIN; - opcode = p[0] & WSBIT_OPCODE_MASK; - infof(data, "WS:%d received FIN bit %u", __LINE__, (int)fin); - *flags = 0; - switch(opcode) { - case WSBIT_OPCODE_CONT: - if(!fin) - *flags |= CURLWS_CONT; - infof(data, "WS: received OPCODE CONT"); - break; - case WSBIT_OPCODE_TEXT: - infof(data, "WS: received OPCODE TEXT"); - *flags |= CURLWS_TEXT; - break; - case WSBIT_OPCODE_BIN: - infof(data, "WS: received OPCODE BINARY"); - *flags |= CURLWS_BINARY; - break; - case WSBIT_OPCODE_CLOSE: - infof(data, "WS: received OPCODE CLOSE"); - *flags |= CURLWS_CLOSE; - break; - case WSBIT_OPCODE_PING: - infof(data, "WS: received OPCODE PING"); - *flags |= CURLWS_PING; - break; - case WSBIT_OPCODE_PONG: - infof(data, "WS: received OPCODE PONG"); - *flags |= CURLWS_PONG; - break; - } - - if(p[1] & WSBIT_MASK) { - /* A client MUST close a connection if it detects a masked frame. */ - failf(data, "WS: masked input frame"); - return CURLE_RECV_ERROR; - } - payloadsize = p[1]; - if(payloadsize == 126) { - if(plen < 4) { - infof(data, "WS:%d plen == %u, EAGAIN", __LINE__, (int)plen); - return CURLE_AGAIN; /* not enough data available */ - } - payloadsize = (p[2] << 8) | p[3]; - dataindex += 2; - } - else if(payloadsize == 127) { - /* 64 bit payload size */ - if(plen < 10) - return CURLE_AGAIN; - if(p[2] & 80) { - failf(data, "WS: too large frame"); - return CURLE_RECV_ERROR; - } - dataindex += 8; - payloadsize = ((curl_off_t)p[2] << 56) | - (curl_off_t)p[3] << 48 | - (curl_off_t)p[4] << 40 | - (curl_off_t)p[5] << 32 | - (curl_off_t)p[6] << 24 | - (curl_off_t)p[7] << 16 | - (curl_off_t)p[8] << 8 | - p[9]; - } - - total = dataindex + payloadsize; - if(total > plen) { - /* deliver a partial frame */ - *oleft = total - dataindex; - payloadsize = total - dataindex; - } - else { - *oleft = 0; - if(plen > total) - /* there is another fragment after */ - *more = TRUE; - } - - /* point to the payload */ - *out = &p[dataindex]; - - /* return the payload length */ - *olen = payloadsize; - - /* number of bytes "used" from the buffer */ - wsp->usedbuf = dataindex + payloadsize; - infof(data, "WS: received %zu bytes payload (%zu left)", - payloadsize, *oleft); - return CURLE_OK; -} - -/* Curl_ws_writecb() is the write callback for websocket traffic. The - websocket data is provided to this raw, in chunks. This function should - handle/decode the data and call the "real" underlying callback accordingly. -*/ -size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */, - size_t nitems, void *userp) -{ - struct HTTP *ws = (struct HTTP *)userp; - struct Curl_easy *data = ws->ws.data; - void *writebody_ptr = data->set.out; - if(data->set.ws_raw_mode) - return data->set.fwrite_func(buffer, size, nitems, writebody_ptr); - else if(nitems) { - unsigned char *frame = NULL; - size_t flen = 0; - size_t wrote = 0; - CURLcode result; - bool more; /* there's is more to parse in the buffer */ - curl_off_t oleft; - - decode: - more = FALSE; - oleft = ws->ws.frame.bytesleft; - if(!oleft) { - unsigned int recvflags; - result = ws_decode(data, (unsigned char *)buffer, nitems, - &frame, &flen, &oleft, &more, &recvflags); - if(result == CURLE_AGAIN) - /* insufficient amount of data, keep it for later */ - return nitems; - else if(result) { - infof(data, "WS: decode error %d", (int)result); - return nitems - 1; - } - /* Store details about the frame to be reachable with curl_ws_meta() - from within the write callback */ - ws->ws.frame.age = 0; - ws->ws.frame.offset = 0; - ws->ws.frame.flags = recvflags; - ws->ws.frame.bytesleft = oleft; - } - else { - if(nitems > (size_t)ws->ws.frame.bytesleft) { - nitems = ws->ws.frame.bytesleft; - more = TRUE; - } - else - more = FALSE; - ws->ws.frame.offset += nitems; - ws->ws.frame.bytesleft -= nitems; - frame = (unsigned char *)buffer; - flen = nitems; - } - if((ws->ws.frame.flags & CURLWS_PING) && !oleft) { - /* auto-respond to PINGs, only works for single-frame payloads atm */ - size_t bytes; - infof(data, "WS: auto-respond to PING with a PONG"); - DEBUGASSERT(frame); - /* send back the exact same content as a PONG */ - result = curl_ws_send(data, frame, flen, &bytes, 0, CURLWS_PONG); - if(result) - return result; - } - else { - /* deliver the decoded frame to the user callback */ - Curl_set_in_callback(data, true); - wrote = data->set.fwrite_func((char *)frame, 1, flen, writebody_ptr); - Curl_set_in_callback(data, false); - if(wrote != flen) - return 0; - } - if(oleft) - ws->ws.frame.offset += flen; - /* the websocket frame has been delivered */ - ws_decode_clear(data); - if(more) { - /* there's more websocket data to deal with in the buffer */ - buffer = NULL; /* the buffer as been drained already */ - goto decode; - } - } - return nitems; -} - - -CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, - size_t buflen, size_t *nread, - struct curl_ws_frame **metap) -{ - size_t bytes; - CURLcode result; - struct websocket *wsp = &data->req.p.http->ws; - - *nread = 0; - *metap = NULL; - /* get a download buffer */ - result = Curl_preconnect(data); - if(result) - return result; - - do { - bool drain = FALSE; /* if there is pending buffered data to drain */ - char *inbuf = data->state.buffer; - bytes = wsp->stillbuffer; - if(!bytes) { - result = curl_easy_recv(data, data->state.buffer, - data->set.buffer_size, &bytes); - if(result) - return result; - } - else { - /* the pending bytes can be found here */ - inbuf = wsp->stillb; - drain = TRUE; - } - if(bytes) { - unsigned char *out; - size_t olen; - bool more; - unsigned int recvflags; - curl_off_t oleft = wsp->frame.bytesleft; - - infof(data, "WS: got %u websocket bytes to decode", (int)bytes); - if(!oleft && !drain) { - result = ws_decode(data, (unsigned char *)inbuf, bytes, - &out, &olen, &oleft, &more, &recvflags); - if(result == CURLE_AGAIN) - /* a packet fragment only */ - break; - else if(result) - return result; - wsp->frame.offset = 0; - wsp->frame.bytesleft = oleft; - wsp->frame.flags = recvflags; - } - else { - olen = oleft; - out = (unsigned char *)wsp->stillb; - recvflags = wsp->frame.flags; - if((curl_off_t)buflen < oleft) - /* there is still data left after this */ - wsp->frame.bytesleft -= buflen; - else - wsp->frame.bytesleft = 0; - } - - /* auto-respond to PINGs */ - if((recvflags & CURLWS_PING) && !oleft) { - infof(data, "WS: auto-respond to PING with a PONG"); - /* send back the exact same content as a PONG */ - result = curl_ws_send(data, out, olen, &bytes, 0, CURLWS_PONG); - if(result) - return result; - } - else { - if(olen < buflen) { - /* copy the payload to the user buffer */ - memcpy(buffer, out, olen); - *nread = olen; - if(!oleft) - /* websocket frame has been delivered */ - ws_decode_clear(data); - } - else { - /* copy a partial payload */ - memcpy(buffer, out, buflen); - *nread = buflen; - /* remember what is left and where */ - wsp->stillbuffer = olen - buflen; - wsp->stillb = (char *)buffer + buflen; - } - wsp->frame.offset += *nread; - } - } - else - *nread = bytes; - break; - } while(1); - *metap = &wsp->frame; - return CURLE_OK; -} - -static void ws_xor(struct Curl_easy *data, - const unsigned char *source, - unsigned char *dest, - size_t len) -{ - struct websocket *wsp = &data->req.p.http->ws; - size_t i; - /* append payload after the mask, XOR appropriately */ - for(i = 0; i < len; i++) { - dest[i] = source[i] ^ wsp->mask[wsp->xori]; - wsp->xori++; - wsp->xori &= 3; - } -} - -/*** - RFC 6455 Section 5.2 - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-------+-+-------------+-------------------------------+ - |F|R|R|R| opcode|M| Payload len | Extended payload length | - |I|S|S|S| (4) |A| (7) | (16/64) | - |N|V|V|V| |S| | (if payload len==126/127) | - | |1|2|3| |K| | | - +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - | Extended payload length continued, if payload len == 127 | - + - - - - - - - - - - - - - - - +-------------------------------+ - | |Masking-key, if MASK set to 1 | - +-------------------------------+-------------------------------+ - | Masking-key (continued) | Payload Data | - +-------------------------------- - - - - - - - - - - - - - - - + - : Payload Data continued ... : - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - | Payload Data continued ... | - +---------------------------------------------------------------+ -*/ - -static size_t ws_packethead(struct Curl_easy *data, - size_t len, unsigned int flags) -{ - struct HTTP *ws = data->req.p.http; - unsigned char *out = (unsigned char *)data->state.ulbuf; - unsigned char firstbyte = 0; - int outi; - unsigned char opcode; - if(flags & CURLWS_TEXT) { - opcode = WSBIT_OPCODE_TEXT; - infof(data, "WS: send OPCODE TEXT"); - } - else if(flags & CURLWS_CLOSE) { - opcode = WSBIT_OPCODE_CLOSE; - infof(data, "WS: send OPCODE CLOSE"); - } - else if(flags & CURLWS_PING) { - opcode = WSBIT_OPCODE_PING; - infof(data, "WS: send OPCODE PING"); - } - else if(flags & CURLWS_PONG) { - opcode = WSBIT_OPCODE_PONG; - infof(data, "WS: send OPCODE PONG"); - } - else { - opcode = WSBIT_OPCODE_BIN; - infof(data, "WS: send OPCODE BINARY"); - } - - if(!(flags & CURLWS_CONT)) { - /* if not marked as continuing, assume this is the final fragment */ - firstbyte |= WSBIT_FIN | opcode; - ws->ws.contfragment = FALSE; - } - else if(ws->ws.contfragment) { - /* the previous fragment was not a final one and this isn't either, keep a - CONT opcode and no FIN bit */ - firstbyte |= WSBIT_OPCODE_CONT; - } - else { - ws->ws.contfragment = TRUE; - } - out[0] = firstbyte; - if(len > 65535) { - out[1] = 127 | WSBIT_MASK; - out[2] = (len >> 8) & 0xff; - out[3] = len & 0xff; - outi = 10; - } - else if(len > 126) { - out[1] = 126 | WSBIT_MASK; - out[2] = (len >> 8) & 0xff; - out[3] = len & 0xff; - outi = 4; - } - else { - out[1] = (unsigned char)len | WSBIT_MASK; - outi = 2; - } - - infof(data, "WS: send FIN bit %u (byte %02x)", - firstbyte & WSBIT_FIN ? 1 : 0, - firstbyte); - infof(data, "WS: send payload len %u", (int)len); - - /* 4 bytes mask */ - memcpy(&out[outi], &ws->ws.mask, 4); - - if(data->set.upload_buffer_size < (len + 10)) - return 0; - - /* pass over the mask */ - outi += 4; - - ws->ws.xori = 0; - /* return packet size */ - return outi; -} - -CURL_EXTERN CURLcode curl_ws_send(struct Curl_easy *data, const void *buffer, - size_t buflen, size_t *sent, - curl_off_t totalsize, - unsigned int sendflags) -{ - CURLcode result; - size_t headlen; - char *out; - ssize_t written; - struct websocket *wsp = &data->req.p.http->ws; - - if(!data->set.ws_raw_mode) { - result = Curl_get_upload_buffer(data); - if(result) - return result; - } - else { - if(totalsize || sendflags) - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - if(data->set.ws_raw_mode) { - if(!buflen) - /* nothing to do */ - return CURLE_OK; - /* raw mode sends exactly what was requested, and this is from within - the write callback */ - if(Curl_is_in_callback(data)) - result = Curl_write(data, data->conn->writesockfd, buffer, buflen, - &written); - else - result = Curl_senddata(data, buffer, buflen, &written); - - infof(data, "WS: wanted to send %zu bytes, sent %zu bytes", - buflen, written); - *sent = written; - return result; - } - - if(buflen > (data->set.upload_buffer_size - 10)) - /* don't do more than this in one go */ - buflen = data->set.upload_buffer_size - 10; - - if(sendflags & CURLWS_OFFSET) { - if(totalsize) { - /* a frame series 'totalsize' bytes big, this is the first */ - headlen = ws_packethead(data, totalsize, sendflags); - wsp->sleft = totalsize - buflen; - } - else { - headlen = 0; - if((curl_off_t)buflen > wsp->sleft) { - infof(data, "WS: unaligned frame size (sending %zu instead of %zu)", - buflen, wsp->sleft); - wsp->sleft = 0; - } - else - wsp->sleft -= buflen; - } - } - else - headlen = ws_packethead(data, buflen, sendflags); - - /* headlen is the size of the frame header */ - out = data->state.ulbuf; - if(buflen) - /* for PING and PONG etc there might not be a payload */ - ws_xor(data, buffer, (unsigned char *)out + headlen, buflen); - - if(data->set.connect_only) - result = Curl_senddata(data, out, buflen + headlen, &written); - else - result = Curl_write(data, data->conn->writesockfd, out, - buflen + headlen, &written); - - infof(data, "WS: wanted to send %zu bytes, sent %zu bytes", - headlen + buflen, written); - *sent = written; - - return result; -} - -void Curl_ws_done(struct Curl_easy *data) -{ - struct websocket *wsp = &data->req.p.http->ws; - DEBUGASSERT(wsp); - Curl_dyn_free(&wsp->buf); -} - -CURL_EXTERN struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data) -{ - /* we only return something for websocket, called from within the callback - when not using raw mode */ - if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->req.p.http && - !data->set.ws_raw_mode) - return &data->req.p.http->ws.frame; - return NULL; -} - -#else - -CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, - size_t *nread, - struct curl_ws_frame **metap) -{ - (void)curl; - (void)buffer; - (void)buflen; - (void)nread; - (void)metap; - return CURLE_OK; -} - -CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer, - size_t buflen, size_t *sent, - curl_off_t framesize, - unsigned int sendflags) -{ - (void)curl; - (void)buffer; - (void)buflen; - (void)sent; - (void)framesize; - (void)sendflags; - return CURLE_OK; -} - -CURL_EXTERN struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data) -{ - (void)data; - return NULL; -} -#endif /* USE_WEBSOCKETS */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+#include <curl/curl.h>
+
+#ifdef USE_WEBSOCKETS
+
+#include "urldata.h"
+#include "dynbuf.h"
+#include "rand.h"
+#include "curl_base64.h"
+#include "sendf.h"
+#include "multiif.h"
+#include "ws.h"
+#include "easyif.h"
+#include "transfer.h"
+#include "nonblock.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+struct wsfield {
+ const char *name;
+ const char *val;
+};
+
+CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req)
+{
+ unsigned int i;
+ CURLcode result = CURLE_OK;
+ unsigned char rand[16];
+ char *randstr;
+ size_t randlen;
+ char keyval[40];
+ struct SingleRequest *k = &data->req;
+ struct wsfield heads[]= {
+ {
+ /* The request MUST contain an |Upgrade| header field whose value
+ MUST include the "websocket" keyword. */
+ "Upgrade:", "websocket"
+ },
+ {
+ /* The request MUST contain a |Connection| header field whose value
+ MUST include the "Upgrade" token. */
+ "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",
+ },
+ {
+ /* The request MUST include a header field with the name
+ |Sec-WebSocket-Key|. The value of this header field MUST be a nonce
+ 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,
+ }
+ };
+ heads[3].val = &keyval[0];
+
+ /* 16 bytes random */
+ result = Curl_rand(data, (unsigned char *)rand, sizeof(rand));
+ if(result)
+ return result;
+ result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen);
+ if(result)
+ return result;
+ DEBUGASSERT(randlen < sizeof(keyval));
+ if(randlen >= sizeof(keyval))
+ return CURLE_FAILED_INIT;
+ strcpy(keyval, randstr);
+ free(randstr);
+ for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) {
+ if(!Curl_checkheaders(data, STRCONST(heads[i].name))) {
+#ifdef USE_HYPER
+ char field[128];
+ msnprintf(field, sizeof(field), "%s %s", heads[i].name,
+ heads[i].val);
+ result = Curl_hyper_header(data, req, field);
+#else
+ (void)data;
+ result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name,
+ heads[i].val);
+#endif
+ }
+ }
+ k->upgr101 = UPGR101_WS;
+ Curl_dyn_init(&data->req.p.http->ws.buf, MAX_WS_SIZE * 2);
+ return result;
+}
+
+CURLcode Curl_ws_accept(struct Curl_easy *data)
+{
+ struct SingleRequest *k = &data->req;
+ struct HTTP *ws = data->req.p.http;
+ struct connectdata *conn = data->conn;
+ struct websocket *wsp = &data->req.p.http->ws;
+ CURLcode result;
+
+ /* Verify the Sec-WebSocket-Accept response.
+
+ The sent value is the base64 encoded version of a SHA-1 hash done on the
+ |Sec-WebSocket-Key| header field concatenated with
+ the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".
+ */
+
+ /* If the response includes a |Sec-WebSocket-Extensions| header field and
+ this header field indicates the use of an extension that was not present
+ in the client's handshake (the server has indicated an extension not
+ requested by the client), the client MUST Fail the WebSocket Connection.
+ */
+
+ /* If the response includes a |Sec-WebSocket-Protocol| header field
+ and this header field indicates the use of a subprotocol that was
+ not present in the client's handshake (the server has indicated a
+ subprotocol not requested by the client), the client MUST Fail
+ the WebSocket Connection. */
+
+ /* 4 bytes random */
+ result = Curl_rand(data, (unsigned char *)&ws->ws.mask, sizeof(ws->ws.mask));
+ if(result)
+ return result;
+
+ infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
+ ws->ws.mask[0], ws->ws.mask[1], ws->ws.mask[2], ws->ws.mask[3]);
+ k->upgr101 = UPGR101_RECEIVED;
+
+ if(data->set.connect_only)
+ /* switch off non-blocking sockets */
+ (void)curlx_nonblock(conn->sock[FIRSTSOCKET], FALSE);
+
+ wsp->oleft = 0;
+ return result;
+}
+
+#define WSBIT_FIN 0x80
+#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_PONG (0xa)
+#define WSBIT_OPCODE_MASK (0xf)
+
+#define WSBIT_MASK 0x80
+
+/* remove the spent bytes from the beginning of the buffer as that part has
+ now been delivered to the application */
+static void ws_decode_shift(struct Curl_easy *data, size_t spent)
+{
+ struct websocket *wsp = &data->req.p.http->ws;
+ size_t len = Curl_dyn_len(&wsp->buf);
+ size_t keep = len - spent;
+ DEBUGASSERT(len >= spent);
+ Curl_dyn_tail(&wsp->buf, keep);
+}
+
+/* ws_decode() decodes a binary frame into structured WebSocket data,
+
+ wpkt - the incoming raw data. If NULL, work on the already buffered data.
+ ilen - the size of the provided data, perhaps too little, perhaps too much
+ out - stored pointed to extracted data
+ olen - stored length of the extracted data
+ oleft - number of unread bytes pending to that belongs to this frame
+ more - if there is more data in there
+ flags - stored bitmask about the frame
+
+ Returns CURLE_AGAIN if there is only a partial frame in the buffer. Then it
+ stores the first part in the ->extra buffer to be used in the next call
+ when more data is provided.
+*/
+
+static CURLcode ws_decode(struct Curl_easy *data,
+ unsigned char *inbuf, size_t inlen,
+ size_t *headlen, size_t *olen,
+ curl_off_t *oleft,
+ unsigned int *flags)
+{
+ bool fin;
+ unsigned char opcode;
+ curl_off_t total;
+ size_t dataindex = 2;
+ curl_off_t payloadsize;
+
+ *olen = *headlen = 0;
+
+ if(inlen < 2) {
+ /* the smallest possible frame is two bytes */
+ infof(data, "WS: plen == %u, EAGAIN", (int)inlen);
+ return CURLE_AGAIN;
+ }
+
+ fin = inbuf[0] & WSBIT_FIN;
+ opcode = inbuf[0] & WSBIT_OPCODE_MASK;
+ infof(data, "WS:%d received FIN bit %u", __LINE__, (int)fin);
+ *flags = 0;
+ switch(opcode) {
+ case WSBIT_OPCODE_CONT:
+ if(!fin)
+ *flags |= CURLWS_CONT;
+ infof(data, "WS: received OPCODE CONT");
+ break;
+ case WSBIT_OPCODE_TEXT:
+ infof(data, "WS: received OPCODE TEXT");
+ *flags |= CURLWS_TEXT;
+ break;
+ case WSBIT_OPCODE_BIN:
+ infof(data, "WS: received OPCODE BINARY");
+ *flags |= CURLWS_BINARY;
+ break;
+ case WSBIT_OPCODE_CLOSE:
+ infof(data, "WS: received OPCODE CLOSE");
+ *flags |= CURLWS_CLOSE;
+ break;
+ case WSBIT_OPCODE_PING:
+ infof(data, "WS: received OPCODE PING");
+ *flags |= CURLWS_PING;
+ break;
+ case WSBIT_OPCODE_PONG:
+ infof(data, "WS: received OPCODE PONG");
+ *flags |= CURLWS_PONG;
+ break;
+ }
+
+ if(inbuf[1] & WSBIT_MASK) {
+ /* A client MUST close a connection if it detects a masked frame. */
+ failf(data, "WS: masked input frame");
+ return CURLE_RECV_ERROR;
+ }
+ payloadsize = inbuf[1];
+ if(payloadsize == 126) {
+ if(inlen < 4) {
+ infof(data, "WS:%d plen == %u, EAGAIN", __LINE__, (int)inlen);
+ return CURLE_AGAIN; /* not enough data available */
+ }
+ payloadsize = (inbuf[2] << 8) | inbuf[3];
+ dataindex += 2;
+ }
+ else if(payloadsize == 127) {
+ /* 64 bit payload size */
+ if(inlen < 10)
+ return CURLE_AGAIN;
+ if(inbuf[2] & 80) {
+ failf(data, "WS: too large frame");
+ return CURLE_RECV_ERROR;
+ }
+ dataindex += 8;
+ payloadsize = ((curl_off_t)inbuf[2] << 56) |
+ (curl_off_t)inbuf[3] << 48 |
+ (curl_off_t)inbuf[4] << 40 |
+ (curl_off_t)inbuf[5] << 32 |
+ (curl_off_t)inbuf[6] << 24 |
+ (curl_off_t)inbuf[7] << 16 |
+ (curl_off_t)inbuf[8] << 8 |
+ inbuf[9];
+ }
+
+ /* point to the payload */
+ *headlen = dataindex;
+ total = dataindex + payloadsize;
+ if(total > (curl_off_t)inlen) {
+ /* buffer contains partial frame */
+ *olen = inlen - dataindex; /* bytes to write out */
+ *oleft = total - inlen; /* bytes yet to come (for this frame) */
+ payloadsize = total - dataindex;
+ }
+ else {
+ /* we have the complete frame (`total` bytes) in buffer */
+ *olen = payloadsize; /* bytes to write out */
+ *oleft = 0; /* bytes yet to come (for this frame) */
+ }
+
+ infof(data, "WS: received %zu bytes payload (%zu left, buflen was %zu)",
+ payloadsize, *oleft, inlen);
+ return CURLE_OK;
+}
+
+/* Curl_ws_writecb() is the write callback for websocket traffic. The
+ websocket data is provided to this raw, in chunks. This function should
+ handle/decode the data and call the "real" underlying callback accordingly.
+*/
+size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */,
+ size_t nitems, void *userp)
+{
+ struct HTTP *ws = (struct HTTP *)userp;
+ struct Curl_easy *data = ws->ws.data;
+ struct websocket *wsp = &data->req.p.http->ws;
+ void *writebody_ptr = data->set.out;
+ if(data->set.ws_raw_mode)
+ return data->set.fwrite_func(buffer, size, nitems, writebody_ptr);
+ else if(nitems) {
+ size_t wrote = 0, headlen;
+ CURLcode result;
+
+ if(buffer) {
+ result = Curl_dyn_addn(&wsp->buf, buffer, nitems);
+ if(result) {
+ infof(data, "WS: error adding data to buffer %d", (int)result);
+ return nitems - 1;
+ }
+ buffer = NULL;
+ }
+
+ while(Curl_dyn_len(&wsp->buf)) {
+ unsigned char *wsbuf = Curl_dyn_uptr(&wsp->buf);
+ size_t buflen = Curl_dyn_len(&wsp->buf);
+ size_t write_len = 0;
+ size_t consumed = 0;
+
+ if(!ws->ws.frame.bytesleft) {
+ unsigned int recvflags;
+ curl_off_t fb_left;
+
+ result = ws_decode(data, wsbuf, buflen,
+ &headlen, &write_len, &fb_left, &recvflags);
+ consumed += headlen;
+ wsbuf += headlen;
+ buflen -= headlen;
+ if(result == CURLE_AGAIN)
+ /* insufficient amount of data, keep it for later.
+ * we pretend to have written all since we have a copy */
+ return nitems;
+ else if(result) {
+ infof(data, "WS: decode error %d", (int)result);
+ return nitems - 1;
+ }
+ /* New frame. store details about the frame to be reachable with
+ curl_ws_meta() from within the write callback */
+ ws->ws.frame.age = 0;
+ ws->ws.frame.offset = 0;
+ ws->ws.frame.flags = recvflags;
+ ws->ws.frame.bytesleft = fb_left;
+ }
+ else {
+ /* continuing frame */
+ write_len = (size_t)ws->ws.frame.bytesleft;
+ if(write_len > buflen)
+ write_len = buflen;
+ ws->ws.frame.offset += write_len;
+ ws->ws.frame.bytesleft -= write_len;
+ }
+ if((ws->ws.frame.flags & CURLWS_PING) && !ws->ws.frame.bytesleft) {
+ /* auto-respond to PINGs, only works for single-frame payloads atm */
+ size_t bytes;
+ infof(data, "WS: auto-respond to PING with a PONG");
+ /* send back the exact same content as a PONG */
+ result = curl_ws_send(data, wsbuf, write_len,
+ &bytes, 0, CURLWS_PONG);
+ if(result)
+ return result;
+ }
+ else if(write_len || !wsp->frame.bytesleft) {
+ /* deliver the decoded frame to the user callback */
+ Curl_set_in_callback(data, true);
+ wrote = data->set.fwrite_func((char *)wsbuf, 1,
+ write_len, writebody_ptr);
+ Curl_set_in_callback(data, false);
+ if(wrote != write_len)
+ return 0;
+ }
+ /* get rid of the buffered data consumed */
+ consumed += write_len;
+ ws_decode_shift(data, consumed);
+ }
+ }
+ return nitems;
+}
+
+
+CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer,
+ size_t buflen, size_t *nread,
+ struct curl_ws_frame **metap)
+{
+ CURLcode result;
+ struct websocket *wsp = &data->req.p.http->ws;
+ bool done = FALSE; /* not filled passed buffer yet */
+
+ *nread = 0;
+ *metap = NULL;
+ /* get a download buffer */
+ result = Curl_preconnect(data);
+ if(result)
+ return result;
+
+ while(!done) {
+ size_t write_len;
+ unsigned int recvflags;
+
+ if(!wsp->stillblen) {
+ /* try to get more data */
+ size_t n;
+ result = curl_easy_recv(data, data->state.buffer,
+ data->set.buffer_size, &n);
+ if(result)
+ return result;
+ if(!n)
+ /* connection closed */
+ return CURLE_GOT_NOTHING;
+ wsp->stillb = data->state.buffer;
+ wsp->stillblen = n;
+ }
+
+ infof(data, "WS: got %u websocket bytes to decode",
+ (int)wsp->stillblen);
+ if(!wsp->frame.bytesleft) {
+ size_t headlen;
+ curl_off_t oleft;
+ /* detect new frame */
+ result = ws_decode(data, (unsigned char *)wsp->stillb, wsp->stillblen,
+ &headlen, &write_len, &oleft, &recvflags);
+ if(result == CURLE_AGAIN)
+ /* a packet fragment only */
+ break;
+ else if(result)
+ return result;
+ wsp->stillb += headlen;
+ wsp->stillblen -= headlen;
+ wsp->frame.offset = 0;
+ wsp->frame.bytesleft = oleft;
+ wsp->frame.flags = recvflags;
+ }
+ else {
+ /* existing frame, remaining payload handling */
+ write_len = wsp->frame.bytesleft;
+ if(write_len > wsp->stillblen)
+ write_len = wsp->stillblen;
+ }
+
+ /* auto-respond to PINGs */
+ if((wsp->frame.flags & CURLWS_PING) && !wsp->frame.bytesleft) {
+ infof(data, "WS: auto-respond to PING with a PONG");
+ /* send back the exact same content as a PONG */
+ result = curl_ws_send(data, wsp->stillb, write_len,
+ &write_len, 0, CURLWS_PONG);
+ if(result)
+ return result;
+ }
+ else if(write_len || !wsp->frame.bytesleft) {
+ if(write_len > buflen)
+ write_len = buflen;
+ /* copy the payload to the user buffer */
+ memcpy(buffer, wsp->stillb, write_len);
+ *nread = write_len;
+ done = TRUE;
+ }
+ if(write_len) {
+ /* update buffer and frame info */
+ wsp->frame.offset += write_len;
+ DEBUGASSERT(wsp->frame.bytesleft >= (curl_off_t)write_len);
+ if(wsp->frame.bytesleft)
+ wsp->frame.bytesleft -= write_len;
+ DEBUGASSERT(write_len <= wsp->stillblen);
+ wsp->stillblen -= write_len;
+ if(wsp->stillblen)
+ wsp->stillb += write_len;
+ else
+ wsp->stillb = NULL;
+ }
+ }
+ *metap = &wsp->frame;
+ return CURLE_OK;
+}
+
+static void ws_xor(struct Curl_easy *data,
+ const unsigned char *source,
+ unsigned char *dest,
+ size_t len)
+{
+ struct websocket *wsp = &data->req.p.http->ws;
+ size_t i;
+ /* append payload after the mask, XOR appropriately */
+ for(i = 0; i < len; i++) {
+ dest[i] = source[i] ^ wsp->mask[wsp->xori];
+ wsp->xori++;
+ wsp->xori &= 3;
+ }
+}
+
+/***
+ RFC 6455 Section 5.2
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-------+-+-------------+-------------------------------+
+ |F|R|R|R| opcode|M| Payload len | Extended payload length |
+ |I|S|S|S| (4) |A| (7) | (16/64) |
+ |N|V|V|V| |S| | (if payload len==126/127) |
+ | |1|2|3| |K| | |
+ +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ | Extended payload length continued, if payload len == 127 |
+ + - - - - - - - - - - - - - - - +-------------------------------+
+ | |Masking-key, if MASK set to 1 |
+ +-------------------------------+-------------------------------+
+ | Masking-key (continued) | Payload Data |
+ +-------------------------------- - - - - - - - - - - - - - - - +
+ : Payload Data continued ... :
+ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ | Payload Data continued ... |
+ +---------------------------------------------------------------+
+*/
+
+static size_t ws_packethead(struct Curl_easy *data,
+ size_t len, unsigned int flags)
+{
+ struct HTTP *ws = data->req.p.http;
+ unsigned char *out = (unsigned char *)data->state.ulbuf;
+ unsigned char firstbyte = 0;
+ int outi;
+ unsigned char opcode;
+ if(flags & CURLWS_TEXT) {
+ opcode = WSBIT_OPCODE_TEXT;
+ infof(data, "WS: send OPCODE TEXT");
+ }
+ else if(flags & CURLWS_CLOSE) {
+ opcode = WSBIT_OPCODE_CLOSE;
+ infof(data, "WS: send OPCODE CLOSE");
+ }
+ else if(flags & CURLWS_PING) {
+ opcode = WSBIT_OPCODE_PING;
+ infof(data, "WS: send OPCODE PING");
+ }
+ else if(flags & CURLWS_PONG) {
+ opcode = WSBIT_OPCODE_PONG;
+ infof(data, "WS: send OPCODE PONG");
+ }
+ else {
+ opcode = WSBIT_OPCODE_BIN;
+ infof(data, "WS: send OPCODE BINARY");
+ }
+
+ if(!(flags & CURLWS_CONT)) {
+ /* if not marked as continuing, assume this is the final fragment */
+ firstbyte |= WSBIT_FIN | opcode;
+ ws->ws.contfragment = FALSE;
+ }
+ else if(ws->ws.contfragment) {
+ /* the previous fragment was not a final one and this isn't either, keep a
+ CONT opcode and no FIN bit */
+ firstbyte |= WSBIT_OPCODE_CONT;
+ }
+ else {
+ ws->ws.contfragment = TRUE;
+ }
+ out[0] = firstbyte;
+ if(len > 65535) {
+ out[1] = 127 | WSBIT_MASK;
+ out[2] = (len >> 8) & 0xff;
+ out[3] = len & 0xff;
+ outi = 10;
+ }
+ else if(len > 126) {
+ out[1] = 126 | WSBIT_MASK;
+ out[2] = (len >> 8) & 0xff;
+ out[3] = len & 0xff;
+ outi = 4;
+ }
+ else {
+ out[1] = (unsigned char)len | WSBIT_MASK;
+ outi = 2;
+ }
+
+ infof(data, "WS: send FIN bit %u (byte %02x)",
+ firstbyte & WSBIT_FIN ? 1 : 0,
+ firstbyte);
+ infof(data, "WS: send payload len %u", (int)len);
+
+ /* 4 bytes mask */
+ memcpy(&out[outi], &ws->ws.mask, 4);
+
+ if(data->set.upload_buffer_size < (len + 10))
+ return 0;
+
+ /* pass over the mask */
+ outi += 4;
+
+ ws->ws.xori = 0;
+ /* return packet size */
+ return outi;
+}
+
+CURL_EXTERN CURLcode curl_ws_send(struct Curl_easy *data, const void *buffer,
+ size_t buflen, size_t *sent,
+ curl_off_t totalsize,
+ unsigned int sendflags)
+{
+ CURLcode result;
+ size_t headlen;
+ char *out;
+ ssize_t written;
+ struct websocket *wsp = &data->req.p.http->ws;
+
+ if(!data->set.ws_raw_mode) {
+ result = Curl_get_upload_buffer(data);
+ if(result)
+ return result;
+ }
+ else {
+ if(totalsize || sendflags)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+
+ if(data->set.ws_raw_mode) {
+ if(!buflen)
+ /* nothing to do */
+ return CURLE_OK;
+ /* raw mode sends exactly what was requested, and this is from within
+ the write callback */
+ if(Curl_is_in_callback(data)) {
+ if(!data->conn) {
+ failf(data, "No associated connection");
+ return CURLE_SEND_ERROR;
+ }
+ result = Curl_write(data, data->conn->writesockfd, buffer, buflen,
+ &written);
+ }
+ else
+ result = Curl_senddata(data, buffer, buflen, &written);
+
+ infof(data, "WS: wanted to send %zu bytes, sent %zu bytes",
+ buflen, written);
+ *sent = written;
+ return result;
+ }
+
+ if(buflen > (data->set.upload_buffer_size - 10))
+ /* don't do more than this in one go */
+ buflen = data->set.upload_buffer_size - 10;
+
+ if(sendflags & CURLWS_OFFSET) {
+ if(totalsize) {
+ /* a frame series 'totalsize' bytes big, this is the first */
+ headlen = ws_packethead(data, totalsize, sendflags);
+ wsp->sleft = totalsize - buflen;
+ }
+ else {
+ headlen = 0;
+ if((curl_off_t)buflen > wsp->sleft) {
+ infof(data, "WS: unaligned frame size (sending %zu instead of %zu)",
+ buflen, wsp->sleft);
+ wsp->sleft = 0;
+ }
+ else
+ wsp->sleft -= buflen;
+ }
+ }
+ else
+ headlen = ws_packethead(data, buflen, sendflags);
+
+ /* headlen is the size of the frame header */
+ out = data->state.ulbuf;
+ if(buflen)
+ /* for PING and PONG etc there might not be a payload */
+ ws_xor(data, buffer, (unsigned char *)out + headlen, buflen);
+
+ if(data->set.connect_only)
+ result = Curl_senddata(data, out, buflen + headlen, &written);
+ else
+ result = Curl_write(data, data->conn->writesockfd, out,
+ buflen + headlen, &written);
+
+ infof(data, "WS: wanted to send %zu bytes, sent %zu bytes",
+ headlen + buflen, written);
+ *sent = written;
+
+ return result;
+}
+
+void Curl_ws_done(struct Curl_easy *data)
+{
+ struct websocket *wsp = &data->req.p.http->ws;
+ DEBUGASSERT(wsp);
+ Curl_dyn_free(&wsp->buf);
+}
+
+CURL_EXTERN struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
+{
+ /* we only return something for websocket, called from within the callback
+ when not using raw mode */
+ if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->req.p.http &&
+ !data->set.ws_raw_mode)
+ return &data->req.p.http->ws.frame;
+ return NULL;
+}
+
+#else
+
+CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
+ size_t *nread,
+ struct curl_ws_frame **metap)
+{
+ (void)curl;
+ (void)buffer;
+ (void)buflen;
+ (void)nread;
+ (void)metap;
+ return CURLE_NOT_BUILT_IN;
+}
+
+CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
+ size_t buflen, size_t *sent,
+ curl_off_t framesize,
+ unsigned int sendflags)
+{
+ (void)curl;
+ (void)buffer;
+ (void)buflen;
+ (void)sent;
+ (void)framesize;
+ (void)sendflags;
+ return CURLE_NOT_BUILT_IN;
+}
+
+CURL_EXTERN struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
+{
+ (void)data;
+ return NULL;
+}
+#endif /* USE_WEBSOCKETS */
|