1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
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.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 * SPDX-License-Identifier: curl
23 ***************************************************************************/
24 #include "curl_setup.h"
25 #include <curl/curl.h>
32 #include "curl_base64.h"
40 /* The last 3 #include files should be in this order */
41 #include "curl_printf.h"
42 #include "curl_memory.h"
50 CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req)
53 CURLcode result = CURLE_OK;
54 unsigned char rand[16];
58 struct SingleRequest *k = &data->req;
59 struct wsfield heads[]= {
61 /* The request MUST contain an |Upgrade| header field whose value
62 MUST include the "websocket" keyword. */
63 "Upgrade:", "websocket"
66 /* The request MUST contain a |Connection| header field whose value
67 MUST include the "Upgrade" token. */
68 "Connection:", "Upgrade",
71 /* The request MUST include a header field with the name
72 |Sec-WebSocket-Version|. The value of this header field MUST be
74 "Sec-WebSocket-Version:", "13",
77 /* The request MUST include a header field with the name
78 |Sec-WebSocket-Key|. The value of this header field MUST be a nonce
79 consisting of a randomly selected 16-byte value that has been
80 base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be
81 selected randomly for each connection. */
82 "Sec-WebSocket-Key:", NULL,
85 heads[3].val = &keyval[0];
88 result = Curl_rand(data, (unsigned char *)rand, sizeof(rand));
91 result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen);
94 DEBUGASSERT(randlen < sizeof(keyval));
95 if(randlen >= sizeof(keyval))
96 return CURLE_FAILED_INIT;
97 strcpy(keyval, randstr);
99 for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) {
100 if(!Curl_checkheaders(data, STRCONST(heads[i].name))) {
103 msnprintf(field, sizeof(field), "%s %s", heads[i].name,
105 result = Curl_hyper_header(data, req, field);
108 result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name,
113 k->upgr101 = UPGR101_WS;
114 Curl_dyn_init(&data->req.p.http->ws.buf, MAX_WS_SIZE * 2);
118 CURLcode Curl_ws_accept(struct Curl_easy *data)
120 struct SingleRequest *k = &data->req;
121 struct HTTP *ws = data->req.p.http;
122 struct connectdata *conn = data->conn;
123 struct websocket *wsp = &data->req.p.http->ws;
126 /* Verify the Sec-WebSocket-Accept response.
128 The sent value is the base64 encoded version of a SHA-1 hash done on the
129 |Sec-WebSocket-Key| header field concatenated with
130 the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".
133 /* If the response includes a |Sec-WebSocket-Extensions| header field and
134 this header field indicates the use of an extension that was not present
135 in the client's handshake (the server has indicated an extension not
136 requested by the client), the client MUST Fail the WebSocket Connection.
139 /* If the response includes a |Sec-WebSocket-Protocol| header field
140 and this header field indicates the use of a subprotocol that was
141 not present in the client's handshake (the server has indicated a
142 subprotocol not requested by the client), the client MUST Fail
143 the WebSocket Connection. */
146 result = Curl_rand(data, (unsigned char *)&ws->ws.mask, sizeof(ws->ws.mask));
150 infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
151 ws->ws.mask[0], ws->ws.mask[1], ws->ws.mask[2], ws->ws.mask[3]);
152 k->upgr101 = UPGR101_RECEIVED;
154 if(data->set.connect_only)
155 /* switch off non-blocking sockets */
156 (void)curlx_nonblock(conn->sock[FIRSTSOCKET], FALSE);
162 #define WSBIT_FIN 0x80
163 #define WSBIT_OPCODE_CONT 0
164 #define WSBIT_OPCODE_TEXT (1)
165 #define WSBIT_OPCODE_BIN (2)
166 #define WSBIT_OPCODE_CLOSE (8)
167 #define WSBIT_OPCODE_PING (9)
168 #define WSBIT_OPCODE_PONG (0xa)
169 #define WSBIT_OPCODE_MASK (0xf)
171 #define WSBIT_MASK 0x80
173 /* remove the spent bytes from the beginning of the buffer as that part has
174 now been delivered to the application */
175 static void ws_decode_clear(struct Curl_easy *data)
177 struct websocket *wsp = &data->req.p.http->ws;
178 size_t spent = wsp->usedbuf;
179 size_t len = Curl_dyn_len(&wsp->buf);
180 size_t keep = len - spent;
181 DEBUGASSERT(len >= spent);
182 Curl_dyn_tail(&wsp->buf, keep);
185 /* ws_decode() decodes a binary frame into structured WebSocket data,
187 wpkt - the incoming raw data. If NULL, work on the already buffered data.
188 ilen - the size of the provided data, perhaps too little, perhaps too much
189 out - stored pointed to extracted data
190 olen - stored length of the extracted data
191 oleft - number of unread bytes pending to that belongs to this frame
192 more - if there is more data in there
193 flags - stored bitmask about the frame
195 Returns CURLE_AGAIN if there is only a partial frame in the buffer. Then it
196 stores the first part in the ->extra buffer to be used in the next call
197 when more data is provided.
200 static CURLcode ws_decode(struct Curl_easy *data,
201 unsigned char *wpkt, size_t ilen,
202 unsigned char **out, size_t *olen,
208 unsigned char opcode;
210 size_t dataindex = 2;
211 curl_off_t plen; /* size of data in the buffer */
212 curl_off_t payloadsize;
213 struct websocket *wsp = &data->req.p.http->ws;
219 /* add the incoming bytes, if any */
221 result = Curl_dyn_addn(&wsp->buf, wpkt, ilen);
226 plen = Curl_dyn_len(&wsp->buf);
228 /* the smallest possible frame is two bytes */
229 infof(data, "WS: plen == %u, EAGAIN", (int)plen);
233 p = Curl_dyn_uptr(&wsp->buf);
235 fin = p[0] & WSBIT_FIN;
236 opcode = p[0] & WSBIT_OPCODE_MASK;
237 infof(data, "WS:%d received FIN bit %u", __LINE__, (int)fin);
240 case WSBIT_OPCODE_CONT:
242 *flags |= CURLWS_CONT;
243 infof(data, "WS: received OPCODE CONT");
245 case WSBIT_OPCODE_TEXT:
246 infof(data, "WS: received OPCODE TEXT");
247 *flags |= CURLWS_TEXT;
249 case WSBIT_OPCODE_BIN:
250 infof(data, "WS: received OPCODE BINARY");
251 *flags |= CURLWS_BINARY;
253 case WSBIT_OPCODE_CLOSE:
254 infof(data, "WS: received OPCODE CLOSE");
255 *flags |= CURLWS_CLOSE;
257 case WSBIT_OPCODE_PING:
258 infof(data, "WS: received OPCODE PING");
259 *flags |= CURLWS_PING;
261 case WSBIT_OPCODE_PONG:
262 infof(data, "WS: received OPCODE PONG");
263 *flags |= CURLWS_PONG;
267 if(p[1] & WSBIT_MASK) {
268 /* A client MUST close a connection if it detects a masked frame. */
269 failf(data, "WS: masked input frame");
270 return CURLE_RECV_ERROR;
273 if(payloadsize == 126) {
275 infof(data, "WS:%d plen == %u, EAGAIN", __LINE__, (int)plen);
276 return CURLE_AGAIN; /* not enough data available */
278 payloadsize = (p[2] << 8) | p[3];
281 else if(payloadsize == 127) {
282 /* 64 bit payload size */
286 failf(data, "WS: too large frame");
287 return CURLE_RECV_ERROR;
290 payloadsize = ((curl_off_t)p[2] << 56) |
291 (curl_off_t)p[3] << 48 |
292 (curl_off_t)p[4] << 40 |
293 (curl_off_t)p[5] << 32 |
294 (curl_off_t)p[6] << 24 |
295 (curl_off_t)p[7] << 16 |
296 (curl_off_t)p[8] << 8 |
300 total = dataindex + payloadsize;
302 /* deliver a partial frame */
303 *oleft = total - dataindex;
304 payloadsize = total - dataindex;
309 /* there is another fragment after */
313 /* point to the payload */
314 *out = &p[dataindex];
316 /* return the payload length */
319 /* number of bytes "used" from the buffer */
320 wsp->usedbuf = dataindex + payloadsize;
321 infof(data, "WS: received %zu bytes payload (%zu left)",
322 payloadsize, *oleft);
326 /* Curl_ws_writecb() is the write callback for websocket traffic. The
327 websocket data is provided to this raw, in chunks. This function should
328 handle/decode the data and call the "real" underlying callback accordingly.
330 size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */,
331 size_t nitems, void *userp)
333 struct HTTP *ws = (struct HTTP *)userp;
334 struct Curl_easy *data = ws->ws.data;
335 void *writebody_ptr = data->set.out;
336 if(data->set.ws_raw_mode)
337 return data->set.fwrite_func(buffer, size, nitems, writebody_ptr);
339 unsigned char *frame = NULL;
343 bool more; /* there's is more to parse in the buffer */
348 oleft = ws->ws.frame.bytesleft;
350 unsigned int recvflags;
351 result = ws_decode(data, (unsigned char *)buffer, nitems,
352 &frame, &flen, &oleft, &more, &recvflags);
353 if(result == CURLE_AGAIN)
354 /* insufficient amount of data, keep it for later */
357 infof(data, "WS: decode error %d", (int)result);
360 /* Store details about the frame to be reachable with curl_ws_meta()
361 from within the write callback */
362 ws->ws.frame.age = 0;
363 ws->ws.frame.offset = 0;
364 ws->ws.frame.flags = recvflags;
365 ws->ws.frame.bytesleft = oleft;
368 if(nitems > (size_t)ws->ws.frame.bytesleft) {
369 nitems = ws->ws.frame.bytesleft;
374 ws->ws.frame.offset += nitems;
375 ws->ws.frame.bytesleft -= nitems;
376 frame = (unsigned char *)buffer;
379 if((ws->ws.frame.flags & CURLWS_PING) && !oleft) {
380 /* auto-respond to PINGs, only works for single-frame payloads atm */
382 infof(data, "WS: auto-respond to PING with a PONG");
384 /* send back the exact same content as a PONG */
385 result = curl_ws_send(data, frame, flen, &bytes, 0, CURLWS_PONG);
390 /* deliver the decoded frame to the user callback */
391 Curl_set_in_callback(data, true);
392 wrote = data->set.fwrite_func((char *)frame, 1, flen, writebody_ptr);
393 Curl_set_in_callback(data, false);
398 ws->ws.frame.offset += flen;
399 /* the websocket frame has been delivered */
400 ws_decode_clear(data);
402 /* there's more websocket data to deal with in the buffer */
403 buffer = NULL; /* the buffer as been drained already */
411 CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer,
412 size_t buflen, size_t *nread,
413 struct curl_ws_frame **metap)
417 struct websocket *wsp = &data->req.p.http->ws;
421 /* get a download buffer */
422 result = Curl_preconnect(data);
427 bool drain = FALSE; /* if there is pending buffered data to drain */
428 char *inbuf = data->state.buffer;
429 bytes = wsp->stillbuffer;
431 result = curl_easy_recv(data, data->state.buffer,
432 data->set.buffer_size, &bytes);
437 /* the pending bytes can be found here */
445 unsigned int recvflags;
446 curl_off_t oleft = wsp->frame.bytesleft;
448 infof(data, "WS: got %u websocket bytes to decode", (int)bytes);
449 if(!oleft && !drain) {
450 result = ws_decode(data, (unsigned char *)inbuf, bytes,
451 &out, &olen, &oleft, &more, &recvflags);
452 if(result == CURLE_AGAIN)
453 /* a packet fragment only */
457 wsp->frame.offset = 0;
458 wsp->frame.bytesleft = oleft;
459 wsp->frame.flags = recvflags;
463 out = (unsigned char *)wsp->stillb;
464 recvflags = wsp->frame.flags;
465 if((curl_off_t)buflen < oleft)
466 /* there is still data left after this */
467 wsp->frame.bytesleft -= buflen;
469 wsp->frame.bytesleft = 0;
472 /* auto-respond to PINGs */
473 if((recvflags & CURLWS_PING) && !oleft) {
474 infof(data, "WS: auto-respond to PING with a PONG");
475 /* send back the exact same content as a PONG */
476 result = curl_ws_send(data, out, olen, &bytes, 0, CURLWS_PONG);
482 /* copy the payload to the user buffer */
483 memcpy(buffer, out, olen);
486 /* websocket frame has been delivered */
487 ws_decode_clear(data);
490 /* copy a partial payload */
491 memcpy(buffer, out, buflen);
493 /* remember what is left and where */
494 wsp->stillbuffer = olen - buflen;
495 wsp->stillb = (char *)buffer + buflen;
497 wsp->frame.offset += *nread;
504 *metap = &wsp->frame;
508 static void ws_xor(struct Curl_easy *data,
509 const unsigned char *source,
513 struct websocket *wsp = &data->req.p.http->ws;
515 /* append payload after the mask, XOR appropriately */
516 for(i = 0; i < len; i++) {
517 dest[i] = source[i] ^ wsp->mask[wsp->xori];
527 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
528 +-+-+-+-+-------+-+-------------+-------------------------------+
529 |F|R|R|R| opcode|M| Payload len | Extended payload length |
530 |I|S|S|S| (4) |A| (7) | (16/64) |
531 |N|V|V|V| |S| | (if payload len==126/127) |
533 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
534 | Extended payload length continued, if payload len == 127 |
535 + - - - - - - - - - - - - - - - +-------------------------------+
536 | |Masking-key, if MASK set to 1 |
537 +-------------------------------+-------------------------------+
538 | Masking-key (continued) | Payload Data |
539 +-------------------------------- - - - - - - - - - - - - - - - +
540 : Payload Data continued ... :
541 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
542 | Payload Data continued ... |
543 +---------------------------------------------------------------+
546 static size_t ws_packethead(struct Curl_easy *data,
547 size_t len, unsigned int flags)
549 struct HTTP *ws = data->req.p.http;
550 unsigned char *out = (unsigned char *)data->state.ulbuf;
551 unsigned char firstbyte = 0;
553 unsigned char opcode;
554 if(flags & CURLWS_TEXT) {
555 opcode = WSBIT_OPCODE_TEXT;
556 infof(data, "WS: send OPCODE TEXT");
558 else if(flags & CURLWS_CLOSE) {
559 opcode = WSBIT_OPCODE_CLOSE;
560 infof(data, "WS: send OPCODE CLOSE");
562 else if(flags & CURLWS_PING) {
563 opcode = WSBIT_OPCODE_PING;
564 infof(data, "WS: send OPCODE PING");
566 else if(flags & CURLWS_PONG) {
567 opcode = WSBIT_OPCODE_PONG;
568 infof(data, "WS: send OPCODE PONG");
571 opcode = WSBIT_OPCODE_BIN;
572 infof(data, "WS: send OPCODE BINARY");
575 if(!(flags & CURLWS_CONT)) {
576 /* if not marked as continuing, assume this is the final fragment */
577 firstbyte |= WSBIT_FIN | opcode;
578 ws->ws.contfragment = FALSE;
580 else if(ws->ws.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;
586 ws->ws.contfragment = TRUE;
590 out[1] = 127 | WSBIT_MASK;
591 out[2] = (len >> 8) & 0xff;
596 out[1] = 126 | WSBIT_MASK;
597 out[2] = (len >> 8) & 0xff;
602 out[1] = (unsigned char)len | WSBIT_MASK;
606 infof(data, "WS: send FIN bit %u (byte %02x)",
607 firstbyte & WSBIT_FIN ? 1 : 0,
609 infof(data, "WS: send payload len %u", (int)len);
612 memcpy(&out[outi], &ws->ws.mask, 4);
614 if(data->set.upload_buffer_size < (len + 10))
617 /* pass over the mask */
621 /* return packet size */
625 CURL_EXTERN CURLcode curl_ws_send(struct Curl_easy *data, const void *buffer,
626 size_t buflen, size_t *sent,
627 curl_off_t totalsize,
628 unsigned int sendflags)
634 struct websocket *wsp = &data->req.p.http->ws;
636 if(!data->set.ws_raw_mode) {
637 result = Curl_get_upload_buffer(data);
642 if(totalsize || sendflags)
643 return CURLE_BAD_FUNCTION_ARGUMENT;
646 if(data->set.ws_raw_mode) {
650 /* raw mode sends exactly what was requested, and this is from within
651 the write callback */
652 if(Curl_is_in_callback(data))
653 result = Curl_write(data, data->conn->writesockfd, buffer, buflen,
656 result = Curl_senddata(data, buffer, buflen, &written);
658 infof(data, "WS: wanted to send %zu bytes, sent %zu bytes",
664 if(buflen > (data->set.upload_buffer_size - 10))
665 /* don't do more than this in one go */
666 buflen = data->set.upload_buffer_size - 10;
668 if(sendflags & CURLWS_OFFSET) {
670 /* a frame series 'totalsize' bytes big, this is the first */
671 headlen = ws_packethead(data, totalsize, sendflags);
672 wsp->sleft = totalsize - buflen;
676 if((curl_off_t)buflen > wsp->sleft) {
677 infof(data, "WS: unaligned frame size (sending %zu instead of %zu)",
682 wsp->sleft -= buflen;
686 headlen = ws_packethead(data, buflen, sendflags);
688 /* headlen is the size of the frame header */
689 out = data->state.ulbuf;
691 /* for PING and PONG etc there might not be a payload */
692 ws_xor(data, buffer, (unsigned char *)out + headlen, buflen);
694 if(data->set.connect_only)
695 result = Curl_senddata(data, out, buflen + headlen, &written);
697 result = Curl_write(data, data->conn->writesockfd, out,
698 buflen + headlen, &written);
700 infof(data, "WS: wanted to send %zu bytes, sent %zu bytes",
701 headlen + buflen, written);
707 void Curl_ws_done(struct Curl_easy *data)
709 struct websocket *wsp = &data->req.p.http->ws;
711 Curl_dyn_free(&wsp->buf);
714 CURL_EXTERN struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
716 /* we only return something for websocket, called from within the callback
717 when not using raw mode */
718 if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->req.p.http &&
719 !data->set.ws_raw_mode)
720 return &data->req.p.http->ws.frame;
726 CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
728 struct curl_ws_frame **metap)
738 CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
739 size_t buflen, size_t *sent,
740 curl_off_t framesize,
741 unsigned int sendflags)
752 CURL_EXTERN struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
757 #endif /* USE_WEBSOCKETS */