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 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 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 256static 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 288static 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 344static 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 */ 358struct ws_cw_ctx { 359 struct Curl_cwriter super; 360 struct bufq buf; 361}; 362 363static 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 372static 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 379struct 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 386static 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 422static 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. */ 477static 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 487static 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 499static void ws_enc_reset(struct ws_encoder *enc) 500{ 501 enc->payload_remain = 0; 502 enc->xori = 0; 503 enc->contfragment = FALSE; 504} 505 506static 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 534static 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 635static 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 670struct wsfield { 671 const char *name; 672 const char *val; 673}; 674 675CURLcode 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 */ 746CURLcode 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 835struct 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 847static 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 895static 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 908CURL_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 998static 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 1038CURL_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 1133static 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 1142static 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 1151void Curl_ws_done(struct Curl_easy *data) 1152{ 1153 (void)data; 1154} 1155 1156static 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 1166CURL_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 1176const 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 1201const 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 1229CURL_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 1241CURL_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 1255CURL_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