#include "curl_setup.h"
#include <curl/curl.h>
#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP)
#include "urldata.h"
#include "url.h"
#include "bufq.h"
#include "curlx/dynbuf.h"
#include "rand.h"
#include "curlx/base64.h"
#include "connect.h"
#include "sendf.h"
#include "multiif.h"
#include "ws.h"
#include "easyif.h"
#include "transfer.h"
#include "select.h"
#include "curlx/nonblock.h"
#include "curlx/strparse.h"
#include "curl_memory.h"
#include "memdebug.h"
#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 (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)
#define WSBIT_MASK 0x80
#define WS_CHUNK_SIZE 65535
#define WS_CHUNK_COUNT 2
enum ws_dec_state {
WS_DEC_INIT,
WS_DEC_HEAD,
WS_DEC_PAYLOAD
};
struct ws_decoder {
int frame_age;
int frame_flags;
curl_off_t payload_offset;
curl_off_t payload_len;
unsigned char head[10];
int head_len, head_total;
enum ws_dec_state state;
int cont_flags;
};
struct ws_encoder {
curl_off_t payload_len;
curl_off_t payload_remain;
unsigned int xori;
unsigned char mask[4];
unsigned char firstbyte;
BIT(contfragment);
};
#define WS_MAX_CNTRL_LEN 125
struct ws_cntrl_frame {
unsigned int type;
size_t payload_len;
unsigned char payload[WS_MAX_CNTRL_LEN];
};
struct websocket {
struct Curl_easy *data;
struct ws_decoder dec;
struct ws_encoder enc;
struct bufq recvbuf;
struct bufq sendbuf;
struct curl_ws_frame recvframe;
struct ws_cntrl_frame pending;
size_t sendbuf_payload;
};
static const char *ws_frame_name_of_op(unsigned char firstbyte)
{
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 "???";
}
}
static int ws_frame_firstbyte2flags(struct Curl_easy *data,
unsigned char firstbyte, int cont_flags)
{
switch(firstbyte) {
case WSBIT_OPCODE_CONT:
if(!(cont_flags & CURLWS_CONT)) {
failf(data, "[WS] no ongoing fragmented message to resume");
return 0;
}
return cont_flags | CURLWS_CONT;
case (WSBIT_OPCODE_CONT | WSBIT_FIN):
if(!(cont_flags & CURLWS_CONT)) {
failf(data, "[WS] no ongoing fragmented message to resume");
return 0;
}
return cont_flags & ~CURLWS_CONT;
case WSBIT_OPCODE_TEXT:
if(cont_flags & CURLWS_CONT) {
failf(data, "[WS] fragmented message interrupted by new TEXT msg");
return 0;
}
return CURLWS_TEXT | CURLWS_CONT;
case (WSBIT_OPCODE_TEXT | WSBIT_FIN):
if(cont_flags & CURLWS_CONT) {
failf(data, "[WS] fragmented message interrupted by new TEXT msg");
return 0;
}
return CURLWS_TEXT;
case WSBIT_OPCODE_BIN:
if(cont_flags & CURLWS_CONT) {
failf(data, "[WS] fragmented message interrupted by new BINARY msg");
return 0;
}
return CURLWS_BINARY | CURLWS_CONT;
case (WSBIT_OPCODE_BIN | WSBIT_FIN):
if(cont_flags & CURLWS_CONT) {
failf(data, "[WS] fragmented message interrupted by new BINARY msg");
return 0;
}
return CURLWS_BINARY;
case WSBIT_OPCODE_CLOSE:
failf(data, "[WS] invalid fragmented CLOSE frame");
return 0;
case (WSBIT_OPCODE_CLOSE | WSBIT_FIN):
return CURLWS_CLOSE;
case WSBIT_OPCODE_PING:
failf(data, "[WS] invalid fragmented PING frame");
return 0;
case (WSBIT_OPCODE_PING | WSBIT_FIN):
return CURLWS_PING;
case WSBIT_OPCODE_PONG:
failf(data, "[WS] invalid fragmented PONG frame");
return 0;
case (WSBIT_OPCODE_PONG | WSBIT_FIN):
return CURLWS_PONG;
default:
if(firstbyte & WSBIT_RSV_MASK)
failf(data, "[WS] invalid reserved bits: %02x", firstbyte);
else
failf(data, "[WS] invalid opcode: %02x", firstbyte);
return 0;
}
}
static CURLcode ws_frame_flags2firstbyte(struct Curl_easy *data,
unsigned int flags,
bool contfragment,
unsigned char *pfirstbyte)
{
*pfirstbyte = 0;
switch(flags & ~CURLWS_OFFSET) {
case 0:
if(contfragment) {
CURL_TRC_WS(data, "no flags given; interpreting as continuation "
"fragment for compatibility");
*pfirstbyte = (WSBIT_OPCODE_CONT | WSBIT_FIN);
return CURLE_OK;
}
failf(data, "[WS] no flags given");
return CURLE_BAD_FUNCTION_ARGUMENT;
case CURLWS_CONT:
if(contfragment) {
infof(data, "[WS] setting CURLWS_CONT flag without message type is "
"supported for compatibility but highly discouraged");
*pfirstbyte = WSBIT_OPCODE_CONT;
return CURLE_OK;
}
failf(data, "[WS] No ongoing fragmented message to continue");
return CURLE_BAD_FUNCTION_ARGUMENT;
case CURLWS_TEXT:
*pfirstbyte = contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN)
: (WSBIT_OPCODE_TEXT | WSBIT_FIN);
return CURLE_OK;
case (CURLWS_TEXT | CURLWS_CONT):
*pfirstbyte = contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_TEXT;
return CURLE_OK;
case CURLWS_BINARY:
*pfirstbyte = contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN)
: (WSBIT_OPCODE_BIN | WSBIT_FIN);
return CURLE_OK;
case (CURLWS_BINARY | CURLWS_CONT):
*pfirstbyte = contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_BIN;
return CURLE_OK;
case CURLWS_CLOSE:
*pfirstbyte = WSBIT_OPCODE_CLOSE | WSBIT_FIN;
return CURLE_OK;
case (CURLWS_CLOSE | CURLWS_CONT):
failf(data, "[WS] CLOSE frame must not be fragmented");
return CURLE_BAD_FUNCTION_ARGUMENT;
case CURLWS_PING:
*pfirstbyte = WSBIT_OPCODE_PING | WSBIT_FIN;
return CURLE_OK;
case (CURLWS_PING | CURLWS_CONT):
failf(data, "[WS] PING frame must not be fragmented");
return CURLE_BAD_FUNCTION_ARGUMENT;
case CURLWS_PONG:
*pfirstbyte = WSBIT_OPCODE_PONG | WSBIT_FIN;
return CURLE_OK;
case (CURLWS_PONG | CURLWS_CONT):
failf(data, "[WS] PONG frame must not be fragmented");
return CURLE_BAD_FUNCTION_ARGUMENT;
default:
failf(data, "[WS] unknown flags: %x", flags);
return CURLE_BAD_FUNCTION_ARGUMENT;
}
}
static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data,
const char *msg)
{
switch(dec->head_len) {
case 0:
break;
case 1:
CURL_TRC_WS(data, "decoded %s [%s%s]", msg,
ws_frame_name_of_op(dec->head[0]),
(dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL");
break;
default:
if(dec->head_len < dec->head_total) {
CURL_TRC_WS(data, "decoded %s [%s%s](%d/%d)", msg,
ws_frame_name_of_op(dec->head[0]),
(dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
dec->head_len, dec->head_total);
}
else {
CURL_TRC_WS(data, "decoded %s [%s%s payload=%"
FMT_OFF_T "/%" FMT_OFF_T "]",
msg, ws_frame_name_of_op(dec->head[0]),
(dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
dec->payload_offset, dec->payload_len);
}
break;
}
}
static CURLcode ws_send_raw_blocking(struct Curl_easy *data,
struct websocket *ws,
const char *buffer, size_t buflen);
typedef CURLcode ws_write_payload(const unsigned char *buf, size_t buflen,
int frame_age, int frame_flags,
curl_off_t payload_offset,
curl_off_t payload_len,
void *userp,
size_t *pnwritten);
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;
}
static void ws_dec_reset(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 = 0;
}
static void ws_dec_init(struct ws_decoder *dec)
{
ws_dec_reset(dec);
}
static CURLcode ws_dec_read_head(struct ws_decoder *dec,
struct Curl_easy *data,
struct bufq *inraw)
{
const unsigned char *inbuf;
size_t inlen;
while(Curl_bufq_peek(inraw, &inbuf, &inlen)) {
if(dec->head_len == 0) {
dec->head[0] = *inbuf;
Curl_bufq_skip(inraw, 1);
dec->frame_flags = ws_frame_firstbyte2flags(data, dec->head[0],
dec->cont_flags);
if(!dec->frame_flags) {
ws_dec_reset(dec);
return CURLE_RECV_ERROR;
}
if(dec->frame_flags & (CURLWS_TEXT | CURLWS_BINARY)) {
dec->cont_flags = dec->frame_flags;
}
dec->head_len = 1;
continue;
}
else if(dec->head_len == 1) {
dec->head[1] = *inbuf;
Curl_bufq_skip(inraw, 1);
dec->head_len = 2;
if(dec->head[1] & WSBIT_MASK) {
failf(data, "[WS] masked input frame");
ws_dec_reset(dec);
return CURLE_RECV_ERROR;
}
if(dec->frame_flags & CURLWS_PING && dec->head[1] > WS_MAX_CNTRL_LEN) {
failf(data, "[WS] received PING frame is too big");
ws_dec_reset(dec);
return CURLE_RECV_ERROR;
}
if(dec->frame_flags & CURLWS_PONG && dec->head[1] > WS_MAX_CNTRL_LEN) {
failf(data, "[WS] received PONG frame is too big");
ws_dec_reset(dec);
return CURLE_RECV_ERROR;
}
if(dec->frame_flags & CURLWS_CLOSE && dec->head[1] > WS_MAX_CNTRL_LEN) {
failf(data, "[WS] received CLOSE frame is too big");
ws_dec_reset(dec);
return CURLE_RECV_ERROR;
}
if(dec->head[1] == 126) {
dec->head_total = 4;
continue;
}
else if(dec->head[1] == 127) {
dec->head_total = 10;
continue;
}
else {
dec->head_total = 2;
}
}
if(dec->head_len < dec->head_total) {
dec->head[dec->head_len] = *inbuf;
Curl_bufq_skip(inraw, 1);
++dec->head_len;
if(dec->head_len < dec->head_total) {
continue;
}
}
DEBUGASSERT(dec->head_len == dec->head_total);
switch(dec->head_total) {
case 2:
dec->payload_len = dec->head[1];
break;
case 4:
dec->payload_len = (dec->head[2] << 8) | dec->head[3];
break;
case 10:
if(dec->head[2] > 127) {
failf(data, "[WS] frame length longer than 63 bit not supported");
return CURLE_RECV_ERROR;
}
dec->payload_len = ((curl_off_t)dec->head[2] << 56) |
(curl_off_t)dec->head[3] << 48 |
(curl_off_t)dec->head[4] << 40 |
(curl_off_t)dec->head[5] << 32 |
(curl_off_t)dec->head[6] << 24 |
(curl_off_t)dec->head[7] << 16 |
(curl_off_t)dec->head[8] << 8 |
dec->head[9];
break;
default:
DEBUGASSERT(0);
failf(data, "[WS] unexpected frame header length");
return CURLE_RECV_ERROR;
}
dec->frame_age = 0;
dec->payload_offset = 0;
ws_dec_info(dec, data, "decoded");
return CURLE_OK;
}
return CURLE_AGAIN;
}
static CURLcode ws_dec_pass_payload(struct ws_decoder *dec,
struct Curl_easy *data,
struct bufq *inraw,
ws_write_payload *write_cb,
void *write_ctx)
{
const unsigned char *inbuf;
size_t inlen;
size_t nwritten;
CURLcode result;
curl_off_t remain = dec->payload_len - dec->payload_offset;
(void)data;
while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen)) {
if((curl_off_t)inlen > remain)
inlen = (size_t)remain;
result = write_cb(inbuf, inlen, dec->frame_age, dec->frame_flags,
dec->payload_offset, dec->payload_len,
write_ctx, &nwritten);
if(result)
return result;
Curl_bufq_skip(inraw, nwritten);
dec->payload_offset += nwritten;
remain = dec->payload_len - dec->payload_offset;
CURL_TRC_WS(data, "passed %zu bytes payload, %"
FMT_OFF_T " remain", nwritten, remain);
}
return remain ? CURLE_AGAIN : CURLE_OK;
}
static CURLcode ws_dec_pass(struct ws_decoder *dec,
struct Curl_easy *data,
struct bufq *inraw,
ws_write_payload *write_cb,
void *write_ctx)
{
CURLcode result;
if(Curl_bufq_is_empty(inraw))
return CURLE_AGAIN;
switch(dec->state) {
case WS_DEC_INIT:
ws_dec_next_frame(dec);
dec->state = WS_DEC_HEAD;
FALLTHROUGH();
case WS_DEC_HEAD:
result = ws_dec_read_head(dec, data, inraw);
if(result) {
if(result != CURLE_AGAIN) {
failf(data, "[WS] decode frame error %d", (int)result);
break;
}
DEBUGASSERT(Curl_bufq_is_empty(inraw));
break;
}
dec->state = WS_DEC_PAYLOAD;
if(dec->payload_len == 0) {
size_t nwritten;
const unsigned char tmp = '\0';
result = write_cb(&tmp, 0, dec->frame_age, dec->frame_flags,
0, 0, write_ctx, &nwritten);
if(result)
return result;
dec->state = WS_DEC_INIT;
break;
}
FALLTHROUGH();
case WS_DEC_PAYLOAD:
result = ws_dec_pass_payload(dec, data, inraw, write_cb, write_ctx);
ws_dec_info(dec, data, "passing");
if(result)
return result;
dec->state = WS_DEC_INIT;
break;
default:
result = CURLE_FAILED_INIT;
}
return result;
}
static void update_meta(struct websocket *ws,
int frame_age, int frame_flags,
curl_off_t payload_offset,
curl_off_t payload_len,
size_t cur_len)
{
curl_off_t bytesleft = (payload_len - payload_offset - cur_len);
ws->recvframe.age = frame_age;
ws->recvframe.flags = frame_flags;
ws->recvframe.offset = payload_offset;
ws->recvframe.len = cur_len;
ws->recvframe.bytesleft = bytesleft;
}
struct ws_cw_ctx {
struct Curl_cwriter super;
struct bufq buf;
};
static CURLcode ws_cw_init(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct ws_cw_ctx *ctx = writer->ctx;
(void)data;
Curl_bufq_init2(&ctx->buf, WS_CHUNK_SIZE, 1, BUFQ_OPT_SOFT_LIMIT);
return CURLE_OK;
}
static void ws_cw_close(struct Curl_easy *data, struct Curl_cwriter *writer)
{
struct ws_cw_ctx *ctx = writer->ctx;
(void)data;
Curl_bufq_free(&ctx->buf);
}
struct ws_cw_dec_ctx {
struct Curl_easy *data;
struct websocket *ws;
struct Curl_cwriter *next_writer;
int cw_type;
};
static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
bool blocking);
static CURLcode ws_enc_send(struct Curl_easy *data,
struct websocket *ws,
const unsigned char *buffer,
size_t buflen,
curl_off_t fragsize,
unsigned int flags,
size_t *sent);
static CURLcode ws_enc_add_pending(struct Curl_easy *data,
struct websocket *ws);
static CURLcode ws_enc_add_cntrl(struct Curl_easy *data,
struct websocket *ws,
const unsigned char *payload,
size_t plen,
unsigned int frame_type)
{
DEBUGASSERT(plen <= WS_MAX_CNTRL_LEN);
if(plen > WS_MAX_CNTRL_LEN)
return CURLE_BAD_FUNCTION_ARGUMENT;
ws->pending.type = frame_type;
ws->pending.payload_len = plen;
memcpy(ws->pending.payload, payload, plen);
if(!ws->enc.payload_remain) {
CURLcode result = ws_enc_add_pending(data, ws);
if(!result)
(void)ws_flush(data, ws, Curl_is_in_callback(data));
return result;
}
return CURLE_OK;
}
static curl_off_t ws_payload_remain(curl_off_t payload_total,
curl_off_t payload_offset,
size_t payload_buffered)
{
curl_off_t remain = payload_total - payload_offset;
if((payload_total < 0) || (payload_offset < 0) || (remain < 0))
return -1;
#if SIZEOF_SIZE_T >= SIZEOF_CURL_OFF_T
if(payload_buffered > (size_t)CURL_OFF_T_MAX)
return -1;
#endif
if(remain < (curl_off_t)payload_buffered)
return -1;
return remain - (curl_off_t)payload_buffered;
}
static CURLcode ws_cw_dec_next(const unsigned char *buf, size_t buflen,
int frame_age, int frame_flags,
curl_off_t payload_offset,
curl_off_t payload_len,
void *user_data,
size_t *pnwritten)
{
struct ws_cw_dec_ctx *ctx = user_data;
struct Curl_easy *data = ctx->data;
struct websocket *ws = ctx->ws;
bool auto_pong = !data->set.ws_no_auto_pong;
curl_off_t remain;
CURLcode result;
(void)frame_age;
*pnwritten = 0;
remain = ws_payload_remain(payload_len, payload_offset, buflen);
if(remain < 0) {
DEBUGASSERT(0);
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
CURL_TRC_WS(data, "auto PONG to [PING payload=%" FMT_OFF_T
"/%" FMT_OFF_T "]", payload_offset, payload_len);
result = ws_enc_add_cntrl(data, ws, buf, buflen, CURLWS_PONG);
if(result)
return result;
}
else if(buflen || !remain) {
update_meta(ws, frame_age, frame_flags, payload_offset,
payload_len, buflen);
result = Curl_cwriter_write(data, ctx->next_writer,
(ctx->cw_type | CLIENTWRITE_0LEN),
(const char *)buf, buflen);
if(result)
return result;
}
*pnwritten = buflen;
return CURLE_OK;
}
static CURLcode ws_cw_write(struct Curl_easy *data,
struct Curl_cwriter *writer, int type,
const char *buf, size_t nbytes)
{
struct ws_cw_ctx *ctx = writer->ctx;
struct websocket *ws;
CURLcode result;
CURL_TRC_WRITE(data, "ws_cw_write(len=%zu, type=%d)", nbytes, type);
if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);
ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
failf(data, "[WS] not a websocket transfer");
return CURLE_FAILED_INIT;
}
if(nbytes) {
size_t nwritten;
result = Curl_bufq_write(&ctx->buf, (const unsigned char *)buf,
nbytes, &nwritten);
if(result) {
infof(data, "[WS] error adding data to buffer %d", result);
return result;
}
}
while(!Curl_bufq_is_empty(&ctx->buf)) {
struct ws_cw_dec_ctx pass_ctx;
pass_ctx.data = data;
pass_ctx.ws = ws;
pass_ctx.next_writer = writer->next;
pass_ctx.cw_type = type;
result = ws_dec_pass(&ws->dec, data, &ctx->buf,
ws_cw_dec_next, &pass_ctx);
if(result == CURLE_AGAIN) {
return CURLE_OK;
}
else if(result) {
failf(data, "[WS] decode payload error %d", (int)result);
return result;
}
}
if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) {
failf(data, "[WS] decode ending with %zd frame bytes remaining",
Curl_bufq_len(&ctx->buf));
return CURLE_RECV_ERROR;
}
return CURLE_OK;
}
static const struct Curl_cwtype ws_cw_decode = {
"ws-decode",
NULL,
ws_cw_init,
ws_cw_write,
ws_cw_close,
sizeof(struct ws_cw_ctx)
};
static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data,
const char *msg)
{
CURL_TRC_WS(data, "WS-ENC: %s [%s%s payload=%"
FMT_OFF_T "/%" FMT_OFF_T "]",
msg, ws_frame_name_of_op(enc->firstbyte),
(enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN",
enc->payload_len - enc->payload_remain, enc->payload_len);
}
static void ws_enc_reset(struct ws_encoder *enc)
{
enc->payload_remain = 0;
enc->xori = 0;
enc->contfragment = FALSE;
}
static void ws_enc_init(struct ws_encoder *enc)
{
ws_enc_reset(enc);
}
static CURLcode ws_enc_add_frame(struct Curl_easy *data,
struct ws_encoder *enc,
unsigned int flags,
curl_off_t payload_len,
struct bufq *out)
{
unsigned char firstb = 0;
unsigned char head[14];
CURLcode result;
size_t hlen, nwritten;
if(payload_len < 0) {
failf(data, "[WS] starting new frame with negative payload length %"
FMT_OFF_T, payload_len);
return CURLE_SEND_ERROR;
}
if(enc->payload_remain > 0) {
failf(data, "[WS] starting new frame with %zd bytes from last one "
"remaining to be sent", (ssize_t)enc->payload_remain);
return CURLE_SEND_ERROR;
}
result = ws_frame_flags2firstbyte(data, flags, enc->contfragment, &firstb);
if(result)
return result;
if(flags & (CURLWS_TEXT | CURLWS_BINARY)) {
enc->contfragment = (flags & CURLWS_CONT) ? (bit)TRUE : (bit)FALSE;
}
if(flags & CURLWS_PING && payload_len > WS_MAX_CNTRL_LEN) {
failf(data, "[WS] given PING frame is too big");
return CURLE_TOO_LARGE;
}
if(flags & CURLWS_PONG && payload_len > WS_MAX_CNTRL_LEN) {
failf(data, "[WS] given PONG frame is too big");
return CURLE_TOO_LARGE;
}
if(flags & CURLWS_CLOSE && payload_len > WS_MAX_CNTRL_LEN) {
failf(data, "[WS] given CLOSE frame is too big");
return CURLE_TOO_LARGE;
}
head[0] = enc->firstbyte = firstb;
if(payload_len > 65535) {
head[1] = 127 | WSBIT_MASK;
head[2] = (unsigned char)((payload_len >> 56) & 0xff);
head[3] = (unsigned char)((payload_len >> 48) & 0xff);
head[4] = (unsigned char)((payload_len >> 40) & 0xff);
head[5] = (unsigned char)((payload_len >> 32) & 0xff);
head[6] = (unsigned char)((payload_len >> 24) & 0xff);
head[7] = (unsigned char)((payload_len >> 16) & 0xff);
head[8] = (unsigned char)((payload_len >> 8) & 0xff);
head[9] = (unsigned char)(payload_len & 0xff);
hlen = 10;
}
else if(payload_len >= 126) {
head[1] = 126 | WSBIT_MASK;
head[2] = (unsigned char)((payload_len >> 8) & 0xff);
head[3] = (unsigned char)(payload_len & 0xff);
hlen = 4;
}
else {
head[1] = (unsigned char)payload_len | WSBIT_MASK;
hlen = 2;
}
enc->payload_remain = enc->payload_len = payload_len;
ws_enc_info(enc, data, "sending");
result = Curl_rand(data, (unsigned char *)&enc->mask, sizeof(enc->mask));
if(result)
return result;
#ifdef DEBUGBUILD
if(getenv("CURL_WS_FORCE_ZERO_MASK"))
memset(&enc->mask, 0, sizeof(enc->mask));
#endif
memcpy(&head[hlen], &enc->mask, 4);
hlen += 4;
enc->xori = 0;
result = Curl_bufq_write(out, head, hlen, &nwritten);
if(result)
return result;
if(nwritten != hlen) {
DEBUGASSERT(0);
return CURLE_SEND_ERROR;
}
return CURLE_OK;
}
static CURLcode ws_enc_write_head(struct Curl_easy *data,
struct websocket *ws,
struct ws_encoder *enc,
unsigned int flags,
curl_off_t payload_len,
struct bufq *out)
{
if(ws->pending.type) {
CURLcode result = ws_enc_add_pending(data, ws);
if(result)
return result;
}
return ws_enc_add_frame(data, enc, flags, payload_len, out);
}
static CURLcode ws_enc_write_payload(struct ws_encoder *enc,
struct Curl_easy *data,
const unsigned char *buf, size_t buflen,
struct bufq *out, size_t *pnwritten)
{
CURLcode result;
size_t i, len, n;
*pnwritten = 0;
if(Curl_bufq_is_full(out))
return CURLE_AGAIN;
len = buflen;
if((curl_off_t)len > enc->payload_remain)
len = (size_t)enc->payload_remain;
for(i = 0; i < len; ++i) {
unsigned char c = buf[i] ^ enc->mask[enc->xori];
result = Curl_bufq_write(out, &c, 1, &n);
if(result) {
if((result != CURLE_AGAIN) || !i)
return result;
break;
}
enc->xori++;
enc->xori &= 3;
}
*pnwritten = i;
enc->payload_remain -= (curl_off_t)i;
ws_enc_info(enc, data, "buffered");
return CURLE_OK;
}
static CURLcode ws_enc_add_pending(struct Curl_easy *data,
struct websocket *ws)
{
CURLcode result;
size_t n;
if(!ws->pending.type)
return CURLE_OK;
if(ws->enc.payload_remain)
return CURLE_AGAIN;
result = ws_enc_add_frame(data, &ws->enc, ws->pending.type,
(curl_off_t)ws->pending.payload_len,
&ws->sendbuf);
if(result) {
CURL_TRC_WS(data, "ws_enc_cntrl(), error adding head: %d",
result);
goto out;
}
result = ws_enc_write_payload(&ws->enc, data, ws->pending.payload,
ws->pending.payload_len,
&ws->sendbuf, &n);
if(result) {
CURL_TRC_WS(data, "ws_enc_cntrl(), error adding payload: %d",
result);
goto out;
}
if(n != ws->pending.payload_len) {
DEBUGASSERT(0);
CURL_TRC_WS(data, "ws_enc_cntrl(), error added only %zu/%zu payload,",
n, ws->pending.payload_len);
result = CURLE_SEND_ERROR;
goto out;
}
DEBUGASSERT(!ws->enc.payload_remain);
memset(&ws->pending, 0, sizeof(ws->pending));
out:
return result;
}
static CURLcode ws_enc_send(struct Curl_easy *data,
struct websocket *ws,
const unsigned char *buffer,
size_t buflen,
curl_off_t fragsize,
unsigned int flags,
size_t *pnsent)
{
size_t n;
CURLcode result = CURLE_OK;
DEBUGASSERT(!data->set.ws_raw_mode);
*pnsent = 0;
if(ws->enc.payload_remain || !Curl_bufq_is_empty(&ws->sendbuf)) {
if(buflen < ws->sendbuf_payload) {
failf(data, "[WS] curl_ws_send() called with smaller 'buflen' than "
"bytes already buffered in previous call, %zu vs %zu",
buflen, ws->sendbuf_payload);
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if((curl_off_t)buflen >
(ws->enc.payload_remain + (curl_off_t)ws->sendbuf_payload)) {
failf(data, "[WS] unaligned frame size (sending %zu instead of %"
FMT_OFF_T ")",
buflen, ws->enc.payload_remain + ws->sendbuf_payload);
return CURLE_BAD_FUNCTION_ARGUMENT;
}
}
else {
result = ws_flush(data, ws, Curl_is_in_callback(data));
if(result)
return result;
result = ws_enc_write_head(data, ws, &ws->enc, flags,
(flags & CURLWS_OFFSET) ?
fragsize : (curl_off_t)buflen,
&ws->sendbuf);
if(result) {
CURL_TRC_WS(data, "curl_ws_send(), error writing frame head %d", result);
return result;
}
}
while(!Curl_bufq_is_empty(&ws->sendbuf) || (buflen > ws->sendbuf_payload)) {
if(buflen > ws->sendbuf_payload) {
size_t prev_len = Curl_bufq_len(&ws->sendbuf);
result = ws_enc_write_payload(&ws->enc, data,
buffer + ws->sendbuf_payload,
buflen - ws->sendbuf_payload,
&ws->sendbuf, &n);
if(result && (result != CURLE_AGAIN))
return result;
ws->sendbuf_payload += Curl_bufq_len(&ws->sendbuf) - prev_len;
if(!ws->sendbuf_payload) {
return CURLE_AGAIN;
}
}
result = ws_flush(data, ws, Curl_is_in_callback(data));
if(!result && ws->sendbuf_payload > 0) {
*pnsent += ws->sendbuf_payload;
buffer += ws->sendbuf_payload;
buflen -= ws->sendbuf_payload;
ws->sendbuf_payload = 0;
}
else if(result == CURLE_AGAIN) {
if(ws->sendbuf_payload > Curl_bufq_len(&ws->sendbuf)) {
size_t flushed = (ws->sendbuf_payload - Curl_bufq_len(&ws->sendbuf));
*pnsent += flushed;
ws->sendbuf_payload -= flushed;
return CURLE_OK;
}
else {
CURL_TRC_WS(data, "EAGAIN flushing sendbuf, payload_encoded: %zu/%zu",
ws->sendbuf_payload, buflen);
DEBUGASSERT(*pnsent == 0);
return CURLE_AGAIN;
}
}
else
return result;
}
return CURLE_OK;
}
struct cr_ws_ctx {
struct Curl_creader super;
BIT(read_eos);
BIT(eos);
};
static CURLcode cr_ws_init(struct Curl_easy *data, struct Curl_creader *reader)
{
(void)data;
(void)reader;
return CURLE_OK;
}
static void cr_ws_close(struct Curl_easy *data, struct Curl_creader *reader)
{
(void)data;
(void)reader;
}
static CURLcode cr_ws_read(struct Curl_easy *data,
struct Curl_creader *reader,
char *buf, size_t blen,
size_t *pnread, bool *peos)
{
struct cr_ws_ctx *ctx = reader->ctx;
CURLcode result = CURLE_OK;
size_t nread, n;
struct websocket *ws;
bool eos;
*pnread = 0;
if(ctx->eos) {
*peos = TRUE;
return CURLE_OK;
}
ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
failf(data, "[WS] not a websocket transfer");
return CURLE_FAILED_INIT;
}
if(Curl_bufq_is_empty(&ws->sendbuf)) {
if(ctx->read_eos) {
ctx->eos = TRUE;
*peos = TRUE;
return CURLE_OK;
}
if(ws->enc.payload_remain) {
CURL_TRC_WS(data, "current frame, %" FMT_OFF_T " remaining",
ws->enc.payload_remain);
if(ws->enc.payload_remain < (curl_off_t)blen)
blen = (size_t)ws->enc.payload_remain;
}
result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos);
if(result)
return result;
ctx->read_eos = eos;
if(!Curl_bufq_is_empty(&ws->sendbuf)) {
ctx->read_eos = FALSE;
Curl_creader_clear_eos(data, reader->next);
}
else if(!nread) {
if(ctx->read_eos)
ctx->eos = TRUE;
*pnread = nread;
*peos = ctx->eos;
goto out;
}
if(!ws->enc.payload_remain && Curl_bufq_is_empty(&ws->sendbuf)) {
result = ws_enc_write_head(data, ws, &ws->enc, CURLWS_BINARY, nread,
&ws->sendbuf);
if(result)
goto out;
}
result = ws_enc_write_payload(&ws->enc, data, (unsigned char *)buf,
nread, &ws->sendbuf, &n);
if(result)
goto out;
CURL_TRC_READ(data, "cr_ws_read, added %zu payload, len=%zu", nread, n);
}
DEBUGASSERT(!Curl_bufq_is_empty(&ws->sendbuf));
*peos = FALSE;
result = Curl_bufq_cread(&ws->sendbuf, buf, blen, pnread);
if(!result && ctx->read_eos && Curl_bufq_is_empty(&ws->sendbuf)) {
ctx->eos = TRUE;
*peos = TRUE;
}
out:
CURL_TRC_READ(data, "cr_ws_read(len=%zu) -> %d, nread=%zu, eos=%d",
blen, result, *pnread, *peos);
return result;
}
static const struct Curl_crtype ws_cr_encode = {
"ws-encode",
cr_ws_init,
cr_ws_read,
cr_ws_close,
Curl_creader_def_needs_rewind,
Curl_creader_def_total_length,
Curl_creader_def_resume_from,
Curl_creader_def_cntrl,
Curl_creader_def_is_paused,
Curl_creader_def_done,
sizeof(struct cr_ws_ctx)
};
struct wsfield {
const char *name;
const char *val;
};
CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *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[]= {
{
"Upgrade", "websocket"
},
{
"Sec-WebSocket-Version", "13",
},
{
"Sec-WebSocket-Key", NULL,
}
};
heads[2].val = &keyval[0];
result = Curl_rand(data, (unsigned char *)rand, sizeof(rand));
if(result)
return result;
result = curlx_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen);
if(result)
return result;
DEBUGASSERT(randlen < sizeof(keyval));
if(randlen >= sizeof(keyval)) {
free(randstr);
return CURLE_FAILED_INIT;
}
strcpy(keyval, randstr);
free(randstr);
for(i = 0; !result && (i < CURL_ARRAYSIZE(heads)); i++) {
if(!Curl_checkheaders(data, heads[i].name, strlen(heads[i].name))) {
result = curlx_dyn_addf(req, "%s: %s\r\n", heads[i].name,
heads[i].val);
}
}
data->state.http_hd_upgrade = TRUE;
k->upgr101 = UPGR101_WS;
data->conn->bits.upgrade_in_progress = TRUE;
return result;
}
static void ws_conn_dtor(void *key, size_t klen, void *entry)
{
struct websocket *ws = entry;
(void)key;
(void)klen;
Curl_bufq_free(&ws->recvbuf);
Curl_bufq_free(&ws->sendbuf);
free(ws);
}
CURLcode Curl_ws_accept(struct Curl_easy *data,
const char *mem, size_t nread)
{
struct SingleRequest *k = &data->req;
struct websocket *ws;
struct Curl_cwriter *ws_dec_writer = NULL;
struct Curl_creader *ws_enc_reader = NULL;
CURLcode result;
DEBUGASSERT(data->conn);
ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
size_t chunk_size = WS_CHUNK_SIZE;
ws = calloc(1, sizeof(*ws));
if(!ws)
return CURLE_OUT_OF_MEMORY;
#ifdef DEBUGBUILD
{
const char *p = getenv("CURL_WS_CHUNK_SIZE");
if(p) {
curl_off_t l;
if(!curlx_str_number(&p, &l, 1*1024*1024))
chunk_size = (size_t)l;
}
}
#endif
CURL_TRC_WS(data, "WS, using chunk size %zu", chunk_size);
Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT,
BUFQ_OPT_SOFT_LIMIT);
Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT,
BUFQ_OPT_SOFT_LIMIT);
ws_dec_init(&ws->dec);
ws_enc_init(&ws->enc);
result = Curl_conn_meta_set(data->conn, CURL_META_PROTO_WS_CONN,
ws, ws_conn_dtor);
if(result)
return result;
}
else {
Curl_bufq_reset(&ws->recvbuf);
ws_dec_reset(&ws->dec);
ws_enc_reset(&ws->enc);
}
infof(data, "[WS] Received 101, switch to WebSocket");
result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode,
CURL_CW_CONTENT_DECODE);
if(result)
goto out;
result = Curl_cwriter_add(data, ws_dec_writer);
if(result)
goto out;
ws_dec_writer = NULL;
k->header = FALSE;
if(data->set.connect_only) {
size_t nwritten;
result = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)mem,
nread, &nwritten);
if(result)
goto out;
DEBUGASSERT(nread == nwritten);
k->keepon &= ~KEEP_RECV;
}
else {
if(data->set.method == HTTPREQ_PUT) {
CURL_TRC_WS(data, "UPLOAD set, add ws-encode reader");
result = Curl_creader_set_fread(data, -1);
if(result)
goto out;
if(!data->set.ws_raw_mode) {
result = Curl_creader_create(&ws_enc_reader, data, &ws_cr_encode,
CURL_CR_CONTENT_ENCODE);
if(result)
goto out;
result = Curl_creader_add(data, ws_enc_reader);
if(result)
goto out;
ws_enc_reader = NULL;
}
data->req.eos_read = FALSE;
data->req.upload_done = FALSE;
k->keepon |= KEEP_SEND;
}
if(nread) {
result = Curl_client_write(data, CLIENTWRITE_BODY, mem, nread);
if(result)
goto out;
}
}
k->upgr101 = UPGR101_RECEIVED;
k->header = FALSE;
out:
if(ws_dec_writer)
Curl_cwriter_free(data, ws_dec_writer);
if(ws_enc_reader)
Curl_creader_free(data, ws_enc_reader);
if(result)
CURL_TRC_WS(data, "Curl_ws_accept() failed -> %d", result);
else
CURL_TRC_WS(data, "websocket established, %s mode",
data->set.connect_only ? "connect-only" : "callback");
return result;
}
struct ws_collect {
struct Curl_easy *data;
struct websocket *ws;
unsigned char *buffer;
size_t buflen;
size_t bufidx;
int frame_age;
int frame_flags;
curl_off_t payload_offset;
curl_off_t payload_len;
bool written;
};
static CURLcode ws_client_collect(const unsigned char *buf, size_t buflen,
int frame_age, int frame_flags,
curl_off_t payload_offset,
curl_off_t payload_len,
void *userp,
size_t *pnwritten)
{
struct ws_collect *ctx = userp;
struct Curl_easy *data = ctx->data;
bool auto_pong = !data->set.ws_no_auto_pong;
curl_off_t remain;
CURLcode result = CURLE_OK;
*pnwritten = 0;
remain = ws_payload_remain(payload_len, payload_offset, buflen);
if(remain < 0) {
DEBUGASSERT(0);
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(!ctx->bufidx) {
ctx->frame_age = frame_age;
ctx->frame_flags = frame_flags;
ctx->payload_offset = payload_offset;
ctx->payload_len = payload_len;
}
if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
CURL_TRC_WS(data, "auto PONG to [PING payload=%" FMT_OFF_T
"/%" FMT_OFF_T "]", payload_offset, payload_len);
result = ws_enc_add_cntrl(ctx->data, ctx->ws, buf, buflen, CURLWS_PONG);
if(result)
return result;
*pnwritten = buflen;
}
else {
size_t write_len;
ctx->written = TRUE;
DEBUGASSERT(ctx->buflen >= ctx->bufidx);
write_len = CURLMIN(buflen, ctx->buflen - ctx->bufidx);
if(!write_len) {
if(!buflen)
return CURLE_OK;
return CURLE_AGAIN;
}
memcpy(ctx->buffer + ctx->bufidx, buf, write_len);
ctx->bufidx += write_len;
*pnwritten = write_len;
}
return result;
}
static CURLcode nw_in_recv(void *reader_ctx,
unsigned char *buf, size_t buflen,
size_t *pnread)
{
struct Curl_easy *data = reader_ctx;
return curl_easy_recv(data, buf, buflen, pnread);
}
CURLcode curl_ws_recv(CURL *d, void *buffer,
size_t buflen, size_t *nread,
const struct curl_ws_frame **metap)
{
struct Curl_easy *data = d;
struct connectdata *conn;
struct websocket *ws;
struct ws_collect ctx;
*nread = 0;
*metap = NULL;
if(!GOOD_EASY_HANDLE(data) || (buflen && !buffer))
return CURLE_BAD_FUNCTION_ARGUMENT;
conn = data->conn;
if(!conn) {
if(!data->set.connect_only) {
failf(data, "[WS] CONNECT_ONLY is required");
return CURLE_UNSUPPORTED_PROTOCOL;
}
Curl_getconnectinfo(data, &conn);
if(!conn) {
failf(data, "[WS] connection not found");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
}
ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
failf(data, "[WS] connection is not setup for websocket");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
memset(&ctx, 0, sizeof(ctx));
ctx.data = data;
ctx.ws = ws;
ctx.buffer = buffer;
ctx.buflen = buflen;
while(1) {
CURLcode result;
if(Curl_bufq_is_empty(&ws->recvbuf)) {
size_t n;
result = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &n);
if(result)
return result;
else if(n == 0) {
infof(data, "[WS] connection expectedly closed?");
return CURLE_GOT_NOTHING;
}
CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network",
Curl_bufq_len(&ws->recvbuf));
}
result = ws_dec_pass(&ws->dec, data, &ws->recvbuf,
ws_client_collect, &ctx);
if(result == CURLE_AGAIN) {
if(!ctx.written) {
ws_dec_info(&ws->dec, data, "need more input");
continue;
}
break;
}
else if(result) {
return result;
}
else if(ctx.written) {
break;
}
}
update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset,
ctx.payload_len, ctx.bufidx);
*metap = &ws->recvframe;
*nread = ws->recvframe.len;
CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %"
FMT_OFF_T ", %" FMT_OFF_T " left)",
buflen, *nread, ws->recvframe.offset,
ws->recvframe.bytesleft);
if(!data->set.ws_raw_mode && ws->pending.type) {
CURLcode r2 = ws_enc_add_pending(data, ws);
if(!r2)
(void)ws_flush(data, ws, Curl_is_in_callback(data));
}
return CURLE_OK;
}
static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
bool blocking)
{
if(!Curl_bufq_is_empty(&ws->sendbuf)) {
CURLcode result;
const unsigned char *out;
size_t outlen, n;
#ifdef DEBUGBUILD
bool eagain_next = FALSE;
size_t chunk_egain = 0;
const char *p = getenv("CURL_WS_CHUNK_EAGAIN");
if(p) {
curl_off_t l;
if(!curlx_str_number(&p, &l, 1*1024*1024))
chunk_egain = (size_t)l;
}
#endif
while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) {
#ifdef DEBUGBUILD
if(eagain_next)
return CURLE_AGAIN;
if(chunk_egain && (outlen > chunk_egain)) {
outlen = chunk_egain;
eagain_next = TRUE;
}
#endif
if(blocking) {
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))
result = Curl_senddata(data, out, outlen, &n);
else {
result = Curl_xfer_send(data, out, outlen, FALSE, &n);
if(!result && !n && outlen)
result = CURLE_AGAIN;
}
if(result == CURLE_AGAIN) {
CURL_TRC_WS(data, "flush EAGAIN, %zu bytes remain in buffer",
Curl_bufq_len(&ws->sendbuf));
return result;
}
else if(result) {
failf(data, "[WS] flush, write error %d", result);
return result;
}
else {
CURL_TRC_WS(data, "flushed %zu bytes", n);
Curl_bufq_skip(&ws->sendbuf, n);
}
}
}
return CURLE_OK;
}
static CURLcode ws_send_raw_blocking(struct Curl_easy *data,
struct websocket *ws,
const char *buffer, size_t buflen)
{
CURLcode result = CURLE_OK;
size_t nwritten;
(void)ws;
while(buflen) {
result = Curl_xfer_send(data, buffer, buflen, FALSE, &nwritten);
if(result)
return result;
DEBUGASSERT(nwritten <= buflen);
buffer += nwritten;
buflen -= nwritten;
if(buflen) {
curl_socket_t sock = data->conn->sock[FIRSTSOCKET];
timediff_t left_ms;
int ev;
CURL_TRC_WS(data, "ws_send_raw_blocking() partial, %zu left to send",
buflen);
left_ms = Curl_timeleft(data, NULL, FALSE);
if(left_ms < 0) {
failf(data, "[WS] Timeout waiting for socket becoming writable");
return CURLE_SEND_ERROR;
}
if(sock == CURL_SOCKET_BAD)
return CURLE_SEND_ERROR;
ev = Curl_socket_check(CURL_SOCKET_BAD, CURL_SOCKET_BAD, sock,
left_ms ? left_ms : 500);
if(ev < 0) {
failf(data, "[WS] Error while waiting for socket becoming writable");
return CURLE_SEND_ERROR;
}
}
}
return result;
}
static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer,
size_t buflen, size_t *pnwritten)
{
struct websocket *ws;
CURLcode result;
ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
failf(data, "[WS] Not a websocket transfer");
return CURLE_SEND_ERROR;
}
if(!buflen)
return CURLE_OK;
if(Curl_is_in_callback(data)) {
result = ws_flush(data, ws, TRUE);
if(result)
return result;
result = ws_send_raw_blocking(data, ws, buffer, buflen);
}
else {
result = ws_flush(data, ws, FALSE);
if(result)
return result;
result = Curl_senddata(data, buffer, buflen, pnwritten);
}
CURL_TRC_WS(data, "ws_send_raw(len=%zu) -> %d, %zu",
buflen, result, *pnwritten);
return result;
}
CURLcode curl_ws_send(CURL *d, const void *buffer_arg,
size_t buflen, size_t *sent,
curl_off_t fragsize,
unsigned int flags)
{
struct websocket *ws;
const unsigned char *buffer = buffer_arg;
CURLcode result = CURLE_OK;
struct Curl_easy *data = d;
size_t ndummy;
size_t *pnsent = sent ? sent : &ndummy;
if(!GOOD_EASY_HANDLE(data))
return CURLE_BAD_FUNCTION_ARGUMENT;
CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T
", flags=%x), raw=%d",
buflen, fragsize, flags, data->set.ws_raw_mode);
*pnsent = 0;
if(!buffer && buflen) {
failf(data, "[WS] buffer is NULL when buflen is not");
result = CURLE_BAD_FUNCTION_ARGUMENT;
goto out;
}
if(!data->conn && data->set.connect_only) {
result = Curl_connect_only_attach(data);
if(result)
goto out;
}
if(!data->conn) {
failf(data, "[WS] No associated connection");
result = CURLE_SEND_ERROR;
goto out;
}
ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
failf(data, "[WS] Not a websocket transfer");
result = CURLE_SEND_ERROR;
goto out;
}
if(data->set.ws_raw_mode) {
result = ws_flush(data, ws, FALSE);
if(result)
goto out;
if(!buffer) {
failf(data, "[WS] buffer is NULL in raw mode");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(!sent) {
failf(data, "[WS] sent is NULL in raw mode");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(fragsize || flags) {
failf(data, "[WS] fragsize and flags must be zero in raw mode");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
result = ws_send_raw(data, buffer, buflen, pnsent);
goto out;
}
result = ws_enc_send(data, ws, buffer, buflen, fragsize, flags, pnsent);
out:
CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T
", flags=%x, raw=%d) -> %d, %zu",
buflen, fragsize, flags, data->set.ws_raw_mode, result,
*pnsent);
return result;
}
static CURLcode ws_setup_conn(struct Curl_easy *data,
struct connectdata *conn)
{
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);
}
const struct curl_ws_frame *curl_ws_meta(CURL *d)
{
struct Curl_easy *data = d;
if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) &&
data->conn && !data->set.ws_raw_mode) {
struct websocket *ws;
ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(ws)
return &ws->recvframe;
}
return NULL;
}
CURL_EXTERN CURLcode curl_ws_start_frame(CURL *d,
unsigned int flags,
curl_off_t frame_len)
{
struct websocket *ws;
CURLcode result = CURLE_OK;
struct Curl_easy *data = d;
if(!GOOD_EASY_HANDLE(data))
return CURLE_BAD_FUNCTION_ARGUMENT;
if(data->set.ws_raw_mode) {
failf(data, "cannot curl_ws_start_frame() with CURLWS_RAW_MODE enabled");
return CURLE_FAILED_INIT;
}
CURL_TRC_WS(data, "curl_ws_start_frame(flags=%x, frame_len=%" FMT_OFF_T,
flags, frame_len);
if(!data->conn) {
failf(data, "[WS] No associated connection");
result = CURLE_SEND_ERROR;
goto out;
}
ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
failf(data, "[WS] Not a websocket transfer");
result = CURLE_SEND_ERROR;
goto out;
}
if(data->set.ws_raw_mode) {
failf(data, "[WS] cannot start frame in raw mode");
result = CURLE_SEND_ERROR;
goto out;
}
if(ws->enc.payload_remain) {
failf(data, "[WS] previous frame not finished");
result = CURLE_SEND_ERROR;
goto out;
}
result = ws_enc_write_head(data, ws, &ws->enc, flags, frame_len,
&ws->sendbuf);
if(result)
CURL_TRC_WS(data, "curl_start_frame(), error adding frame head %d",
result);
out:
return result;
}
const struct Curl_handler Curl_handler_ws = {
"WS",
ws_setup_conn,
Curl_http,
Curl_http_done,
ZERO_NULL,
Curl_http_connect,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
Curl_http_do_pollset,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
Curl_http_write_resp,
Curl_http_write_resp_hd,
ZERO_NULL,
ZERO_NULL,
Curl_http_follow,
PORT_HTTP,
CURLPROTO_WS,
CURLPROTO_HTTP,
PROTOPT_CREDSPERREQUEST |
PROTOPT_USERPWDCTRL
};
#ifdef USE_SSL
const struct Curl_handler Curl_handler_wss = {
"WSS",
ws_setup_conn,
Curl_http,
Curl_http_done,
ZERO_NULL,
Curl_http_connect,
NULL,
ZERO_NULL,
NULL,
Curl_http_do_pollset,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
Curl_http_write_resp,
Curl_http_write_resp_hd,
ZERO_NULL,
ZERO_NULL,
Curl_http_follow,
PORT_HTTPS,
CURLPROTO_WSS,
CURLPROTO_HTTP,
PROTOPT_SSL | PROTOPT_CREDSPERREQUEST |
PROTOPT_USERPWDCTRL
};
#endif
#else
CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
size_t *nread,
const struct curl_ws_frame **metap)
{
(void)curl;
(void)buffer;
(void)buflen;
(void)nread;
(void)metap;
return CURLE_NOT_BUILT_IN;
}
CURLcode curl_ws_send(CURL *curl, const void *buffer,
size_t buflen, size_t *sent,
curl_off_t fragsize,
unsigned int flags)
{
(void)curl;
(void)buffer;
(void)buflen;
(void)sent;
(void)fragsize;
(void)flags;
return CURLE_NOT_BUILT_IN;
}
const struct curl_ws_frame *curl_ws_meta(CURL *data)
{
(void)data;
return NULL;
}
CURL_EXTERN CURLcode curl_ws_start_frame(CURL *curl,
unsigned int flags,
curl_off_t frame_len)
{
(void)curl;
(void)flags;
(void)frame_len;
return CURLE_NOT_BUILT_IN;
}
#endif