summaryrefslogtreecommitdiff
path: root/libs/libcurl/src/ws.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/libcurl/src/ws.c')
-rw-r--r--libs/libcurl/src/ws.c1501
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 */