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 #if defined(USE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP)
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
63 struct ws_frame_meta {
64 char proto_opcode;
65 int flags;
66 const char *name;
67 };
68
69 static 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
ws_frame_name_of_op(unsigned char proto_opcode)78 static 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
ws_frame_op2flags(unsigned char proto_opcode)89 static 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
ws_frame_flags2op(int flags)100 static 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
ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data, const char *msg)110 static 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
139 typedef 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
ws_dec_reset(struct ws_decoder *dec)147 static 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
ws_dec_init(struct ws_decoder *dec)157 static void ws_dec_init(struct ws_decoder *dec)
158 {
159 ws_dec_reset(dec);
160 }
161
ws_dec_read_head(struct ws_decoder *dec, struct Curl_easy *data, struct bufq *inraw)162 static 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 if(dec->head[2] > 127) {
229 failf(data, "WS: frame length longer than 64 signed not supported");
230 return CURLE_RECV_ERROR;
231 }
232 dec->payload_len = ((curl_off_t)dec->head[2] << 56) |
233 (curl_off_t)dec->head[3] << 48 |
234 (curl_off_t)dec->head[4] << 40 |
235 (curl_off_t)dec->head[5] << 32 |
236 (curl_off_t)dec->head[6] << 24 |
237 (curl_off_t)dec->head[7] << 16 |
238 (curl_off_t)dec->head[8] << 8 |
239 dec->head[9];
240 break;
241 default:
242 /* this should never happen */
243 DEBUGASSERT(0);
244 failf(data, "WS: unexpected frame header length");
245 return CURLE_RECV_ERROR;
246 }
247
248 dec->frame_age = 0;
249 dec->payload_offset = 0;
250 ws_dec_info(dec, data, "decoded");
251 return CURLE_OK;
252 }
253 return CURLE_AGAIN;
254 }
255
ws_dec_pass_payload(struct ws_decoder *dec, struct Curl_easy *data, struct bufq *inraw, ws_write_payload *write_payload, void *write_ctx)256 static CURLcode ws_dec_pass_payload(struct ws_decoder *dec,
257 struct Curl_easy *data,
258 struct bufq *inraw,
259 ws_write_payload *write_payload,
260 void *write_ctx)
261 {
262 const unsigned char *inbuf;
263 size_t inlen;
264 ssize_t nwritten;
265 CURLcode result;
266 curl_off_t remain = dec->payload_len - dec->payload_offset;
267
268 (void)data;
269 while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen)) {
270 if((curl_off_t)inlen > remain)
271 inlen = (size_t)remain;
272 nwritten = write_payload(inbuf, inlen, dec->frame_age, dec->frame_flags,
273 dec->payload_offset, dec->payload_len,
274 write_ctx, &result);
275 if(nwritten < 0)
276 return result;
277 Curl_bufq_skip(inraw, (size_t)nwritten);
278 dec->payload_offset += (curl_off_t)nwritten;
279 remain = dec->payload_len - dec->payload_offset;
280 /* infof(data, "WS-DEC: passed %zd bytes payload, %"
281 CURL_FORMAT_CURL_OFF_T " remain",
282 nwritten, remain); */
283 }
284
285 return remain? CURLE_AGAIN : CURLE_OK;
286 }
287
ws_dec_pass(struct ws_decoder *dec, struct Curl_easy *data, struct bufq *inraw, ws_write_payload *write_payload, void *write_ctx)288 static CURLcode ws_dec_pass(struct ws_decoder *dec,
289 struct Curl_easy *data,
290 struct bufq *inraw,
291 ws_write_payload *write_payload,
292 void *write_ctx)
293 {
294 CURLcode result;
295
296 if(Curl_bufq_is_empty(inraw))
297 return CURLE_AGAIN;
298
299 switch(dec->state) {
300 case WS_DEC_INIT:
301 ws_dec_reset(dec);
302 dec->state = WS_DEC_HEAD;
303 FALLTHROUGH();
304 case WS_DEC_HEAD:
305 result = ws_dec_read_head(dec, data, inraw);
306 if(result) {
307 if(result != CURLE_AGAIN) {
308 infof(data, "WS: decode error %d", (int)result);
309 break; /* real error */
310 }
311 /* incomplete ws frame head */
312 DEBUGASSERT(Curl_bufq_is_empty(inraw));
313 break;
314 }
315 /* head parsing done */
316 dec->state = WS_DEC_PAYLOAD;
317 if(dec->payload_len == 0) {
318 ssize_t nwritten;
319 const unsigned char tmp = '\0';
320 /* special case of a 0 length frame, need to write once */
321 nwritten = write_payload(&tmp, 0, dec->frame_age, dec->frame_flags,
322 0, 0, write_ctx, &result);
323 if(nwritten < 0)
324 return result;
325 dec->state = WS_DEC_INIT;
326 break;
327 }
328 FALLTHROUGH();
329 case WS_DEC_PAYLOAD:
330 result = ws_dec_pass_payload(dec, data, inraw, write_payload, write_ctx);
331 ws_dec_info(dec, data, "passing");
332 if(result)
333 return result;
334 /* paylod parsing done */
335 dec->state = WS_DEC_INIT;
336 break;
337 default:
338 /* we covered all enums above, but some code analyzers are whimps */
339 result = CURLE_FAILED_INIT;
340 }
341 return result;
342 }
343
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)344 static void update_meta(struct websocket *ws,
345 int frame_age, int frame_flags,
346 curl_off_t payload_offset,
347 curl_off_t payload_len,
348 size_t cur_len)
349 {
350 ws->frame.age = frame_age;
351 ws->frame.flags = frame_flags;
352 ws->frame.offset = payload_offset;
353 ws->frame.len = cur_len;
354 ws->frame.bytesleft = (payload_len - payload_offset - cur_len);
355 }
356
357 /* WebSockets decoding client writer */
358 struct ws_cw_ctx {
359 struct Curl_cwriter super;
360 struct bufq buf;
361 };
362
ws_cw_init(struct Curl_easy *data, struct Curl_cwriter *writer)363 static CURLcode ws_cw_init(struct Curl_easy *data,
364 struct Curl_cwriter *writer)
365 {
366 struct ws_cw_ctx *ctx = (struct ws_cw_ctx *)writer;
367 (void)data;
368 Curl_bufq_init2(&ctx->buf, WS_CHUNK_SIZE, 1, BUFQ_OPT_SOFT_LIMIT);
369 return CURLE_OK;
370 }
371
ws_cw_close(struct Curl_easy *data, struct Curl_cwriter *writer)372 static void ws_cw_close(struct Curl_easy *data, struct Curl_cwriter *writer)
373 {
374 struct ws_cw_ctx *ctx = (struct ws_cw_ctx *)writer;
375 (void) data;
376 Curl_bufq_free(&ctx->buf);
377 }
378
379 struct ws_cw_dec_ctx {
380 struct Curl_easy *data;
381 struct websocket *ws;
382 struct Curl_cwriter *next_writer;
383 int cw_type;
384 };
385
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, CURLcode *err)386 static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen,
387 int frame_age, int frame_flags,
388 curl_off_t payload_offset,
389 curl_off_t payload_len,
390 void *user_data,
391 CURLcode *err)
392 {
393 struct ws_cw_dec_ctx *ctx = user_data;
394 struct Curl_easy *data = ctx->data;
395 struct websocket *ws = ctx->ws;
396 curl_off_t remain = (payload_len - (payload_offset + buflen));
397
398 (void)frame_age;
399 if((frame_flags & CURLWS_PING) && !remain) {
400 /* auto-respond to PINGs, only works for single-frame payloads atm */
401 size_t bytes;
402 infof(data, "WS: auto-respond to PING with a PONG");
403 /* send back the exact same content as a PONG */
404 *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG);
405 if(*err)
406 return -1;
407 }
408 else if(buflen || !remain) {
409 /* forward the decoded frame to the next client writer. */
410 update_meta(ws, frame_age, frame_flags, payload_offset,
411 payload_len, buflen);
412
413 *err = Curl_cwriter_write(data, ctx->next_writer, ctx->cw_type,
414 (const char *)buf, buflen);
415 if(*err)
416 return -1;
417 }
418 *err = CURLE_OK;
419 return (ssize_t)buflen;
420 }
421
ws_cw_write(struct Curl_easy *data, struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes)422 static CURLcode ws_cw_write(struct Curl_easy *data,
423 struct Curl_cwriter *writer, int type,
424 const char *buf, size_t nbytes)
425 {
426 struct ws_cw_ctx *ctx = (struct ws_cw_ctx *)writer;
427 struct websocket *ws;
428 CURLcode result;
429
430 if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode)
431 return Curl_cwriter_write(data, writer->next, type, buf, nbytes);
432
433 ws = data->conn->proto.ws;
434 if(!ws) {
435 failf(data, "WS: not a websocket transfer");
436 return CURLE_FAILED_INIT;
437 }
438
439 if(nbytes) {
440 ssize_t nwritten;
441 nwritten = Curl_bufq_write(&ctx->buf, (const unsigned char *)buf,
442 nbytes, &result);
443 if(nwritten < 0) {
444 infof(data, "WS: error adding data to buffer %d", result);
445 return result;
446 }
447 }
448
449 while(!Curl_bufq_is_empty(&ctx->buf)) {
450 struct ws_cw_dec_ctx pass_ctx;
451 pass_ctx.data = data;
452 pass_ctx.ws = ws;
453 pass_ctx.next_writer = writer->next;
454 pass_ctx.cw_type = type;
455 result = ws_dec_pass(&ws->dec, data, &ctx->buf,
456 ws_cw_dec_next, &pass_ctx);
457 if(result == CURLE_AGAIN)
458 /* insufficient amount of data, keep it for later.
459 * we pretend to have written all since we have a copy */
460 return CURLE_OK;
461 else if(result) {
462 infof(data, "WS: decode error %d", (int)result);
463 return result;
464 }
465 }
466
467 if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) {
468 infof(data, "WS: decode ending with %zd frame bytes remaining",
469 Curl_bufq_len(&ctx->buf));
470 return CURLE_RECV_ERROR;
471 }
472
473 return CURLE_OK;
474 }
475
476 /* WebSocket payload decoding client writer. */
477 static const struct Curl_cwtype ws_cw_decode = {
478 "ws-decode",
479 NULL,
480 ws_cw_init,
481 ws_cw_write,
482 ws_cw_close,
483 sizeof(struct ws_cw_ctx)
484 };
485
486
ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data, const char *msg)487 static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data,
488 const char *msg)
489 {
490 infof(data, "WS-ENC: %s [%s%s%s payload=%" CURL_FORMAT_CURL_OFF_T
491 "/%" CURL_FORMAT_CURL_OFF_T "]",
492 msg, ws_frame_name_of_op(enc->firstbyte),
493 (enc->firstbyte & WSBIT_OPCODE_MASK) == WSBIT_OPCODE_CONT ?
494 " CONT" : "",
495 (enc->firstbyte & WSBIT_FIN)? "" : " NON-FIN",
496 enc->payload_len - enc->payload_remain, enc->payload_len);
497 }
498
ws_enc_reset(struct ws_encoder *enc)499 static void ws_enc_reset(struct ws_encoder *enc)
500 {
501 enc->payload_remain = 0;
502 enc->xori = 0;
503 enc->contfragment = FALSE;
504 }
505
ws_enc_init(struct ws_encoder *enc)506 static void ws_enc_init(struct ws_encoder *enc)
507 {
508 ws_enc_reset(enc);
509 }
510
511 /***
512 RFC 6455 Section 5.2
513
514 0 1 2 3
515 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
516 +-+-+-+-+-------+-+-------------+-------------------------------+
517 |F|R|R|R| opcode|M| Payload len | Extended payload length |
518 |I|S|S|S| (4) |A| (7) | (16/64) |
519 |N|V|V|V| |S| | (if payload len==126/127) |
520 | |1|2|3| |K| | |
521 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
522 | Extended payload length continued, if payload len == 127 |
523 + - - - - - - - - - - - - - - - +-------------------------------+
524 | |Masking-key, if MASK set to 1 |
525 +-------------------------------+-------------------------------+
526 | Masking-key (continued) | Payload Data |
527 +-------------------------------- - - - - - - - - - - - - - - - +
528 : Payload Data continued ... :
529 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
530 | Payload Data continued ... |
531 +---------------------------------------------------------------+
532 */
533
ws_enc_write_head(struct Curl_easy *data, struct ws_encoder *enc, unsigned int flags, curl_off_t payload_len, struct bufq *out, CURLcode *err)534 static ssize_t ws_enc_write_head(struct Curl_easy *data,
535 struct ws_encoder *enc,
536 unsigned int flags,
537 curl_off_t payload_len,
538 struct bufq *out,
539 CURLcode *err)
540 {
541 unsigned char firstbyte = 0;
542 unsigned char opcode;
543 unsigned char head[14];
544 size_t hlen;
545 ssize_t n;
546
547 if(payload_len < 0) {
548 failf(data, "WS: starting new frame with negative payload length %"
549 CURL_FORMAT_CURL_OFF_T, payload_len);
550 *err = CURLE_SEND_ERROR;
551 return -1;
552 }
553
554 if(enc->payload_remain > 0) {
555 /* trying to write a new frame before the previous one is finished */
556 failf(data, "WS: starting new frame with %zd bytes from last one"
557 "remaining to be sent", (ssize_t)enc->payload_remain);
558 *err = CURLE_SEND_ERROR;
559 return -1;
560 }
561
562 opcode = ws_frame_flags2op(flags);
563 if(!opcode) {
564 failf(data, "WS: provided flags not recognized '%x'", flags);
565 *err = CURLE_SEND_ERROR;
566 return -1;
567 }
568
569 if(!(flags & CURLWS_CONT)) {
570 if(!enc->contfragment)
571 /* not marked as continuing, this is the final fragment */
572 firstbyte |= WSBIT_FIN | opcode;
573 else
574 /* marked as continuing, this is the final fragment; set CONT
575 opcode and FIN bit */
576 firstbyte |= WSBIT_FIN | WSBIT_OPCODE_CONT;
577
578 enc->contfragment = FALSE;
579 }
580 else if(enc->contfragment) {
581 /* the previous fragment was not a final one and this isn't either, keep a
582 CONT opcode and no FIN bit */
583 firstbyte |= WSBIT_OPCODE_CONT;
584 }
585 else {
586 firstbyte = opcode;
587 enc->contfragment = TRUE;
588 }
589
590 head[0] = enc->firstbyte = firstbyte;
591 if(payload_len > 65535) {
592 head[1] = 127 | WSBIT_MASK;
593 head[2] = (unsigned char)((payload_len >> 56) & 0xff);
594 head[3] = (unsigned char)((payload_len >> 48) & 0xff);
595 head[4] = (unsigned char)((payload_len >> 40) & 0xff);
596 head[5] = (unsigned char)((payload_len >> 32) & 0xff);
597 head[6] = (unsigned char)((payload_len >> 24) & 0xff);
598 head[7] = (unsigned char)((payload_len >> 16) & 0xff);
599 head[8] = (unsigned char)((payload_len >> 8) & 0xff);
600 head[9] = (unsigned char)(payload_len & 0xff);
601 hlen = 10;
602 }
603 else if(payload_len >= 126) {
604 head[1] = 126 | WSBIT_MASK;
605 head[2] = (unsigned char)((payload_len >> 8) & 0xff);
606 head[3] = (unsigned char)(payload_len & 0xff);
607 hlen = 4;
608 }
609 else {
610 head[1] = (unsigned char)payload_len | WSBIT_MASK;
611 hlen = 2;
612 }
613
614 enc->payload_remain = enc->payload_len = payload_len;
615 ws_enc_info(enc, data, "sending");
616
617 /* add 4 bytes mask */
618 memcpy(&head[hlen], &enc->mask, 4);
619 hlen += 4;
620 /* reset for payload to come */
621 enc->xori = 0;
622
623 n = Curl_bufq_write(out, head, hlen, err);
624 if(n < 0)
625 return -1;
626 if((size_t)n != hlen) {
627 /* We use a bufq with SOFT_LIMIT, writing should always succeed */
628 DEBUGASSERT(0);
629 *err = CURLE_SEND_ERROR;
630 return -1;
631 }
632 return n;
633 }
634
ws_enc_write_payload(struct ws_encoder *enc, struct Curl_easy *data, const unsigned char *buf, size_t buflen, struct bufq *out, CURLcode *err)635 static ssize_t ws_enc_write_payload(struct ws_encoder *enc,
636 struct Curl_easy *data,
637 const unsigned char *buf, size_t buflen,
638 struct bufq *out, CURLcode *err)
639 {
640 ssize_t n;
641 size_t i, len;
642
643 if(Curl_bufq_is_full(out)) {
644 *err = CURLE_AGAIN;
645 return -1;
646 }
647
648 /* not the most performant way to do this */
649 len = buflen;
650 if((curl_off_t)len > enc->payload_remain)
651 len = (size_t)enc->payload_remain;
652
653 for(i = 0; i < len; ++i) {
654 unsigned char c = buf[i] ^ enc->mask[enc->xori];
655 n = Curl_bufq_write(out, &c, 1, err);
656 if(n < 0) {
657 if((*err != CURLE_AGAIN) || !i)
658 return -1;
659 break;
660 }
661 enc->xori++;
662 enc->xori &= 3;
663 }
664 enc->payload_remain -= (curl_off_t)i;
665 ws_enc_info(enc, data, "buffered");
666 return (ssize_t)i;
667 }
668
669
670 struct wsfield {
671 const char *name;
672 const char *val;
673 };
674
Curl_ws_request(struct Curl_easy *data, REQTYPE *req)675 CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req)
676 {
677 unsigned int i;
678 CURLcode result = CURLE_OK;
679 unsigned char rand[16];
680 char *randstr;
681 size_t randlen;
682 char keyval[40];
683 struct SingleRequest *k = &data->req;
684 struct wsfield heads[]= {
685 {
686 /* The request MUST contain an |Upgrade| header field whose value
687 MUST include the "websocket" keyword. */
688 "Upgrade:", "websocket"
689 },
690 {
691 /* The request MUST contain a |Connection| header field whose value
692 MUST include the "Upgrade" token. */
693 "Connection:", "Upgrade",
694 },
695 {
696 /* The request MUST include a header field with the name
697 |Sec-WebSocket-Version|. The value of this header field MUST be
698 13. */
699 "Sec-WebSocket-Version:", "13",
700 },
701 {
702 /* The request MUST include a header field with the name
703 |Sec-WebSocket-Key|. The value of this header field MUST be a nonce
704 consisting of a randomly selected 16-byte value that has been
705 base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be
706 selected randomly for each connection. */
707 "Sec-WebSocket-Key:", NULL,
708 }
709 };
710 heads[3].val = &keyval[0];
711
712 /* 16 bytes random */
713 result = Curl_rand(data, (unsigned char *)rand, sizeof(rand));
714 if(result)
715 return result;
716 result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen);
717 if(result)
718 return result;
719 DEBUGASSERT(randlen < sizeof(keyval));
720 if(randlen >= sizeof(keyval))
721 return CURLE_FAILED_INIT;
722 strcpy(keyval, randstr);
723 free(randstr);
724 for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) {
725 if(!Curl_checkheaders(data, STRCONST(heads[i].name))) {
726 #ifdef USE_HYPER
727 char field[128];
728 msnprintf(field, sizeof(field), "%s %s", heads[i].name,
729 heads[i].val);
730 result = Curl_hyper_header(data, req, field);
731 #else
732 (void)data;
733 result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name,
734 heads[i].val);
735 #endif
736 }
737 }
738 k->upgr101 = UPGR101_WS;
739 return result;
740 }
741
742 /*
743 * 'nread' is number of bytes of websocket data already in the buffer at
744 * 'mem'.
745 */
Curl_ws_accept(struct Curl_easy *data, const char *mem, size_t nread)746 CURLcode Curl_ws_accept(struct Curl_easy *data,
747 const char *mem, size_t nread)
748 {
749 struct SingleRequest *k = &data->req;
750 struct websocket *ws;
751 struct Curl_cwriter *ws_dec_writer;
752 CURLcode result;
753
754 DEBUGASSERT(data->conn);
755 ws = data->conn->proto.ws;
756 if(!ws) {
757 ws = calloc(1, sizeof(*ws));
758 if(!ws)
759 return CURLE_OUT_OF_MEMORY;
760 data->conn->proto.ws = ws;
761 Curl_bufq_init2(&ws->recvbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT,
762 BUFQ_OPT_SOFT_LIMIT);
763 Curl_bufq_init2(&ws->sendbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT,
764 BUFQ_OPT_SOFT_LIMIT);
765 ws_dec_init(&ws->dec);
766 ws_enc_init(&ws->enc);
767 }
768 else {
769 Curl_bufq_reset(&ws->recvbuf);
770 ws_dec_reset(&ws->dec);
771 ws_enc_reset(&ws->enc);
772 }
773 /* Verify the Sec-WebSocket-Accept response.
774
775 The sent value is the base64 encoded version of a SHA-1 hash done on the
776 |Sec-WebSocket-Key| header field concatenated with
777 the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".
778 */
779
780 /* If the response includes a |Sec-WebSocket-Extensions| header field and
781 this header field indicates the use of an extension that was not present
782 in the client's handshake (the server has indicated an extension not
783 requested by the client), the client MUST Fail the WebSocket Connection.
784 */
785
786 /* If the response includes a |Sec-WebSocket-Protocol| header field
787 and this header field indicates the use of a subprotocol that was
788 not present in the client's handshake (the server has indicated a
789 subprotocol not requested by the client), the client MUST Fail
790 the WebSocket Connection. */
791
792 /* 4 bytes random */
793
794 result = Curl_rand(data, (unsigned char *)&ws->enc.mask,
795 sizeof(ws->enc.mask));
796 if(result)
797 return result;
798 infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
799 ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]);
800
801 /* Install our client writer that decodes WS frames payload */
802 result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode,
803 CURL_CW_CONTENT_DECODE);
804 if(result)
805 return result;
806
807 result = Curl_cwriter_add(data, ws_dec_writer);
808 if(result) {
809 Curl_cwriter_free(data, ws_dec_writer);
810 return result;
811 }
812
813 if(data->set.connect_only) {
814 ssize_t nwritten;
815 /* In CONNECT_ONLY setup, the payloads from `mem` need to be received
816 * when using `curl_ws_recv` later on after this transfer is already
817 * marked as DONE. */
818 nwritten = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)mem,
819 nread, &result);
820 if(nwritten < 0)
821 return result;
822 infof(data, "%zu bytes websocket payload", nread);
823 }
824 else { /* !connect_only */
825 /* And pass any additional data to the writers */
826 if(nread) {
827 result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)mem, nread);
828 }
829 }
830 k->upgr101 = UPGR101_RECEIVED;
831
832 return result;
833 }
834
835 struct ws_collect {
836 struct Curl_easy *data;
837 void *buffer;
838 size_t buflen;
839 size_t bufidx;
840 int frame_age;
841 int frame_flags;
842 curl_off_t payload_offset;
843 curl_off_t payload_len;
844 bool written;
845 };
846
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, CURLcode *err)847 static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen,
848 int frame_age, int frame_flags,
849 curl_off_t payload_offset,
850 curl_off_t payload_len,
851 void *userp,
852 CURLcode *err)
853 {
854 struct ws_collect *ctx = userp;
855 size_t nwritten;
856 curl_off_t remain = (payload_len - (payload_offset + buflen));
857
858 if(!ctx->bufidx) {
859 /* first write */
860 ctx->frame_age = frame_age;
861 ctx->frame_flags = frame_flags;
862 ctx->payload_offset = payload_offset;
863 ctx->payload_len = payload_len;
864 }
865
866 if((frame_flags & CURLWS_PING) && !remain) {
867 /* auto-respond to PINGs, only works for single-frame payloads atm */
868 size_t bytes;
869 infof(ctx->data, "WS: auto-respond to PING with a PONG");
870 /* send back the exact same content as a PONG */
871 *err = curl_ws_send(ctx->data, buf, buflen, &bytes, 0, CURLWS_PONG);
872 if(*err)
873 return -1;
874 nwritten = bytes;
875 }
876 else {
877 ctx->written = TRUE;
878 DEBUGASSERT(ctx->buflen >= ctx->bufidx);
879 nwritten = CURLMIN(buflen, ctx->buflen - ctx->bufidx);
880 if(!nwritten) {
881 if(!buflen) { /* 0 length write, we accept that */
882 *err = CURLE_OK;
883 return 0;
884 }
885 *err = CURLE_AGAIN; /* no more space */
886 return -1;
887 }
888 *err = CURLE_OK;
889 memcpy(ctx->buffer, buf, nwritten);
890 ctx->bufidx += nwritten;
891 }
892 return nwritten;
893 }
894
nw_in_recv(void *reader_ctx, unsigned char *buf, size_t buflen, CURLcode *err)895 static ssize_t nw_in_recv(void *reader_ctx,
896 unsigned char *buf, size_t buflen,
897 CURLcode *err)
898 {
899 struct Curl_easy *data = reader_ctx;
900 size_t nread;
901
902 *err = curl_easy_recv(data, buf, buflen, &nread);
903 if(*err)
904 return -1;
905 return (ssize_t)nread;
906 }
907
curl_ws_recv(struct Curl_easy *data, void *buffer, size_t buflen, size_t *nread, const struct curl_ws_frame **metap)908 CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer,
909 size_t buflen, size_t *nread,
910 const struct curl_ws_frame **metap)
911 {
912 struct connectdata *conn = data->conn;
913 struct websocket *ws;
914 bool done = FALSE; /* not filled passed buffer yet */
915 struct ws_collect ctx;
916 CURLcode result;
917
918 if(!conn) {
919 /* Unhappy hack with lifetimes of transfers and connection */
920 if(!data->set.connect_only) {
921 failf(data, "CONNECT_ONLY is required");
922 return CURLE_UNSUPPORTED_PROTOCOL;
923 }
924
925 Curl_getconnectinfo(data, &conn);
926 if(!conn) {
927 failf(data, "connection not found");
928 return CURLE_BAD_FUNCTION_ARGUMENT;
929 }
930 }
931 ws = conn->proto.ws;
932 if(!ws) {
933 failf(data, "connection is not setup for websocket");
934 return CURLE_BAD_FUNCTION_ARGUMENT;
935 }
936
937 *nread = 0;
938 *metap = NULL;
939 /* get a download buffer */
940 result = Curl_preconnect(data);
941 if(result)
942 return result;
943
944 memset(&ctx, 0, sizeof(ctx));
945 ctx.data = data;
946 ctx.buffer = buffer;
947 ctx.buflen = buflen;
948
949 while(!done) {
950 /* receive more when our buffer is empty */
951 if(Curl_bufq_is_empty(&ws->recvbuf)) {
952 ssize_t n = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &result);
953 if(n < 0) {
954 return result;
955 }
956 else if(n == 0) {
957 /* connection closed */
958 infof(data, "connection expectedly closed?");
959 return CURLE_GOT_NOTHING;
960 }
961 DEBUGF(infof(data, "curl_ws_recv, added %zu bytes from network",
962 Curl_bufq_len(&ws->recvbuf)));
963 }
964
965 result = ws_dec_pass(&ws->dec, data, &ws->recvbuf,
966 ws_client_collect, &ctx);
967 if(result == CURLE_AGAIN) {
968 if(!ctx.written) {
969 ws_dec_info(&ws->dec, data, "need more input");
970 continue; /* nothing written, try more input */
971 }
972 done = TRUE;
973 break;
974 }
975 else if(result) {
976 return result;
977 }
978 else if(ctx.written) {
979 /* The decoded frame is passed back to our caller.
980 * There are frames like PING were we auto-respond to and
981 * that we do not return. For these `ctx.written` is not set. */
982 done = TRUE;
983 break;
984 }
985 }
986
987 /* update frame information to be passed back */
988 update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset,
989 ctx.payload_len, ctx.bufidx);
990 *metap = &ws->frame;
991 *nread = ws->frame.len;
992 /* infof(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %"
993 CURL_FORMAT_CURL_OFF_T ", %" CURL_FORMAT_CURL_OFF_T " left)",
994 buflen, *nread, ws->frame.offset, ws->frame.bytesleft); */
995 return CURLE_OK;
996 }
997
ws_flush(struct Curl_easy *data, struct websocket *ws, bool complete)998 static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
999 bool complete)
1000 {
1001 if(!Curl_bufq_is_empty(&ws->sendbuf)) {
1002 CURLcode result;
1003 const unsigned char *out;
1004 size_t outlen;
1005 ssize_t n;
1006
1007 while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) {
1008 if(data->set.connect_only)
1009 result = Curl_senddata(data, out, outlen, &n);
1010 else
1011 result = Curl_write(data, data->conn->writesockfd, out, outlen, &n);
1012 if(result) {
1013 if(result == CURLE_AGAIN) {
1014 if(!complete) {
1015 infof(data, "WS: flush EAGAIN, %zu bytes remain in buffer",
1016 Curl_bufq_len(&ws->sendbuf));
1017 return result;
1018 }
1019 /* TODO: the current design does not allow for buffered writes.
1020 * We need to flush the buffer now. There is no ws_flush() later */
1021 n = 0;
1022 continue;
1023 }
1024 else if(result) {
1025 failf(data, "WS: flush, write error %d", result);
1026 return result;
1027 }
1028 }
1029 else {
1030 infof(data, "WS: flushed %zu bytes", (size_t)n);
1031 Curl_bufq_skip(&ws->sendbuf, (size_t)n);
1032 }
1033 }
1034 }
1035 return CURLE_OK;
1036 }
1037
curl_ws_send(CURL *data, const void *buffer, size_t buflen, size_t *sent, curl_off_t fragsize, unsigned int flags)1038 CURL_EXTERN CURLcode curl_ws_send(CURL *data, const void *buffer,
1039 size_t buflen, size_t *sent,
1040 curl_off_t fragsize,
1041 unsigned int flags)
1042 {
1043 struct websocket *ws;
1044 ssize_t nwritten, n;
1045 size_t space;
1046 CURLcode result;
1047
1048 *sent = 0;
1049 if(!data->conn && data->set.connect_only) {
1050 result = Curl_connect_only_attach(data);
1051 if(result)
1052 return result;
1053 }
1054 if(!data->conn) {
1055 failf(data, "No associated connection");
1056 return CURLE_SEND_ERROR;
1057 }
1058 if(!data->conn->proto.ws) {
1059 failf(data, "Not a websocket transfer");
1060 return CURLE_SEND_ERROR;
1061 }
1062 ws = data->conn->proto.ws;
1063
1064 if(data->set.ws_raw_mode) {
1065 if(fragsize || flags) {
1066 DEBUGF(infof(data, "ws_send: "
1067 "fragsize and flags cannot be non-zero in raw mode"));
1068 return CURLE_BAD_FUNCTION_ARGUMENT;
1069 }
1070 if(!buflen)
1071 /* nothing to do */
1072 return CURLE_OK;
1073 /* raw mode sends exactly what was requested, and this is from within
1074 the write callback */
1075 if(Curl_is_in_callback(data)) {
1076 result = Curl_write(data, data->conn->writesockfd, buffer, buflen,
1077 &nwritten);
1078 }
1079 else
1080 result = Curl_senddata(data, buffer, buflen, &nwritten);
1081
1082 infof(data, "WS: wanted to send %zu bytes, sent %zu bytes",
1083 buflen, nwritten);
1084 *sent = (nwritten >= 0)? (size_t)nwritten : 0;
1085 return result;
1086 }
1087
1088 /* Not RAW mode, buf we do the frame encoding */
1089 result = ws_flush(data, ws, FALSE);
1090 if(result)
1091 return result;
1092
1093 /* TODO: the current design does not allow partial writes, afaict.
1094 * It is not clear who the application is supposed to react. */
1095 space = Curl_bufq_space(&ws->sendbuf);
1096 DEBUGF(infof(data, "curl_ws_send(len=%zu), sendbuf len=%zu space %zu",
1097 buflen, Curl_bufq_len(&ws->sendbuf), space));
1098 if(space < 14)
1099 return CURLE_AGAIN;
1100
1101 if(flags & CURLWS_OFFSET) {
1102 if(fragsize) {
1103 /* a frame series 'fragsize' bytes big, this is the first */
1104 n = ws_enc_write_head(data, &ws->enc, flags, fragsize,
1105 &ws->sendbuf, &result);
1106 if(n < 0)
1107 return result;
1108 }
1109 else {
1110 if((curl_off_t)buflen > ws->enc.payload_remain) {
1111 infof(data, "WS: unaligned frame size (sending %zu instead of %"
1112 CURL_FORMAT_CURL_OFF_T ")",
1113 buflen, ws->enc.payload_remain);
1114 }
1115 }
1116 }
1117 else if(!ws->enc.payload_remain) {
1118 n = ws_enc_write_head(data, &ws->enc, flags, (curl_off_t)buflen,
1119 &ws->sendbuf, &result);
1120 if(n < 0)
1121 return result;
1122 }
1123
1124 n = ws_enc_write_payload(&ws->enc, data,
1125 buffer, buflen, &ws->sendbuf, &result);
1126 if(n < 0)
1127 return result;
1128
1129 *sent = (size_t)n;
1130 return ws_flush(data, ws, TRUE);
1131 }
1132
ws_free(struct connectdata *conn)1133 static void ws_free(struct connectdata *conn)
1134 {
1135 if(conn && conn->proto.ws) {
1136 Curl_bufq_free(&conn->proto.ws->recvbuf);
1137 Curl_bufq_free(&conn->proto.ws->sendbuf);
1138 Curl_safefree(conn->proto.ws);
1139 }
1140 }
1141
ws_setup_conn(struct Curl_easy *data, struct connectdata *conn)1142 static CURLcode ws_setup_conn(struct Curl_easy *data,
1143 struct connectdata *conn)
1144 {
1145 /* websockets is 1.1 only (for now) */
1146 data->state.httpwant = CURL_HTTP_VERSION_1_1;
1147 return Curl_http_setup_conn(data, conn);
1148 }
1149
1150
Curl_ws_done(struct Curl_easy *data)1151 void Curl_ws_done(struct Curl_easy *data)
1152 {
1153 (void)data;
1154 }
1155
ws_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection)1156 static CURLcode ws_disconnect(struct Curl_easy *data,
1157 struct connectdata *conn,
1158 bool dead_connection)
1159 {
1160 (void)data;
1161 (void)dead_connection;
1162 ws_free(conn);
1163 return CURLE_OK;
1164 }
1165
curl_ws_meta(struct Curl_easy *data)1166 CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
1167 {
1168 /* we only return something for websocket, called from within the callback
1169 when not using raw mode */
1170 if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->conn &&
1171 data->conn->proto.ws && !data->set.ws_raw_mode)
1172 return &data->conn->proto.ws->frame;
1173 return NULL;
1174 }
1175
1176 const struct Curl_handler Curl_handler_ws = {
1177 "WS", /* scheme */
1178 ws_setup_conn, /* setup_connection */
1179 Curl_http, /* do_it */
1180 Curl_http_done, /* done */
1181 ZERO_NULL, /* do_more */
1182 Curl_http_connect, /* connect_it */
1183 ZERO_NULL, /* connecting */
1184 ZERO_NULL, /* doing */
1185 ZERO_NULL, /* proto_getsock */
1186 Curl_http_getsock_do, /* doing_getsock */
1187 ZERO_NULL, /* domore_getsock */
1188 ZERO_NULL, /* perform_getsock */
1189 ws_disconnect, /* disconnect */
1190 Curl_http_write_resp, /* write_resp */
1191 ZERO_NULL, /* connection_check */
1192 ZERO_NULL, /* attach connection */
1193 PORT_HTTP, /* defport */
1194 CURLPROTO_WS, /* protocol */
1195 CURLPROTO_HTTP, /* family */
1196 PROTOPT_CREDSPERREQUEST | /* flags */
1197 PROTOPT_USERPWDCTRL
1198 };
1199
1200 #ifdef USE_SSL
1201 const struct Curl_handler Curl_handler_wss = {
1202 "WSS", /* scheme */
1203 ws_setup_conn, /* setup_connection */
1204 Curl_http, /* do_it */
1205 Curl_http_done, /* done */
1206 ZERO_NULL, /* do_more */
1207 Curl_http_connect, /* connect_it */
1208 NULL, /* connecting */
1209 ZERO_NULL, /* doing */
1210 NULL, /* proto_getsock */
1211 Curl_http_getsock_do, /* doing_getsock */
1212 ZERO_NULL, /* domore_getsock */
1213 ZERO_NULL, /* perform_getsock */
1214 ws_disconnect, /* disconnect */
1215 Curl_http_write_resp, /* write_resp */
1216 ZERO_NULL, /* connection_check */
1217 ZERO_NULL, /* attach connection */
1218 PORT_HTTPS, /* defport */
1219 CURLPROTO_WSS, /* protocol */
1220 CURLPROTO_HTTP, /* family */
1221 PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */
1222 PROTOPT_USERPWDCTRL
1223 };
1224 #endif
1225
1226
1227 #else
1228
curl_ws_recv(CURL *curl, void *buffer, size_t buflen, size_t *nread, const struct curl_ws_frame **metap)1229 CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
1230 size_t *nread,
1231 const struct curl_ws_frame **metap)
1232 {
1233 (void)curl;
1234 (void)buffer;
1235 (void)buflen;
1236 (void)nread;
1237 (void)metap;
1238 return CURLE_NOT_BUILT_IN;
1239 }
1240
curl_ws_send(CURL *curl, const void *buffer, size_t buflen, size_t *sent, curl_off_t fragsize, unsigned int flags)1241 CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
1242 size_t buflen, size_t *sent,
1243 curl_off_t fragsize,
1244 unsigned int flags)
1245 {
1246 (void)curl;
1247 (void)buffer;
1248 (void)buflen;
1249 (void)sent;
1250 (void)fragsize;
1251 (void)flags;
1252 return CURLE_NOT_BUILT_IN;
1253 }
1254
curl_ws_meta(struct Curl_easy *data)1255 CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
1256 {
1257 (void)data;
1258 return NULL;
1259 }
1260 #endif /* USE_WEBSOCKETS */
1261