1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24#include "curl_setup.h"
25#include <curl/curl.h>
26
27#ifdef USE_WEBSOCKETS
28
29#include "urldata.h"
30#include "bufq.h"
31#include "dynbuf.h"
32#include "rand.h"
33#include "curl_base64.h"
34#include "connect.h"
35#include "sendf.h"
36#include "multiif.h"
37#include "ws.h"
38#include "easyif.h"
39#include "transfer.h"
40#include "nonblock.h"
41
42/* The last 3 #include files should be in this order */
43#include "curl_printf.h"
44#include "curl_memory.h"
45#include "memdebug.h"
46
47
48#define WSBIT_FIN 0x80
49#define WSBIT_OPCODE_CONT 0
50#define WSBIT_OPCODE_TEXT (1)
51#define WSBIT_OPCODE_BIN (2)
52#define WSBIT_OPCODE_CLOSE (8)
53#define WSBIT_OPCODE_PING (9)
54#define WSBIT_OPCODE_PONG (0xa)
55#define WSBIT_OPCODE_MASK (0xf)
56
57#define WSBIT_MASK 0x80
58
59/* buffer dimensioning */
60#define WS_CHUNK_SIZE 65535
61#define WS_CHUNK_COUNT 2
62
63struct ws_frame_meta {
64 char proto_opcode;
65 int flags;
66 const char *name;
67};
68
69static struct ws_frame_meta WS_FRAMES[] = {
70 { WSBIT_OPCODE_CONT, CURLWS_CONT, "CONT" },
71 { WSBIT_OPCODE_TEXT, CURLWS_TEXT, "TEXT" },
72 { WSBIT_OPCODE_BIN, CURLWS_BINARY, "BIN" },
73 { WSBIT_OPCODE_CLOSE, CURLWS_CLOSE, "CLOSE" },
74 { WSBIT_OPCODE_PING, CURLWS_PING, "PING" },
75 { WSBIT_OPCODE_PONG, CURLWS_PONG, "PONG" },
76};
77
78static const char *ws_frame_name_of_op(unsigned char proto_opcode)
79{
80 unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK;
81 size_t i;
82 for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) {
83 if(WS_FRAMES[i].proto_opcode == opcode)
84 return WS_FRAMES[i].name;
85 }
86 return "???";
87}
88
89static int ws_frame_op2flags(unsigned char proto_opcode)
90{
91 unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK;
92 size_t i;
93 for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) {
94 if(WS_FRAMES[i].proto_opcode == opcode)
95 return WS_FRAMES[i].flags;
96 }
97 return 0;
98}
99
100static unsigned char ws_frame_flags2op(int flags)
101{
102 size_t i;
103 for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) {
104 if(WS_FRAMES[i].flags & flags)
105 return WS_FRAMES[i].proto_opcode;
106 }
107 return 0;
108}
109
110static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data,
111 const char *msg)
112{
113 switch(dec->head_len) {
114 case 0:
115 break;
116 case 1:
117 infof(data, "WS-DEC: %s [%s%s]", msg,
118 ws_frame_name_of_op(dec->head[0]),
119 (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL");
120 break;
121 default:
122 if(dec->head_len < dec->head_total) {
123 infof(data, "WS-DEC: %s [%s%s](%d/%d)", msg,
124 ws_frame_name_of_op(dec->head[0]),
125 (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL",
126 dec->head_len, dec->head_total);
127 }
128 else {
129 infof(data, "WS-DEC: %s [%s%s payload=%" CURL_FORMAT_CURL_OFF_T
130 "/%" CURL_FORMAT_CURL_OFF_T "]",
131 msg, ws_frame_name_of_op(dec->head[0]),
132 (dec->head[0] & WSBIT_FIN)? "" : " NON-FINAL",
133 dec->payload_offset, dec->payload_len);
134 }
135 break;
136 }
137}
138
139typedef ssize_t ws_write_payload(const unsigned char *buf, size_t buflen,
140 int frame_age, int frame_flags,
141 curl_off_t payload_offset,
142 curl_off_t payload_len,
143 void *userp,
144 CURLcode *err);
145
146
147static void ws_dec_reset(struct ws_decoder *dec)
148{
149 dec->frame_age = 0;
150 dec->frame_flags = 0;
151 dec->payload_offset = 0;
152 dec->payload_len = 0;
153 dec->head_len = dec->head_total = 0;
154 dec->state = WS_DEC_INIT;
155}
156
157static void ws_dec_init(struct ws_decoder *dec)
158{
159 ws_dec_reset(dec);
160}
161
162static CURLcode ws_dec_read_head(struct ws_decoder *dec,
163 struct Curl_easy *data,
164 struct bufq *inraw)
165{
166 const unsigned char *inbuf;
167 size_t inlen;
168
169 while(Curl_bufq_peek(inraw, &inbuf, &inlen)) {
170 if(dec->head_len == 0) {
171 dec->head[0] = *inbuf;
172 Curl_bufq_skip(inraw, 1);
173
174 dec->frame_flags = ws_frame_op2flags(dec->head[0]);
175 if(!dec->frame_flags) {
176 failf(data, "WS: unknown opcode: %x", dec->head[0]);
177 ws_dec_reset(dec);
178 return CURLE_RECV_ERROR;
179 }
180 dec->head_len = 1;
181 /* ws_dec_info(dec, data, "seeing opcode"); */
182 continue;
183 }
184 else if(dec->head_len == 1) {
185 dec->head[1] = *inbuf;
186 Curl_bufq_skip(inraw, 1);
187 dec->head_len = 2;
188
189 if(dec->head[1] & WSBIT_MASK) {
190 /* A client MUST close a connection if it detects a masked frame. */
191 failf(data, "WS: masked input frame");
192 ws_dec_reset(dec);
193 return CURLE_RECV_ERROR;
194 }
195 /* How long is the frame head? */
196 if(dec->head[1] == 126) {
197 dec->head_total = 4;
198 continue;
199 }
200 else if(dec->head[1] == 127) {
201 dec->head_total = 10;
202 continue;
203 }
204 else {
205 dec->head_total = 2;
206 }
207 }
208
209 if(dec->head_len < dec->head_total) {
210 dec->head[dec->head_len] = *inbuf;
211 Curl_bufq_skip(inraw, 1);
212 ++dec->head_len;
213 if(dec->head_len < dec->head_total) {
214 /* ws_dec_info(dec, data, "decoding head"); */
215 continue;
216 }
217 }
218 /* got the complete frame head */
219 DEBUGASSERT(dec->head_len == dec->head_total);
220 switch(dec->head_total) {
221 case 2:
222 dec->payload_len = dec->head[1];
223 break;
224 case 4:
225 dec->payload_len = (dec->head[2] << 8) | dec->head[3];
226 break;
227 case 10:
228 dec->payload_len = ((curl_off_t)dec->head[2] << 56) |
229 (curl_off_t)dec->head[3] << 48 |
230 (curl_off_t)dec->head[4] << 40 |
231 (curl_off_t)dec->head[5] << 32 |
232 (curl_off_t)dec->head[6] << 24 |
233 (curl_off_t)dec->head[7] << 16 |
234 (curl_off_t)dec->head[8] << 8 |
235 dec->head[9];
236 break;
237 default:
238 /* this should never happen */
239 DEBUGASSERT(0);
240 failf(data, "WS: unexpected frame header length");
241 return CURLE_RECV_ERROR;
242 }
243
244 dec->frame_age = 0;
245 dec->payload_offset = 0;
246 ws_dec_info(dec, data, "decoded");
247 return CURLE_OK;
248 }
249 return CURLE_AGAIN;
250}
251
252static CURLcode ws_dec_pass_payload(struct ws_decoder *dec,
253 struct Curl_easy *data,
254 struct bufq *inraw,
255 ws_write_payload *write_payload,
256 void *write_ctx)
257{
258 const unsigned char *inbuf;
259 size_t inlen;
260 ssize_t nwritten;
261 CURLcode result;
262 curl_off_t remain = dec->payload_len - dec->payload_offset;
263
264 (void)data;
265 while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen)) {
266 if((curl_off_t)inlen > remain)
267 inlen = (size_t)remain;
268 nwritten = write_payload(inbuf, inlen, dec->frame_age, dec->frame_flags,
269 dec->payload_offset, dec->payload_len,
270 write_ctx, &result);
271 if(nwritten < 0)
272 return result;
273 Curl_bufq_skip(inraw, (size_t)nwritten);
274 dec->payload_offset += (curl_off_t)nwritten;
275 remain = dec->payload_len - dec->payload_offset;
276 /* infof(data, "WS-DEC: passed %zd bytes payload, %"
277 CURL_FORMAT_CURL_OFF_T " remain",
278 nwritten, remain); */
279 }
280
281 return remain? CURLE_AGAIN : CURLE_OK;
282}
283
284static CURLcode ws_dec_pass(struct ws_decoder *dec,
285 struct Curl_easy *data,
286 struct bufq *inraw,
287 ws_write_payload *write_payload,
288 void *write_ctx)
289{
290 CURLcode result;
291
292 if(Curl_bufq_is_empty(inraw))
293 return CURLE_AGAIN;
294
295 switch(dec->state) {
296 case WS_DEC_INIT:
297 ws_dec_reset(dec);
298 dec->state = WS_DEC_HEAD;
299 /* FALLTHROUGH */
300 case WS_DEC_HEAD:
301 result = ws_dec_read_head(dec, data, inraw);
302 if(result) {
303 if(result != CURLE_AGAIN) {
304 infof(data, "WS: decode error %d", (int)result);
305 break; /* real error */
306 }
307 /* incomplete ws frame head */
308 DEBUGASSERT(Curl_bufq_is_empty(inraw));
309 break;
310 }
311 /* head parsing done */
312 dec->state = WS_DEC_PAYLOAD;
313 if(dec->payload_len == 0) {
314 ssize_t nwritten;
315 const unsigned char tmp = '\0';
316 /* special case of a 0 length frame, need to write once */
317 nwritten = write_payload(&tmp, 0, dec->frame_age, dec->frame_flags,
318 0, 0, write_ctx, &result);
319 if(nwritten < 0)
320 return result;
321 dec->state = WS_DEC_INIT;
322 break;
323 }
324 /* FALLTHROUGH */
325 case WS_DEC_PAYLOAD:
326 result = ws_dec_pass_payload(dec, data, inraw, write_payload, write_ctx);
327 ws_dec_info(dec, data, "passing");
328 if(result)
329 return result;
330 /* paylod parsing done */
331 dec->state = WS_DEC_INIT;
332 break;
333 default:
334 /* we covered all enums above, but some code analyzers are whimps */
335 result = CURLE_FAILED_INIT;
336 }
337 return result;
338}
339
340static void update_meta(struct websocket *ws,
341 int frame_age, int frame_flags,
342 curl_off_t payload_offset,
343 curl_off_t payload_len,
344 size_t cur_len)
345{
346 ws->frame.age = frame_age;
347 ws->frame.flags = frame_flags;
348 ws->frame.offset = payload_offset;
349 ws->frame.len = cur_len;
350 ws->frame.bytesleft = (payload_len - payload_offset - cur_len);
351}
352
353static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data,
354 const char *msg)
355{
356 infof(data, "WS-ENC: %s [%s%s%s payload=%" CURL_FORMAT_CURL_OFF_T
357 "/%" CURL_FORMAT_CURL_OFF_T "]",
358 msg, ws_frame_name_of_op(enc->firstbyte),
359 (enc->firstbyte & WSBIT_OPCODE_MASK) == WSBIT_OPCODE_CONT ?
360 " CONT" : "",
361 (enc->firstbyte & WSBIT_FIN)? "" : " NON-FIN",
362 enc->payload_len - enc->payload_remain, enc->payload_len);
363}
364
365static void ws_enc_reset(struct ws_encoder *enc)
366{
367 enc->payload_remain = 0;
368 enc->xori = 0;
369 enc->contfragment = FALSE;
370}
371
372static void ws_enc_init(struct ws_encoder *enc)
373{
374 ws_enc_reset(enc);
375}
376
377/***
378 RFC 6455 Section 5.2
379
380 0 1 2 3
381 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
382 +-+-+-+-+-------+-+-------------+-------------------------------+
383 |F|R|R|R| opcode|M| Payload len | Extended payload length |
384 |I|S|S|S| (4) |A| (7) | (16/64) |
385 |N|V|V|V| |S| | (if payload len==126/127) |
386 | |1|2|3| |K| | |
387 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
388 | Extended payload length continued, if payload len == 127 |
389 + - - - - - - - - - - - - - - - +-------------------------------+
390 | |Masking-key, if MASK set to 1 |
391 +-------------------------------+-------------------------------+
392 | Masking-key (continued) | Payload Data |
393 +-------------------------------- - - - - - - - - - - - - - - - +
394 : Payload Data continued ... :
395 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
396 | Payload Data continued ... |
397 +---------------------------------------------------------------+
398*/
399
400static ssize_t ws_enc_write_head(struct Curl_easy *data,
401 struct ws_encoder *enc,
402 unsigned int flags,
403 curl_off_t payload_len,
404 struct bufq *out,
405 CURLcode *err)
406{
407 unsigned char firstbyte = 0;
408 unsigned char opcode;
409 unsigned char head[14];
410 size_t hlen;
411 ssize_t n;
412
413 if(enc->payload_remain > 0) {
414 /* trying to write a new frame before the previous one is finished */
415 failf(data, "WS: starting new frame with %zd bytes from last one"
416 "remaining to be sent", (ssize_t)enc->payload_remain);
417 *err = CURLE_SEND_ERROR;
418 return -1;
419 }
420
421 opcode = ws_frame_flags2op(flags);
422 if(!opcode) {
423 failf(data, "WS: provided flags not recognized '%x'", flags);
424 *err = CURLE_SEND_ERROR;
425 return -1;
426 }
427
428 if(!(flags & CURLWS_CONT)) {
429 if(!enc->contfragment)
430 /* not marked as continuing, this is the final fragment */
431 firstbyte |= WSBIT_FIN | opcode;
432 else
433 /* marked as continuing, this is the final fragment; set CONT
434 opcode and FIN bit */
435 firstbyte |= WSBIT_FIN | WSBIT_OPCODE_CONT;
436
437 enc->contfragment = FALSE;
438 }
439 else if(enc->contfragment) {
440 /* the previous fragment was not a final one and this isn't either, keep a
441 CONT opcode and no FIN bit */
442 firstbyte |= WSBIT_OPCODE_CONT;
443 }
444 else {
445 firstbyte = opcode;
446 enc->contfragment = TRUE;
447 }
448
449 head[0] = enc->firstbyte = firstbyte;
450 if(payload_len > 65535) {
451 head[1] = 127 | WSBIT_MASK;
452 head[2] = (unsigned char)((payload_len >> 56) & 0xff);
453 head[3] = (unsigned char)((payload_len >> 48) & 0xff);
454 head[4] = (unsigned char)((payload_len >> 40) & 0xff);
455 head[5] = (unsigned char)((payload_len >> 32) & 0xff);
456 head[6] = (unsigned char)((payload_len >> 24) & 0xff);
457 head[7] = (unsigned char)((payload_len >> 16) & 0xff);
458 head[8] = (unsigned char)((payload_len >> 8) & 0xff);
459 head[9] = (unsigned char)(payload_len & 0xff);
460 hlen = 10;
461 }
462 else if(payload_len >= 126) {
463 head[1] = 126 | WSBIT_MASK;
464 head[2] = (unsigned char)((payload_len >> 8) & 0xff);
465 head[3] = (unsigned char)(payload_len & 0xff);
466 hlen = 4;
467 }
468 else {
469 head[1] = (unsigned char)payload_len | WSBIT_MASK;
470 hlen = 2;
471 }
472
473 enc->payload_remain = enc->payload_len = payload_len;
474 ws_enc_info(enc, data, "sending");
475
476 /* add 4 bytes mask */
477 memcpy(&head[hlen], &enc->mask, 4);
478 hlen += 4;
479 /* reset for payload to come */
480 enc->xori = 0;
481
482 n = Curl_bufq_write(out, head, hlen, err);
483 if(n < 0)
484 return -1;
485 if((size_t)n != hlen) {
486 /* We use a bufq with SOFT_LIMIT, writing should always succeed */
487 DEBUGASSERT(0);
488 *err = CURLE_SEND_ERROR;
489 return -1;
490 }
491 return n;
492}
493
494static ssize_t ws_enc_write_payload(struct ws_encoder *enc,
495 struct Curl_easy *data,
496 const unsigned char *buf, size_t buflen,
497 struct bufq *out, CURLcode *err)
498{
499 ssize_t n;
500 size_t i, len;
501
502 if(Curl_bufq_is_full(out)) {
503 *err = CURLE_AGAIN;
504 return -1;
505 }
506
507 /* not the most performant way to do this */
508 len = buflen;
509 if((curl_off_t)len > enc->payload_remain)
510 len = (size_t)enc->payload_remain;
511
512 for(i = 0; i < len; ++i) {
513 unsigned char c = buf[i] ^ enc->mask[enc->xori];
514 n = Curl_bufq_write(out, &c, 1, err);
515 if(n < 0) {
516 if((*err != CURLE_AGAIN) || !i)
517 return -1;
518 break;
519 }
520 enc->xori++;
521 enc->xori &= 3;
522 }
523 enc->payload_remain -= (curl_off_t)i;
524 ws_enc_info(enc, data, "buffered");
525 return (ssize_t)i;
526}
527
528
529struct wsfield {
530 const char *name;
531 const char *val;
532};
533
534CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req)
535{
536 unsigned int i;
537 CURLcode result = CURLE_OK;
538 unsigned char rand[16];
539 char *randstr;
540 size_t randlen;
541 char keyval[40];
542 struct SingleRequest *k = &data->req;
543 struct wsfield heads[]= {
544 {
545 /* The request MUST contain an |Upgrade| header field whose value
546 MUST include the "websocket" keyword. */
547 "Upgrade:", "websocket"
548 },
549 {
550 /* The request MUST contain a |Connection| header field whose value
551 MUST include the "Upgrade" token. */
552 "Connection:", "Upgrade",
553 },
554 {
555 /* The request MUST include a header field with the name
556 |Sec-WebSocket-Version|. The value of this header field MUST be
557 13. */
558 "Sec-WebSocket-Version:", "13",
559 },
560 {
561 /* The request MUST include a header field with the name
562 |Sec-WebSocket-Key|. The value of this header field MUST be a nonce
563 consisting of a randomly selected 16-byte value that has been
564 base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be
565 selected randomly for each connection. */
566 "Sec-WebSocket-Key:", NULL,
567 }
568 };
569 heads[3].val = &keyval[0];
570
571 /* 16 bytes random */
572 result = Curl_rand(data, (unsigned char *)rand, sizeof(rand));
573 if(result)
574 return result;
575 result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen);
576 if(result)
577 return result;
578 DEBUGASSERT(randlen < sizeof(keyval));
579 if(randlen >= sizeof(keyval))
580 return CURLE_FAILED_INIT;
581 strcpy(keyval, randstr);
582 free(randstr);
583 for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) {
584 if(!Curl_checkheaders(data, STRCONST(heads[i].name))) {
585#ifdef USE_HYPER
586 char field[128];
587 msnprintf(field, sizeof(field), "%s %s", heads[i].name,
588 heads[i].val);
589 result = Curl_hyper_header(data, req, field);
590#else
591 (void)data;
592 result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name,
593 heads[i].val);
594#endif
595 }
596 }
597 k->upgr101 = UPGR101_WS;
598 return result;
599}
600
601/*
602 * 'nread' is number of bytes of websocket data already in the buffer at
603 * 'mem'.
604 */
605CURLcode Curl_ws_accept(struct Curl_easy *data,
606 const char *mem, size_t nread)
607{
608 struct SingleRequest *k = &data->req;
609 struct websocket *ws;
610 CURLcode result;
611
612 DEBUGASSERT(data->conn);
613 ws = data->conn->proto.ws;
614 if(!ws) {
615 ws = calloc(1, sizeof(*ws));
616 if(!ws)
617 return CURLE_OUT_OF_MEMORY;
618 data->conn->proto.ws = ws;
619 Curl_bufq_init(&ws->recvbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT);
620 Curl_bufq_init2(&ws->sendbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT,
621 BUFQ_OPT_SOFT_LIMIT);
622 ws_dec_init(&ws->dec);
623 ws_enc_init(&ws->enc);
624 }
625 else {
626 Curl_bufq_reset(&ws->recvbuf);
627 ws_dec_reset(&ws->dec);
628 ws_enc_reset(&ws->enc);
629 }
630 /* Verify the Sec-WebSocket-Accept response.
631
632 The sent value is the base64 encoded version of a SHA-1 hash done on the
633 |Sec-WebSocket-Key| header field concatenated with
634 the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".
635 */
636
637 /* If the response includes a |Sec-WebSocket-Extensions| header field and
638 this header field indicates the use of an extension that was not present
639 in the client's handshake (the server has indicated an extension not
640 requested by the client), the client MUST Fail the WebSocket Connection.
641 */
642
643 /* If the response includes a |Sec-WebSocket-Protocol| header field
644 and this header field indicates the use of a subprotocol that was
645 not present in the client's handshake (the server has indicated a
646 subprotocol not requested by the client), the client MUST Fail
647 the WebSocket Connection. */
648
649 /* 4 bytes random */
650
651 result = Curl_rand(data, (unsigned char *)&ws->enc.mask,
652 sizeof(ws->enc.mask));
653 if(result)
654 return result;
655 infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
656 ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]);
657
658 if(data->set.connect_only) {
659 ssize_t nwritten;
660 /* In CONNECT_ONLY setup, the payloads from `mem` need to be received
661 * when using `curl_ws_recv` later on after this transfer is already
662 * marked as DONE. */
663 nwritten = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)mem,
664 nread, &result);
665 if(nwritten < 0)
666 return result;
667 infof(data, "%zu bytes websocket payload", nread);
668 }
669 k->upgr101 = UPGR101_RECEIVED;
670
671 return result;
672}
673
674static ssize_t ws_client_write(const unsigned char *buf, size_t buflen,
675 int frame_age, int frame_flags,
676 curl_off_t payload_offset,
677 curl_off_t payload_len,
678 void *userp,
679 CURLcode *err)
680{
681 struct Curl_easy *data = userp;
682 struct websocket *ws;
683 size_t wrote;
684 curl_off_t remain = (payload_len - (payload_offset + buflen));
685
686 (void)frame_age;
687 if(!data->conn || !data->conn->proto.ws) {
688 *err = CURLE_FAILED_INIT;
689 return -1;
690 }
691 ws = data->conn->proto.ws;
692
693 if((frame_flags & CURLWS_PING) && !remain) {
694 /* auto-respond to PINGs, only works for single-frame payloads atm */
695 size_t bytes;
696 infof(data, "WS: auto-respond to PING with a PONG");
697 /* send back the exact same content as a PONG */
698 *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG);
699 if(*err)
700 return -1;
701 }
702 else if(buflen || !remain) {
703 /* deliver the decoded frame to the user callback. The application
704 * may invoke curl_ws_meta() to access frame information. */
705 update_meta(ws, frame_age, frame_flags, payload_offset,
706 payload_len, buflen);
707 Curl_set_in_callback(data, true);
708 wrote = data->set.fwrite_func((char *)buf, 1,
709 buflen, data->set.out);
710 Curl_set_in_callback(data, false);
711 if(wrote != buflen) {
712 *err = CURLE_RECV_ERROR;
713 return -1;
714 }
715 }
716 *err = CURLE_OK;
717 return (ssize_t)buflen;
718}
719
720/* Curl_ws_writecb() is the write callback for websocket traffic. The
721 websocket data is provided to this raw, in chunks. This function should
722 handle/decode the data and call the "real" underlying callback accordingly.
723*/
724size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */,
725 size_t nitems, void *userp)
726{
727 struct Curl_easy *data = userp;
728
729 if(data->set.ws_raw_mode)
730 return data->set.fwrite_func(buffer, size, nitems, data->set.out);
731 else if(nitems) {
732 struct websocket *ws;
733 CURLcode result;
734
735 if(!data->conn || !data->conn->proto.ws) {
736 failf(data, "WS: not a websocket transfer");
737 return nitems - 1;
738 }
739 ws = data->conn->proto.ws;
740
741 if(buffer) {
742 ssize_t nwritten;
743
744 nwritten = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)buffer,
745 nitems, &result);
746 if(nwritten < 0) {
747 infof(data, "WS: error adding data to buffer %d", (int)result);
748 return nitems - 1;
749 }
750 buffer = NULL;
751 }
752
753 while(!Curl_bufq_is_empty(&ws->recvbuf)) {
754
755 result = ws_dec_pass(&ws->dec, data, &ws->recvbuf,
756 ws_client_write, data);
757 if(result == CURLE_AGAIN)
758 /* insufficient amount of data, keep it for later.
759 * we pretend to have written all since we have a copy */
760 return nitems;
761 else if(result) {
762 infof(data, "WS: decode error %d", (int)result);
763 return nitems - 1;
764 }
765 }
766 }
767 return nitems;
768}
769
770struct ws_collect {
771 struct Curl_easy *data;
772 void *buffer;
773 size_t buflen;
774 size_t bufidx;
775 int frame_age;
776 int frame_flags;
777 curl_off_t payload_offset;
778 curl_off_t payload_len;
779 bool written;
780};
781
782static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen,
783 int frame_age, int frame_flags,
784 curl_off_t payload_offset,
785 curl_off_t payload_len,
786 void *userp,
787 CURLcode *err)
788{
789 struct ws_collect *ctx = userp;
790 size_t nwritten;
791 curl_off_t remain = (payload_len - (payload_offset + buflen));
792
793 if(!ctx->bufidx) {
794 /* first write */
795 ctx->frame_age = frame_age;
796 ctx->frame_flags = frame_flags;
797 ctx->payload_offset = payload_offset;
798 ctx->payload_len = payload_len;
799 }
800
801 if((frame_flags & CURLWS_PING) && !remain) {
802 /* auto-respond to PINGs, only works for single-frame payloads atm */
803 size_t bytes;
804 infof(ctx->data, "WS: auto-respond to PING with a PONG");
805 /* send back the exact same content as a PONG */
806 *err = curl_ws_send(ctx->data, buf, buflen, &bytes, 0, CURLWS_PONG);
807 if(*err)
808 return -1;
809 nwritten = bytes;
810 }
811 else {
812 ctx->written = TRUE;
813 DEBUGASSERT(ctx->buflen >= ctx->bufidx);
814 nwritten = CURLMIN(buflen, ctx->buflen - ctx->bufidx);
815 if(!nwritten) {
816 if(!buflen) { /* 0 length write, we accept that */
817 *err = CURLE_OK;
818 return 0;
819 }
820 *err = CURLE_AGAIN; /* no more space */
821 return -1;
822 }
823 *err = CURLE_OK;
824 memcpy(ctx->buffer, buf, nwritten);
825 ctx->bufidx += nwritten;
826 }
827 return nwritten;
828}
829
830static ssize_t nw_in_recv(void *reader_ctx,
831 unsigned char *buf, size_t buflen,
832 CURLcode *err)
833{
834 struct Curl_easy *data = reader_ctx;
835 size_t nread;
836
837 *err = curl_easy_recv(data, buf, buflen, &nread);
838 if(*err)
839 return -1;
840 return (ssize_t)nread;
841}
842
843CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer,
844 size_t buflen, size_t *nread,
845 const struct curl_ws_frame **metap)
846{
847 struct connectdata *conn = data->conn;
848 struct websocket *ws;
849 bool done = FALSE; /* not filled passed buffer yet */
850 struct ws_collect ctx;
851 CURLcode result;
852
853 if(!conn) {
854 /* Unhappy hack with lifetimes of transfers and connection */
855 if(!data->set.connect_only) {
856 failf(data, "CONNECT_ONLY is required");
857 return CURLE_UNSUPPORTED_PROTOCOL;
858 }
859
860 Curl_getconnectinfo(data, &conn);
861 if(!conn) {
862 failf(data, "connection not found");
863 return CURLE_BAD_FUNCTION_ARGUMENT;
864 }
865 }
866 ws = conn->proto.ws;
867 if(!ws) {
868 failf(data, "connection is not setup for websocket");
869 return CURLE_BAD_FUNCTION_ARGUMENT;
870 }
871
872 *nread = 0;
873 *metap = NULL;
874 /* get a download buffer */
875 result = Curl_preconnect(data);
876 if(result)
877 return result;
878
879 memset(&ctx, 0, sizeof(ctx));
880 ctx.data = data;
881 ctx.buffer = buffer;
882 ctx.buflen = buflen;
883
884 while(!done) {
885 /* receive more when our buffer is empty */
886 if(Curl_bufq_is_empty(&ws->recvbuf)) {
887 ssize_t n = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &result);
888 if(n < 0) {
889 return result;
890 }
891 else if(n == 0) {
892 /* connection closed */
893 infof(data, "connection expectedly closed?");
894 return CURLE_GOT_NOTHING;
895 }
896 DEBUGF(infof(data, "curl_ws_recv, added %zu bytes from network",
897 Curl_bufq_len(&ws->recvbuf)));
898 }
899
900 result = ws_dec_pass(&ws->dec, data, &ws->recvbuf,
901 ws_client_collect, &ctx);
902 if(result == CURLE_AGAIN) {
903 if(!ctx.written) {
904 ws_dec_info(&ws->dec, data, "need more input");
905 continue; /* nothing written, try more input */
906 }
907 done = TRUE;
908 break;
909 }
910 else if(result) {
911 return result;
912 }
913 else if(ctx.written) {
914 /* The decoded frame is passed back to our caller.
915 * There are frames like PING were we auto-respond to and
916 * that we do not return. For these `ctx.written` is not set. */
917 done = TRUE;
918 break;
919 }
920 }
921
922 /* update frame information to be passed back */
923 update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset,
924 ctx.payload_len, ctx.bufidx);
925 *metap = &ws->frame;
926 *nread = ws->frame.len;
927 /* infof(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %"
928 CURL_FORMAT_CURL_OFF_T ", %" CURL_FORMAT_CURL_OFF_T " left)",
929 buflen, *nread, ws->frame.offset, ws->frame.bytesleft); */
930 return CURLE_OK;
931}
932
933static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
934 bool complete)
935{
936 if(!Curl_bufq_is_empty(&ws->sendbuf)) {
937 CURLcode result;
938 const unsigned char *out;
939 size_t outlen;
940 ssize_t n;
941
942 while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) {
943 if(data->set.connect_only)
944 result = Curl_senddata(data, out, outlen, &n);
945 else
946 result = Curl_write(data, data->conn->writesockfd, out, outlen, &n);
947 if(result) {
948 if(result == CURLE_AGAIN) {
949 if(!complete) {
950 infof(data, "WS: flush EAGAIN, %zu bytes remain in buffer",
951 Curl_bufq_len(&ws->sendbuf));
952 return result;
953 }
954 /* TODO: the current design does not allow for buffered writes.
955 * We need to flush the buffer now. There is no ws_flush() later */
956 n = 0;
957 continue;
958 }
959 else if(result) {
960 failf(data, "WS: flush, write error %d", result);
961 return result;
962 }
963 }
964 else {
965 infof(data, "WS: flushed %zu bytes", (size_t)n);
966 Curl_bufq_skip(&ws->sendbuf, (size_t)n);
967 }
968 }
969 }
970 return CURLE_OK;
971}
972
973CURL_EXTERN CURLcode curl_ws_send(CURL *data, const void *buffer,
974 size_t buflen, size_t *sent,
975 curl_off_t fragsize,
976 unsigned int flags)
977{
978 struct websocket *ws;
979 ssize_t nwritten, n;
980 size_t space;
981 CURLcode result;
982
983 *sent = 0;
984 if(!data->conn && data->set.connect_only) {
985 result = Curl_connect_only_attach(data);
986 if(result)
987 return result;
988 }
989 if(!data->conn) {
990 failf(data, "No associated connection");
991 return CURLE_SEND_ERROR;
992 }
993 if(!data->conn->proto.ws) {
994 failf(data, "Not a websocket transfer");
995 return CURLE_SEND_ERROR;
996 }
997 ws = data->conn->proto.ws;
998
999 if(data->set.ws_raw_mode) {
1000 if(fragsize || flags)
1001 return CURLE_BAD_FUNCTION_ARGUMENT;
1002 if(!buflen)
1003 /* nothing to do */
1004 return CURLE_OK;
1005 /* raw mode sends exactly what was requested, and this is from within
1006 the write callback */
1007 if(Curl_is_in_callback(data)) {
1008 result = Curl_write(data, data->conn->writesockfd, buffer, buflen,
1009 &nwritten);
1010 }
1011 else
1012 result = Curl_senddata(data, buffer, buflen, &nwritten);
1013
1014 infof(data, "WS: wanted to send %zu bytes, sent %zu bytes",
1015 buflen, nwritten);
1016 *sent = (nwritten >= 0)? (size_t)nwritten : 0;
1017 return result;
1018 }
1019
1020 /* Not RAW mode, buf we do the frame encoding */
1021 result = ws_flush(data, ws, FALSE);
1022 if(result)
1023 return result;
1024
1025 /* TODO: the current design does not allow partial writes, afaict.
1026 * It is not clear who the application is supposed to react. */
1027 space = Curl_bufq_space(&ws->sendbuf);
1028 DEBUGF(infof(data, "curl_ws_send(len=%zu), sendbuf len=%zu space %zu",
1029 buflen, Curl_bufq_len(&ws->sendbuf), space));
1030 if(space < 14)
1031 return CURLE_AGAIN;
1032
1033 if(flags & CURLWS_OFFSET) {
1034 if(fragsize) {
1035 /* a frame series 'fragsize' bytes big, this is the first */
1036 n = ws_enc_write_head(data, &ws->enc, flags, fragsize,
1037 &ws->sendbuf, &result);
1038 if(n < 0)
1039 return result;
1040 }
1041 else {
1042 if((curl_off_t)buflen > ws->enc.payload_remain) {
1043 infof(data, "WS: unaligned frame size (sending %zu instead of %"
1044 CURL_FORMAT_CURL_OFF_T ")",
1045 buflen, ws->enc.payload_remain);
1046 }
1047 }
1048 }
1049 else if(!ws->enc.payload_remain) {
1050 n = ws_enc_write_head(data, &ws->enc, flags, (curl_off_t)buflen,
1051 &ws->sendbuf, &result);
1052 if(n < 0)
1053 return result;
1054 }
1055
1056 n = ws_enc_write_payload(&ws->enc, data,
1057 buffer, buflen, &ws->sendbuf, &result);
1058 if(n < 0)
1059 return result;
1060
1061 *sent = (size_t)n;
1062 return ws_flush(data, ws, TRUE);
1063}
1064
1065static void ws_free(struct connectdata *conn)
1066{
1067 if(conn && conn->proto.ws) {
1068 Curl_bufq_free(&conn->proto.ws->recvbuf);
1069 Curl_bufq_free(&conn->proto.ws->sendbuf);
1070 Curl_safefree(conn->proto.ws);
1071 }
1072}
1073
1074void Curl_ws_done(struct Curl_easy *data)
1075{
1076 (void)data;
1077}
1078
1079CURLcode Curl_ws_disconnect(struct Curl_easy *data,
1080 struct connectdata *conn,
1081 bool dead_connection)
1082{
1083 (void)data;
1084 (void)dead_connection;
1085 ws_free(conn);
1086 return CURLE_OK;
1087}
1088
1089CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
1090{
1091 /* we only return something for websocket, called from within the callback
1092 when not using raw mode */
1093 if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->conn &&
1094 data->conn->proto.ws && !data->set.ws_raw_mode)
1095 return &data->conn->proto.ws->frame;
1096 return NULL;
1097}
1098
1099#else
1100
1101CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
1102 size_t *nread,
1103 const struct curl_ws_frame **metap)
1104{
1105 (void)curl;
1106 (void)buffer;
1107 (void)buflen;
1108 (void)nread;
1109 (void)metap;
1110 return CURLE_NOT_BUILT_IN;
1111}
1112
1113CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
1114 size_t buflen, size_t *sent,
1115 curl_off_t fragsize,
1116 unsigned int flags)
1117{
1118 (void)curl;
1119 (void)buffer;
1120 (void)buflen;
1121 (void)sent;
1122 (void)fragsize;
1123 (void)flags;
1124 return CURLE_NOT_BUILT_IN;
1125}
1126
1127CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
1128{
1129 (void)data;
1130 return NULL;
1131}
1132#endif /* USE_WEBSOCKETS */
1133