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(CURL_DISABLE_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 "select.h"
41 #include "nonblock.h"
42
43 /* The last 3 #include files should be in this order */
44 #include "curl_printf.h"
45 #include "curl_memory.h"
46 #include "memdebug.h"
47
48
49 #define WSBIT_FIN 0x80
50 #define WSBIT_OPCODE_CONT 0
51 #define WSBIT_OPCODE_TEXT (1)
52 #define WSBIT_OPCODE_BIN (2)
53 #define WSBIT_OPCODE_CLOSE (8)
54 #define WSBIT_OPCODE_PING (9)
55 #define WSBIT_OPCODE_PONG (0xa)
56 #define WSBIT_OPCODE_MASK (0xf)
57
58 #define WSBIT_MASK 0x80
59
60 /* buffer dimensioning */
61 #define WS_CHUNK_SIZE 65535
62 #define WS_CHUNK_COUNT 2
63
64 struct ws_frame_meta {
65 char proto_opcode;
66 int flags;
67 const char *name;
68 };
69
70 static struct ws_frame_meta WS_FRAMES[] = {
71 { WSBIT_OPCODE_CONT, CURLWS_CONT, "CONT" },
72 { WSBIT_OPCODE_TEXT, CURLWS_TEXT, "TEXT" },
73 { WSBIT_OPCODE_BIN, CURLWS_BINARY, "BIN" },
74 { WSBIT_OPCODE_CLOSE, CURLWS_CLOSE, "CLOSE" },
75 { WSBIT_OPCODE_PING, CURLWS_PING, "PING" },
76 { WSBIT_OPCODE_PONG, CURLWS_PONG, "PONG" },
77 };
78
ws_frame_name_of_op(unsigned char proto_opcode)79 static const char *ws_frame_name_of_op(unsigned char proto_opcode)
80 {
81 unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK;
82 size_t i;
83 for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) {
84 if(WS_FRAMES[i].proto_opcode == opcode)
85 return WS_FRAMES[i].name;
86 }
87 return "???";
88 }
89
ws_frame_op2flags(unsigned char proto_opcode)90 static int ws_frame_op2flags(unsigned char proto_opcode)
91 {
92 unsigned char opcode = proto_opcode & WSBIT_OPCODE_MASK;
93 size_t i;
94 for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) {
95 if(WS_FRAMES[i].proto_opcode == opcode)
96 return WS_FRAMES[i].flags;
97 }
98 return 0;
99 }
100
ws_frame_flags2op(int flags)101 static unsigned char ws_frame_flags2op(int flags)
102 {
103 size_t i;
104 for(i = 0; i < sizeof(WS_FRAMES)/sizeof(WS_FRAMES[0]); ++i) {
105 if(WS_FRAMES[i].flags & flags)
106 return (unsigned char)WS_FRAMES[i].proto_opcode;
107 }
108 return 0;
109 }
110
ws_dec_info(struct ws_decoder * dec,struct Curl_easy * data,const char * msg)111 static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data,
112 const char *msg)
113 {
114 switch(dec->head_len) {
115 case 0:
116 break;
117 case 1:
118 CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s]", msg,
119 ws_frame_name_of_op(dec->head[0]),
120 (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL");
121 break;
122 default:
123 if(dec->head_len < dec->head_total) {
124 CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s](%d/%d)", msg,
125 ws_frame_name_of_op(dec->head[0]),
126 (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
127 dec->head_len, dec->head_total);
128 }
129 else {
130 CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s payload=%"
131 FMT_OFF_T "/%" FMT_OFF_T "]",
132 msg, ws_frame_name_of_op(dec->head[0]),
133 (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
134 dec->payload_offset, dec->payload_len);
135 }
136 break;
137 }
138 }
139
140 static CURLcode ws_send_raw_blocking(CURL *data, struct websocket *ws,
141 const char *buffer, size_t buflen);
142
143 typedef ssize_t ws_write_payload(const unsigned char *buf, size_t buflen,
144 int frame_age, int frame_flags,
145 curl_off_t payload_offset,
146 curl_off_t payload_len,
147 void *userp,
148 CURLcode *err);
149
150
ws_dec_reset(struct ws_decoder * dec)151 static void ws_dec_reset(struct ws_decoder *dec)
152 {
153 dec->frame_age = 0;
154 dec->frame_flags = 0;
155 dec->payload_offset = 0;
156 dec->payload_len = 0;
157 dec->head_len = dec->head_total = 0;
158 dec->state = WS_DEC_INIT;
159 }
160
ws_dec_init(struct ws_decoder * dec)161 static void ws_dec_init(struct ws_decoder *dec)
162 {
163 ws_dec_reset(dec);
164 }
165
ws_dec_read_head(struct ws_decoder * dec,struct Curl_easy * data,struct bufq * inraw)166 static CURLcode ws_dec_read_head(struct ws_decoder *dec,
167 struct Curl_easy *data,
168 struct bufq *inraw)
169 {
170 const unsigned char *inbuf;
171 size_t inlen;
172
173 while(Curl_bufq_peek(inraw, &inbuf, &inlen)) {
174 if(dec->head_len == 0) {
175 dec->head[0] = *inbuf;
176 Curl_bufq_skip(inraw, 1);
177
178 dec->frame_flags = ws_frame_op2flags(dec->head[0]);
179 if(!dec->frame_flags) {
180 failf(data, "WS: unknown opcode: %x", dec->head[0]);
181 ws_dec_reset(dec);
182 return CURLE_RECV_ERROR;
183 }
184 dec->head_len = 1;
185 /* ws_dec_info(dec, data, "seeing opcode"); */
186 continue;
187 }
188 else if(dec->head_len == 1) {
189 dec->head[1] = *inbuf;
190 Curl_bufq_skip(inraw, 1);
191 dec->head_len = 2;
192
193 if(dec->head[1] & WSBIT_MASK) {
194 /* A client MUST close a connection if it detects a masked frame. */
195 failf(data, "WS: masked input frame");
196 ws_dec_reset(dec);
197 return CURLE_RECV_ERROR;
198 }
199 /* How long is the frame head? */
200 if(dec->head[1] == 126) {
201 dec->head_total = 4;
202 continue;
203 }
204 else if(dec->head[1] == 127) {
205 dec->head_total = 10;
206 continue;
207 }
208 else {
209 dec->head_total = 2;
210 }
211 }
212
213 if(dec->head_len < dec->head_total) {
214 dec->head[dec->head_len] = *inbuf;
215 Curl_bufq_skip(inraw, 1);
216 ++dec->head_len;
217 if(dec->head_len < dec->head_total) {
218 /* ws_dec_info(dec, data, "decoding head"); */
219 continue;
220 }
221 }
222 /* got the complete frame head */
223 DEBUGASSERT(dec->head_len == dec->head_total);
224 switch(dec->head_total) {
225 case 2:
226 dec->payload_len = dec->head[1];
227 break;
228 case 4:
229 dec->payload_len = (dec->head[2] << 8) | dec->head[3];
230 break;
231 case 10:
232 if(dec->head[2] > 127) {
233 failf(data, "WS: frame length longer than 64 signed not supported");
234 return CURLE_RECV_ERROR;
235 }
236 dec->payload_len = ((curl_off_t)dec->head[2] << 56) |
237 (curl_off_t)dec->head[3] << 48 |
238 (curl_off_t)dec->head[4] << 40 |
239 (curl_off_t)dec->head[5] << 32 |
240 (curl_off_t)dec->head[6] << 24 |
241 (curl_off_t)dec->head[7] << 16 |
242 (curl_off_t)dec->head[8] << 8 |
243 dec->head[9];
244 break;
245 default:
246 /* this should never happen */
247 DEBUGASSERT(0);
248 failf(data, "WS: unexpected frame header length");
249 return CURLE_RECV_ERROR;
250 }
251
252 dec->frame_age = 0;
253 dec->payload_offset = 0;
254 ws_dec_info(dec, data, "decoded");
255 return CURLE_OK;
256 }
257 return CURLE_AGAIN;
258 }
259
ws_dec_pass_payload(struct ws_decoder * dec,struct Curl_easy * data,struct bufq * inraw,ws_write_payload * write_payload,void * write_ctx)260 static CURLcode ws_dec_pass_payload(struct ws_decoder *dec,
261 struct Curl_easy *data,
262 struct bufq *inraw,
263 ws_write_payload *write_payload,
264 void *write_ctx)
265 {
266 const unsigned char *inbuf;
267 size_t inlen;
268 ssize_t nwritten;
269 CURLcode result;
270 curl_off_t remain = dec->payload_len - dec->payload_offset;
271
272 (void)data;
273 while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen)) {
274 if((curl_off_t)inlen > remain)
275 inlen = (size_t)remain;
276 nwritten = write_payload(inbuf, inlen, dec->frame_age, dec->frame_flags,
277 dec->payload_offset, dec->payload_len,
278 write_ctx, &result);
279 if(nwritten < 0)
280 return result;
281 Curl_bufq_skip(inraw, (size_t)nwritten);
282 dec->payload_offset += (curl_off_t)nwritten;
283 remain = dec->payload_len - dec->payload_offset;
284 CURL_TRC_WRITE(data, "websocket, passed %zd bytes payload, %"
285 FMT_OFF_T " remain", nwritten, remain);
286 }
287
288 return remain ? CURLE_AGAIN : CURLE_OK;
289 }
290
ws_dec_pass(struct ws_decoder * dec,struct Curl_easy * data,struct bufq * inraw,ws_write_payload * write_payload,void * write_ctx)291 static CURLcode ws_dec_pass(struct ws_decoder *dec,
292 struct Curl_easy *data,
293 struct bufq *inraw,
294 ws_write_payload *write_payload,
295 void *write_ctx)
296 {
297 CURLcode result;
298
299 if(Curl_bufq_is_empty(inraw))
300 return CURLE_AGAIN;
301
302 switch(dec->state) {
303 case WS_DEC_INIT:
304 ws_dec_reset(dec);
305 dec->state = WS_DEC_HEAD;
306 FALLTHROUGH();
307 case WS_DEC_HEAD:
308 result = ws_dec_read_head(dec, data, inraw);
309 if(result) {
310 if(result != CURLE_AGAIN) {
311 infof(data, "WS: decode error %d", (int)result);
312 break; /* real error */
313 }
314 /* incomplete ws frame head */
315 DEBUGASSERT(Curl_bufq_is_empty(inraw));
316 break;
317 }
318 /* head parsing done */
319 dec->state = WS_DEC_PAYLOAD;
320 if(dec->payload_len == 0) {
321 ssize_t nwritten;
322 const unsigned char tmp = '\0';
323 /* special case of a 0 length frame, need to write once */
324 nwritten = write_payload(&tmp, 0, dec->frame_age, dec->frame_flags,
325 0, 0, write_ctx, &result);
326 if(nwritten < 0)
327 return result;
328 dec->state = WS_DEC_INIT;
329 break;
330 }
331 FALLTHROUGH();
332 case WS_DEC_PAYLOAD:
333 result = ws_dec_pass_payload(dec, data, inraw, write_payload, write_ctx);
334 ws_dec_info(dec, data, "passing");
335 if(result)
336 return result;
337 /* paylod parsing done */
338 dec->state = WS_DEC_INIT;
339 break;
340 default:
341 /* we covered all enums above, but some code analyzers are whimps */
342 result = CURLE_FAILED_INIT;
343 }
344 return result;
345 }
346
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)347 static void update_meta(struct websocket *ws,
348 int frame_age, int frame_flags,
349 curl_off_t payload_offset,
350 curl_off_t payload_len,
351 size_t cur_len)
352 {
353 ws->frame.age = frame_age;
354 ws->frame.flags = frame_flags;
355 ws->frame.offset = payload_offset;
356 ws->frame.len = cur_len;
357 ws->frame.bytesleft = (payload_len - payload_offset - cur_len);
358 }
359
360 /* WebSockets decoding client writer */
361 struct ws_cw_ctx {
362 struct Curl_cwriter super;
363 struct bufq buf;
364 };
365
ws_cw_init(struct Curl_easy * data,struct Curl_cwriter * writer)366 static CURLcode ws_cw_init(struct Curl_easy *data,
367 struct Curl_cwriter *writer)
368 {
369 struct ws_cw_ctx *ctx = writer->ctx;
370 (void)data;
371 Curl_bufq_init2(&ctx->buf, WS_CHUNK_SIZE, 1, BUFQ_OPT_SOFT_LIMIT);
372 return CURLE_OK;
373 }
374
ws_cw_close(struct Curl_easy * data,struct Curl_cwriter * writer)375 static void ws_cw_close(struct Curl_easy *data, struct Curl_cwriter *writer)
376 {
377 struct ws_cw_ctx *ctx = writer->ctx;
378 (void) data;
379 Curl_bufq_free(&ctx->buf);
380 }
381
382 struct ws_cw_dec_ctx {
383 struct Curl_easy *data;
384 struct websocket *ws;
385 struct Curl_cwriter *next_writer;
386 int cw_type;
387 };
388
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)389 static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen,
390 int frame_age, int frame_flags,
391 curl_off_t payload_offset,
392 curl_off_t payload_len,
393 void *user_data,
394 CURLcode *err)
395 {
396 struct ws_cw_dec_ctx *ctx = user_data;
397 struct Curl_easy *data = ctx->data;
398 struct websocket *ws = ctx->ws;
399 curl_off_t remain = (payload_len - (payload_offset + buflen));
400
401 (void)frame_age;
402 if((frame_flags & CURLWS_PING) && !remain) {
403 /* auto-respond to PINGs, only works for single-frame payloads atm */
404 size_t bytes;
405 infof(data, "WS: auto-respond to PING with a PONG");
406 /* send back the exact same content as a PONG */
407 *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG);
408 if(*err)
409 return -1;
410 }
411 else if(buflen || !remain) {
412 /* forward the decoded frame to the next client writer. */
413 update_meta(ws, frame_age, frame_flags, payload_offset,
414 payload_len, buflen);
415
416 *err = Curl_cwriter_write(data, ctx->next_writer, ctx->cw_type,
417 (const char *)buf, buflen);
418 if(*err)
419 return -1;
420 }
421 *err = CURLE_OK;
422 return (ssize_t)buflen;
423 }
424
ws_cw_write(struct Curl_easy * data,struct Curl_cwriter * writer,int type,const char * buf,size_t nbytes)425 static CURLcode ws_cw_write(struct Curl_easy *data,
426 struct Curl_cwriter *writer, int type,
427 const char *buf, size_t nbytes)
428 {
429 struct ws_cw_ctx *ctx = writer->ctx;
430 struct websocket *ws;
431 CURLcode result;
432
433 if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode)
434 return Curl_cwriter_write(data, writer->next, type, buf, nbytes);
435
436 ws = data->conn->proto.ws;
437 if(!ws) {
438 failf(data, "WS: not a websocket transfer");
439 return CURLE_FAILED_INIT;
440 }
441
442 if(nbytes) {
443 ssize_t nwritten;
444 nwritten = Curl_bufq_write(&ctx->buf, (const unsigned char *)buf,
445 nbytes, &result);
446 if(nwritten < 0) {
447 infof(data, "WS: error adding data to buffer %d", result);
448 return result;
449 }
450 }
451
452 while(!Curl_bufq_is_empty(&ctx->buf)) {
453 struct ws_cw_dec_ctx pass_ctx;
454 pass_ctx.data = data;
455 pass_ctx.ws = ws;
456 pass_ctx.next_writer = writer->next;
457 pass_ctx.cw_type = type;
458 result = ws_dec_pass(&ws->dec, data, &ctx->buf,
459 ws_cw_dec_next, &pass_ctx);
460 if(result == CURLE_AGAIN) {
461 /* insufficient amount of data, keep it for later.
462 * we pretend to have written all since we have a copy */
463 CURL_TRC_WRITE(data, "websocket, buffered incomplete frame head");
464 return CURLE_OK;
465 }
466 else if(result) {
467 infof(data, "WS: decode error %d", (int)result);
468 return result;
469 }
470 }
471
472 if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) {
473 infof(data, "WS: decode ending with %zd frame bytes remaining",
474 Curl_bufq_len(&ctx->buf));
475 return CURLE_RECV_ERROR;
476 }
477
478 return CURLE_OK;
479 }
480
481 /* WebSocket payload decoding client writer. */
482 static const struct Curl_cwtype ws_cw_decode = {
483 "ws-decode",
484 NULL,
485 ws_cw_init,
486 ws_cw_write,
487 ws_cw_close,
488 sizeof(struct ws_cw_ctx)
489 };
490
491
ws_enc_info(struct ws_encoder * enc,struct Curl_easy * data,const char * msg)492 static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data,
493 const char *msg)
494 {
495 infof(data, "WS-ENC: %s [%s%s%s payload=%" FMT_OFF_T "/%" FMT_OFF_T "]",
496 msg, ws_frame_name_of_op(enc->firstbyte),
497 (enc->firstbyte & WSBIT_OPCODE_MASK) == WSBIT_OPCODE_CONT ?
498 " CONT" : "",
499 (enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN",
500 enc->payload_len - enc->payload_remain, enc->payload_len);
501 }
502
ws_enc_reset(struct ws_encoder * enc)503 static void ws_enc_reset(struct ws_encoder *enc)
504 {
505 enc->payload_remain = 0;
506 enc->xori = 0;
507 enc->contfragment = FALSE;
508 }
509
ws_enc_init(struct ws_encoder * enc)510 static void ws_enc_init(struct ws_encoder *enc)
511 {
512 ws_enc_reset(enc);
513 }
514
515 /***
516 RFC 6455 Section 5.2
517
518 0 1 2 3
519 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
520 +-+-+-+-+-------+-+-------------+-------------------------------+
521 |F|R|R|R| opcode|M| Payload len | Extended payload length |
522 |I|S|S|S| (4) |A| (7) | (16/64) |
523 |N|V|V|V| |S| | (if payload len==126/127) |
524 | |1|2|3| |K| | |
525 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
526 | Extended payload length continued, if payload len == 127 |
527 + - - - - - - - - - - - - - - - +-------------------------------+
528 | |Masking-key, if MASK set to 1 |
529 +-------------------------------+-------------------------------+
530 | Masking-key (continued) | Payload Data |
531 +-------------------------------- - - - - - - - - - - - - - - - +
532 : Payload Data continued ... :
533 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
534 | Payload Data continued ... |
535 +---------------------------------------------------------------+
536 */
537
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)538 static ssize_t ws_enc_write_head(struct Curl_easy *data,
539 struct ws_encoder *enc,
540 unsigned int flags,
541 curl_off_t payload_len,
542 struct bufq *out,
543 CURLcode *err)
544 {
545 unsigned char firstbyte = 0;
546 unsigned char opcode;
547 unsigned char head[14];
548 size_t hlen;
549 ssize_t n;
550
551 if(payload_len < 0) {
552 failf(data, "WS: starting new frame with negative payload length %"
553 FMT_OFF_T, payload_len);
554 *err = CURLE_SEND_ERROR;
555 return -1;
556 }
557
558 if(enc->payload_remain > 0) {
559 /* trying to write a new frame before the previous one is finished */
560 failf(data, "WS: starting new frame with %zd bytes from last one "
561 "remaining to be sent", (ssize_t)enc->payload_remain);
562 *err = CURLE_SEND_ERROR;
563 return -1;
564 }
565
566 opcode = ws_frame_flags2op((int)flags & ~CURLWS_CONT);
567 if(!opcode) {
568 failf(data, "WS: provided flags not recognized '%x'", flags);
569 *err = CURLE_SEND_ERROR;
570 return -1;
571 }
572
573 if(!(flags & CURLWS_CONT)) {
574 if(!enc->contfragment)
575 /* not marked as continuing, this is the final fragment */
576 firstbyte |= WSBIT_FIN | opcode;
577 else
578 /* marked as continuing, this is the final fragment; set CONT
579 opcode and FIN bit */
580 firstbyte |= WSBIT_FIN | WSBIT_OPCODE_CONT;
581
582 enc->contfragment = FALSE;
583 }
584 else if(enc->contfragment) {
585 /* the previous fragment was not a final one and this is not either, keep a
586 CONT opcode and no FIN bit */
587 firstbyte |= WSBIT_OPCODE_CONT;
588 }
589 else {
590 firstbyte = opcode;
591 enc->contfragment = TRUE;
592 }
593
594 head[0] = enc->firstbyte = firstbyte;
595 if(payload_len > 65535) {
596 head[1] = 127 | WSBIT_MASK;
597 head[2] = (unsigned char)((payload_len >> 56) & 0xff);
598 head[3] = (unsigned char)((payload_len >> 48) & 0xff);
599 head[4] = (unsigned char)((payload_len >> 40) & 0xff);
600 head[5] = (unsigned char)((payload_len >> 32) & 0xff);
601 head[6] = (unsigned char)((payload_len >> 24) & 0xff);
602 head[7] = (unsigned char)((payload_len >> 16) & 0xff);
603 head[8] = (unsigned char)((payload_len >> 8) & 0xff);
604 head[9] = (unsigned char)(payload_len & 0xff);
605 hlen = 10;
606 }
607 else if(payload_len >= 126) {
608 head[1] = 126 | WSBIT_MASK;
609 head[2] = (unsigned char)((payload_len >> 8) & 0xff);
610 head[3] = (unsigned char)(payload_len & 0xff);
611 hlen = 4;
612 }
613 else {
614 head[1] = (unsigned char)payload_len | WSBIT_MASK;
615 hlen = 2;
616 }
617
618 enc->payload_remain = enc->payload_len = payload_len;
619 ws_enc_info(enc, data, "sending");
620
621 /* add 4 bytes mask */
622 memcpy(&head[hlen], &enc->mask, 4);
623 hlen += 4;
624 /* reset for payload to come */
625 enc->xori = 0;
626
627 n = Curl_bufq_write(out, head, hlen, err);
628 if(n < 0)
629 return -1;
630 if((size_t)n != hlen) {
631 /* We use a bufq with SOFT_LIMIT, writing should always succeed */
632 DEBUGASSERT(0);
633 *err = CURLE_SEND_ERROR;
634 return -1;
635 }
636 return n;
637 }
638
ws_enc_write_payload(struct ws_encoder * enc,struct Curl_easy * data,const unsigned char * buf,size_t buflen,struct bufq * out,CURLcode * err)639 static ssize_t ws_enc_write_payload(struct ws_encoder *enc,
640 struct Curl_easy *data,
641 const unsigned char *buf, size_t buflen,
642 struct bufq *out, CURLcode *err)
643 {
644 ssize_t n;
645 size_t i, len;
646
647 if(Curl_bufq_is_full(out)) {
648 *err = CURLE_AGAIN;
649 return -1;
650 }
651
652 /* not the most performant way to do this */
653 len = buflen;
654 if((curl_off_t)len > enc->payload_remain)
655 len = (size_t)enc->payload_remain;
656
657 for(i = 0; i < len; ++i) {
658 unsigned char c = buf[i] ^ enc->mask[enc->xori];
659 n = Curl_bufq_write(out, &c, 1, err);
660 if(n < 0) {
661 if((*err != CURLE_AGAIN) || !i)
662 return -1;
663 break;
664 }
665 enc->xori++;
666 enc->xori &= 3;
667 }
668 enc->payload_remain -= (curl_off_t)i;
669 ws_enc_info(enc, data, "buffered");
670 return (ssize_t)i;
671 }
672
673
674 struct wsfield {
675 const char *name;
676 const char *val;
677 };
678
Curl_ws_request(struct Curl_easy * data,REQTYPE * req)679 CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req)
680 {
681 unsigned int i;
682 CURLcode result = CURLE_OK;
683 unsigned char rand[16];
684 char *randstr;
685 size_t randlen;
686 char keyval[40];
687 struct SingleRequest *k = &data->req;
688 struct wsfield heads[]= {
689 {
690 /* The request MUST contain an |Upgrade| header field whose value
691 MUST include the "websocket" keyword. */
692 "Upgrade:", "websocket"
693 },
694 {
695 /* The request MUST contain a |Connection| header field whose value
696 MUST include the "Upgrade" token. */
697 "Connection:", "Upgrade",
698 },
699 {
700 /* The request MUST include a header field with the name
701 |Sec-WebSocket-Version|. The value of this header field MUST be
702 13. */
703 "Sec-WebSocket-Version:", "13",
704 },
705 {
706 /* The request MUST include a header field with the name
707 |Sec-WebSocket-Key|. The value of this header field MUST be a nonce
708 consisting of a randomly selected 16-byte value that has been
709 base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be
710 selected randomly for each connection. */
711 "Sec-WebSocket-Key:", NULL,
712 }
713 };
714 heads[3].val = &keyval[0];
715
716 /* 16 bytes random */
717 result = Curl_rand(data, (unsigned char *)rand, sizeof(rand));
718 if(result)
719 return result;
720 result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen);
721 if(result)
722 return result;
723 DEBUGASSERT(randlen < sizeof(keyval));
724 if(randlen >= sizeof(keyval)) {
725 free(randstr);
726 return CURLE_FAILED_INIT;
727 }
728 strcpy(keyval, randstr);
729 free(randstr);
730 for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) {
731 if(!Curl_checkheaders(data, STRCONST(heads[i].name))) {
732 #ifdef USE_HYPER
733 char field[128];
734 msnprintf(field, sizeof(field), "%s %s", heads[i].name,
735 heads[i].val);
736 result = Curl_hyper_header(data, req, field);
737 #else
738 (void)data;
739 result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name,
740 heads[i].val);
741 #endif
742 }
743 }
744 k->upgr101 = UPGR101_WS;
745 return result;
746 }
747
748 /*
749 * 'nread' is number of bytes of websocket data already in the buffer at
750 * 'mem'.
751 */
Curl_ws_accept(struct Curl_easy * data,const char * mem,size_t nread)752 CURLcode Curl_ws_accept(struct Curl_easy *data,
753 const char *mem, size_t nread)
754 {
755 struct SingleRequest *k = &data->req;
756 struct websocket *ws;
757 struct Curl_cwriter *ws_dec_writer;
758 CURLcode result;
759
760 DEBUGASSERT(data->conn);
761 ws = data->conn->proto.ws;
762 if(!ws) {
763 size_t chunk_size = WS_CHUNK_SIZE;
764 ws = calloc(1, sizeof(*ws));
765 if(!ws)
766 return CURLE_OUT_OF_MEMORY;
767 data->conn->proto.ws = ws;
768 #ifdef DEBUGBUILD
769 {
770 char *p = getenv("CURL_WS_CHUNK_SIZE");
771 if(p) {
772 long l = strtol(p, NULL, 10);
773 if(l > 0 && l <= (1*1024*1024)) {
774 chunk_size = (size_t)l;
775 }
776 }
777 }
778 #endif
779 CURL_TRC_WS(data, "WS, using chunk size %zu", chunk_size);
780 Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT,
781 BUFQ_OPT_SOFT_LIMIT);
782 Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT,
783 BUFQ_OPT_SOFT_LIMIT);
784 ws_dec_init(&ws->dec);
785 ws_enc_init(&ws->enc);
786 }
787 else {
788 Curl_bufq_reset(&ws->recvbuf);
789 ws_dec_reset(&ws->dec);
790 ws_enc_reset(&ws->enc);
791 }
792 /* Verify the Sec-WebSocket-Accept response.
793
794 The sent value is the base64 encoded version of a SHA-1 hash done on the
795 |Sec-WebSocket-Key| header field concatenated with
796 the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".
797 */
798
799 /* If the response includes a |Sec-WebSocket-Extensions| header field and
800 this header field indicates the use of an extension that was not present
801 in the client's handshake (the server has indicated an extension not
802 requested by the client), the client MUST Fail the WebSocket Connection.
803 */
804
805 /* If the response includes a |Sec-WebSocket-Protocol| header field
806 and this header field indicates the use of a subprotocol that was
807 not present in the client's handshake (the server has indicated a
808 subprotocol not requested by the client), the client MUST Fail
809 the WebSocket Connection. */
810
811 /* 4 bytes random */
812
813 result = Curl_rand(data, (unsigned char *)&ws->enc.mask,
814 sizeof(ws->enc.mask));
815 if(result)
816 return result;
817 infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
818 ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]);
819
820 /* Install our client writer that decodes WS frames payload */
821 result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode,
822 CURL_CW_CONTENT_DECODE);
823 if(result)
824 return result;
825
826 result = Curl_cwriter_add(data, ws_dec_writer);
827 if(result) {
828 Curl_cwriter_free(data, ws_dec_writer);
829 return result;
830 }
831
832 if(data->set.connect_only) {
833 ssize_t nwritten;
834 /* In CONNECT_ONLY setup, the payloads from `mem` need to be received
835 * when using `curl_ws_recv` later on after this transfer is already
836 * marked as DONE. */
837 nwritten = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)mem,
838 nread, &result);
839 if(nwritten < 0)
840 return result;
841 infof(data, "%zu bytes websocket payload", nread);
842 }
843 else { /* !connect_only */
844 /* And pass any additional data to the writers */
845 if(nread) {
846 result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)mem, nread);
847 }
848 }
849 k->upgr101 = UPGR101_RECEIVED;
850
851 return result;
852 }
853
854 struct ws_collect {
855 struct Curl_easy *data;
856 unsigned char *buffer;
857 size_t buflen;
858 size_t bufidx;
859 int frame_age;
860 int frame_flags;
861 curl_off_t payload_offset;
862 curl_off_t payload_len;
863 bool written;
864 };
865
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)866 static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen,
867 int frame_age, int frame_flags,
868 curl_off_t payload_offset,
869 curl_off_t payload_len,
870 void *userp,
871 CURLcode *err)
872 {
873 struct ws_collect *ctx = userp;
874 size_t nwritten;
875 curl_off_t remain = (payload_len - (payload_offset + buflen));
876
877 if(!ctx->bufidx) {
878 /* first write */
879 ctx->frame_age = frame_age;
880 ctx->frame_flags = frame_flags;
881 ctx->payload_offset = payload_offset;
882 ctx->payload_len = payload_len;
883 }
884
885 if((frame_flags & CURLWS_PING) && !remain) {
886 /* auto-respond to PINGs, only works for single-frame payloads atm */
887 size_t bytes;
888 infof(ctx->data, "WS: auto-respond to PING with a PONG");
889 /* send back the exact same content as a PONG */
890 *err = curl_ws_send(ctx->data, buf, buflen, &bytes, 0, CURLWS_PONG);
891 if(*err)
892 return -1;
893 nwritten = bytes;
894 }
895 else {
896 ctx->written = TRUE;
897 DEBUGASSERT(ctx->buflen >= ctx->bufidx);
898 nwritten = CURLMIN(buflen, ctx->buflen - ctx->bufidx);
899 if(!nwritten) {
900 if(!buflen) { /* 0 length write, we accept that */
901 *err = CURLE_OK;
902 return 0;
903 }
904 *err = CURLE_AGAIN; /* no more space */
905 return -1;
906 }
907 *err = CURLE_OK;
908 memcpy(ctx->buffer + ctx->bufidx, buf, nwritten);
909 ctx->bufidx += nwritten;
910 }
911 return nwritten;
912 }
913
nw_in_recv(void * reader_ctx,unsigned char * buf,size_t buflen,CURLcode * err)914 static ssize_t nw_in_recv(void *reader_ctx,
915 unsigned char *buf, size_t buflen,
916 CURLcode *err)
917 {
918 struct Curl_easy *data = reader_ctx;
919 size_t nread;
920
921 *err = curl_easy_recv(data, buf, buflen, &nread);
922 if(*err)
923 return -1;
924 return (ssize_t)nread;
925 }
926
curl_ws_recv(CURL * d,void * buffer,size_t buflen,size_t * nread,const struct curl_ws_frame ** metap)927 CURL_EXTERN CURLcode curl_ws_recv(CURL *d, void *buffer,
928 size_t buflen, size_t *nread,
929 const struct curl_ws_frame **metap)
930 {
931 struct Curl_easy *data = d;
932 struct connectdata *conn = data->conn;
933 struct websocket *ws;
934 struct ws_collect ctx;
935
936 *nread = 0;
937 *metap = NULL;
938
939 if(!conn) {
940 /* Unhappy hack with lifetimes of transfers and connection */
941 if(!data->set.connect_only) {
942 failf(data, "CONNECT_ONLY is required");
943 return CURLE_UNSUPPORTED_PROTOCOL;
944 }
945
946 Curl_getconnectinfo(data, &conn);
947 if(!conn) {
948 failf(data, "connection not found");
949 return CURLE_BAD_FUNCTION_ARGUMENT;
950 }
951 }
952 ws = conn->proto.ws;
953 if(!ws) {
954 failf(data, "connection is not setup for websocket");
955 return CURLE_BAD_FUNCTION_ARGUMENT;
956 }
957
958
959 memset(&ctx, 0, sizeof(ctx));
960 ctx.data = data;
961 ctx.buffer = buffer;
962 ctx.buflen = buflen;
963
964 while(1) {
965 CURLcode result;
966
967 /* receive more when our buffer is empty */
968 if(Curl_bufq_is_empty(&ws->recvbuf)) {
969 ssize_t n = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &result);
970 if(n < 0) {
971 return result;
972 }
973 else if(n == 0) {
974 /* connection closed */
975 infof(data, "connection expectedly closed?");
976 return CURLE_GOT_NOTHING;
977 }
978 CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network",
979 Curl_bufq_len(&ws->recvbuf));
980 }
981
982 result = ws_dec_pass(&ws->dec, data, &ws->recvbuf,
983 ws_client_collect, &ctx);
984 if(result == CURLE_AGAIN) {
985 if(!ctx.written) {
986 ws_dec_info(&ws->dec, data, "need more input");
987 continue; /* nothing written, try more input */
988 }
989 break;
990 }
991 else if(result) {
992 return result;
993 }
994 else if(ctx.written) {
995 /* The decoded frame is passed back to our caller.
996 * There are frames like PING were we auto-respond to and
997 * that we do not return. For these `ctx.written` is not set. */
998 break;
999 }
1000 }
1001
1002 /* update frame information to be passed back */
1003 update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset,
1004 ctx.payload_len, ctx.bufidx);
1005 *metap = &ws->frame;
1006 *nread = ws->frame.len;
1007 CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %"
1008 FMT_OFF_T ", %" FMT_OFF_T " left)",
1009 buflen, *nread, ws->frame.offset, ws->frame.bytesleft);
1010 return CURLE_OK;
1011 }
1012
ws_flush(struct Curl_easy * data,struct websocket * ws,bool blocking)1013 static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
1014 bool blocking)
1015 {
1016 if(!Curl_bufq_is_empty(&ws->sendbuf)) {
1017 CURLcode result;
1018 const unsigned char *out;
1019 size_t outlen, n;
1020
1021 while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) {
1022 if(blocking) {
1023 result = ws_send_raw_blocking(data, ws, (char *)out, outlen);
1024 n = result ? 0 : outlen;
1025 }
1026 else if(data->set.connect_only || Curl_is_in_callback(data))
1027 result = Curl_senddata(data, out, outlen, &n);
1028 else {
1029 result = Curl_xfer_send(data, out, outlen, FALSE, &n);
1030 if(!result && !n && outlen)
1031 result = CURLE_AGAIN;
1032 }
1033
1034 if(result == CURLE_AGAIN) {
1035 CURL_TRC_WS(data, "flush EAGAIN, %zu bytes remain in buffer",
1036 Curl_bufq_len(&ws->sendbuf));
1037 return result;
1038 }
1039 else if(result) {
1040 failf(data, "WS: flush, write error %d", result);
1041 return result;
1042 }
1043 else {
1044 infof(data, "WS: flushed %zu bytes", n);
1045 Curl_bufq_skip(&ws->sendbuf, n);
1046 }
1047 }
1048 }
1049 return CURLE_OK;
1050 }
1051
ws_send_raw_blocking(CURL * d,struct websocket * ws,const char * buffer,size_t buflen)1052 static CURLcode ws_send_raw_blocking(CURL *d, struct websocket *ws,
1053 const char *buffer, size_t buflen)
1054 {
1055 CURLcode result = CURLE_OK;
1056 size_t nwritten;
1057 struct Curl_easy *data = d;
1058
1059 (void)ws;
1060 while(buflen) {
1061 result = Curl_xfer_send(data, buffer, buflen, FALSE, &nwritten);
1062 if(result)
1063 return result;
1064 DEBUGASSERT(nwritten <= buflen);
1065 buffer += nwritten;
1066 buflen -= nwritten;
1067 if(buflen) {
1068 curl_socket_t sock = data->conn->sock[FIRSTSOCKET];
1069 timediff_t left_ms;
1070 int ev;
1071
1072 CURL_TRC_WS(data, "ws_send_raw_blocking() partial, %zu left to send",
1073 buflen);
1074 left_ms = Curl_timeleft(data, NULL, FALSE);
1075 if(left_ms < 0) {
1076 failf(data, "Timeout waiting for socket becoming writable");
1077 return CURLE_SEND_ERROR;
1078 }
1079
1080 /* POLLOUT socket */
1081 if(sock == CURL_SOCKET_BAD)
1082 return CURLE_SEND_ERROR;
1083 ev = Curl_socket_check(CURL_SOCKET_BAD, CURL_SOCKET_BAD, sock,
1084 left_ms ? left_ms : 500);
1085 if(ev < 0) {
1086 failf(data, "Error while waiting for socket becoming writable");
1087 return CURLE_SEND_ERROR;
1088 }
1089 }
1090 }
1091 return result;
1092 }
1093
ws_send_raw(struct Curl_easy * data,const void * buffer,size_t buflen,size_t * pnwritten)1094 static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer,
1095 size_t buflen, size_t *pnwritten)
1096 {
1097 struct websocket *ws = data->conn->proto.ws;
1098 CURLcode result;
1099
1100 if(!ws) {
1101 failf(data, "Not a websocket transfer");
1102 return CURLE_SEND_ERROR;
1103 }
1104 if(!buflen)
1105 return CURLE_OK;
1106
1107 if(Curl_is_in_callback(data)) {
1108 /* When invoked from inside callbacks, we do a blocking send as the
1109 * callback will probably not implement partial writes that may then
1110 * mess up the ws framing subsequently.
1111 * We need any pending data to be flushed before sending. */
1112 result = ws_flush(data, ws, TRUE);
1113 if(result)
1114 return result;
1115 result = ws_send_raw_blocking(data, ws, buffer, buflen);
1116 }
1117 else {
1118 /* We need any pending data to be sent or EAGAIN this call. */
1119 result = ws_flush(data, ws, FALSE);
1120 if(result)
1121 return result;
1122 result = Curl_senddata(data, buffer, buflen, pnwritten);
1123 }
1124
1125 CURL_TRC_WS(data, "ws_send_raw(len=%zu) -> %d, %zu",
1126 buflen, result, *pnwritten);
1127 return result;
1128 }
1129
curl_ws_send(CURL * d,const void * buffer,size_t buflen,size_t * sent,curl_off_t fragsize,unsigned int flags)1130 CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer,
1131 size_t buflen, size_t *sent,
1132 curl_off_t fragsize,
1133 unsigned int flags)
1134 {
1135 struct websocket *ws;
1136 ssize_t n;
1137 size_t space, payload_added;
1138 CURLcode result;
1139 struct Curl_easy *data = d;
1140
1141 CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T
1142 ", flags=%x), raw=%d",
1143 buflen, fragsize, flags, data->set.ws_raw_mode);
1144 *sent = 0;
1145 if(!data->conn && data->set.connect_only) {
1146 result = Curl_connect_only_attach(data);
1147 if(result)
1148 goto out;
1149 }
1150 if(!data->conn) {
1151 failf(data, "No associated connection");
1152 result = CURLE_SEND_ERROR;
1153 goto out;
1154 }
1155 if(!data->conn->proto.ws) {
1156 failf(data, "Not a websocket transfer");
1157 result = CURLE_SEND_ERROR;
1158 goto out;
1159 }
1160 ws = data->conn->proto.ws;
1161
1162 /* try flushing any content still waiting to be sent. */
1163 result = ws_flush(data, ws, FALSE);
1164 if(result)
1165 goto out;
1166
1167 if(data->set.ws_raw_mode) {
1168 /* In raw mode, we write directly to the connection */
1169 if(fragsize || flags) {
1170 failf(data, "ws_send, raw mode: fragsize and flags cannot be non-zero");
1171 return CURLE_BAD_FUNCTION_ARGUMENT;
1172 }
1173 result = ws_send_raw(data, buffer, buflen, sent);
1174 goto out;
1175 }
1176
1177 /* Not RAW mode, buf we do the frame encoding */
1178 space = Curl_bufq_space(&ws->sendbuf);
1179 CURL_TRC_WS(data, "curl_ws_send(len=%zu), sendbuf=%zu space_left=%zu",
1180 buflen, Curl_bufq_len(&ws->sendbuf), space);
1181 if(space < 14) {
1182 result = CURLE_AGAIN;
1183 goto out;
1184 }
1185
1186 if(flags & CURLWS_OFFSET) {
1187 if(fragsize) {
1188 /* a frame series 'fragsize' bytes big, this is the first */
1189 n = ws_enc_write_head(data, &ws->enc, flags, fragsize,
1190 &ws->sendbuf, &result);
1191 if(n < 0)
1192 goto out;
1193 }
1194 else {
1195 if((curl_off_t)buflen > ws->enc.payload_remain) {
1196 infof(data, "WS: unaligned frame size (sending %zu instead of %"
1197 FMT_OFF_T ")",
1198 buflen, ws->enc.payload_remain);
1199 }
1200 }
1201 }
1202 else if(!ws->enc.payload_remain) {
1203 n = ws_enc_write_head(data, &ws->enc, flags, (curl_off_t)buflen,
1204 &ws->sendbuf, &result);
1205 if(n < 0)
1206 goto out;
1207 }
1208
1209 n = ws_enc_write_payload(&ws->enc, data,
1210 buffer, buflen, &ws->sendbuf, &result);
1211 if(n < 0)
1212 goto out;
1213 payload_added = (size_t)n;
1214
1215 while(!result && (buflen || !Curl_bufq_is_empty(&ws->sendbuf))) {
1216 /* flush, blocking when in callback */
1217 result = ws_flush(data, ws, Curl_is_in_callback(data));
1218 if(!result) {
1219 DEBUGASSERT(payload_added <= buflen);
1220 /* all buffered data sent. Try sending the rest if there is any. */
1221 *sent += payload_added;
1222 buffer = (const char *)buffer + payload_added;
1223 buflen -= payload_added;
1224 payload_added = 0;
1225 if(buflen) {
1226 n = ws_enc_write_payload(&ws->enc, data,
1227 buffer, buflen, &ws->sendbuf, &result);
1228 if(n < 0)
1229 goto out;
1230 payload_added = Curl_bufq_len(&ws->sendbuf);
1231 }
1232 }
1233 else if(result == CURLE_AGAIN) {
1234 /* partially sent. how much of the call data has been part of it? what
1235 * should we report to out caller so it can retry/send the rest? */
1236 if(payload_added < buflen) {
1237 /* We did not add everything the caller wanted. Return just
1238 * the partial write to our buffer. */
1239 *sent = payload_added;
1240 result = CURLE_OK;
1241 goto out;
1242 }
1243 else if(!buflen) {
1244 /* We have no payload to report a partial write. EAGAIN would make
1245 * the caller repeat this and add the frame again.
1246 * Flush blocking seems the only way out of this. */
1247 *sent = (size_t)n;
1248 result = ws_flush(data, ws, TRUE);
1249 goto out;
1250 }
1251 /* We added the complete data to our sendbuf. Report one byte less as
1252 * sent. This partial success should make the caller invoke us again
1253 * with the last byte. */
1254 *sent = payload_added - 1;
1255 result = Curl_bufq_unwrite(&ws->sendbuf, 1);
1256 if(!result)
1257 result = CURLE_AGAIN;
1258 }
1259 }
1260
1261 out:
1262 CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T
1263 ", flags=%x, raw=%d) -> %d, %zu",
1264 buflen, fragsize, flags, data->set.ws_raw_mode, result, *sent);
1265 return result;
1266 }
1267
ws_free(struct connectdata * conn)1268 static void ws_free(struct connectdata *conn)
1269 {
1270 if(conn && conn->proto.ws) {
1271 Curl_bufq_free(&conn->proto.ws->recvbuf);
1272 Curl_bufq_free(&conn->proto.ws->sendbuf);
1273 Curl_safefree(conn->proto.ws);
1274 }
1275 }
1276
ws_setup_conn(struct Curl_easy * data,struct connectdata * conn)1277 static CURLcode ws_setup_conn(struct Curl_easy *data,
1278 struct connectdata *conn)
1279 {
1280 /* WebSockets is 1.1 only (for now) */
1281 data->state.httpwant = CURL_HTTP_VERSION_1_1;
1282 return Curl_http_setup_conn(data, conn);
1283 }
1284
1285
ws_disconnect(struct Curl_easy * data,struct connectdata * conn,bool dead_connection)1286 static CURLcode ws_disconnect(struct Curl_easy *data,
1287 struct connectdata *conn,
1288 bool dead_connection)
1289 {
1290 (void)data;
1291 (void)dead_connection;
1292 ws_free(conn);
1293 return CURLE_OK;
1294 }
1295
curl_ws_meta(CURL * d)1296 CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *d)
1297 {
1298 /* we only return something for websocket, called from within the callback
1299 when not using raw mode */
1300 struct Curl_easy *data = d;
1301 if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->conn &&
1302 data->conn->proto.ws && !data->set.ws_raw_mode)
1303 return &data->conn->proto.ws->frame;
1304 return NULL;
1305 }
1306
1307 const struct Curl_handler Curl_handler_ws = {
1308 "WS", /* scheme */
1309 ws_setup_conn, /* setup_connection */
1310 Curl_http, /* do_it */
1311 Curl_http_done, /* done */
1312 ZERO_NULL, /* do_more */
1313 Curl_http_connect, /* connect_it */
1314 ZERO_NULL, /* connecting */
1315 ZERO_NULL, /* doing */
1316 ZERO_NULL, /* proto_getsock */
1317 Curl_http_getsock_do, /* doing_getsock */
1318 ZERO_NULL, /* domore_getsock */
1319 ZERO_NULL, /* perform_getsock */
1320 ws_disconnect, /* disconnect */
1321 Curl_http_write_resp, /* write_resp */
1322 Curl_http_write_resp_hd, /* write_resp_hd */
1323 ZERO_NULL, /* connection_check */
1324 ZERO_NULL, /* attach connection */
1325 PORT_HTTP, /* defport */
1326 CURLPROTO_WS, /* protocol */
1327 CURLPROTO_HTTP, /* family */
1328 PROTOPT_CREDSPERREQUEST | /* flags */
1329 PROTOPT_USERPWDCTRL
1330 };
1331
1332 #ifdef USE_SSL
1333 const struct Curl_handler Curl_handler_wss = {
1334 "WSS", /* scheme */
1335 ws_setup_conn, /* setup_connection */
1336 Curl_http, /* do_it */
1337 Curl_http_done, /* done */
1338 ZERO_NULL, /* do_more */
1339 Curl_http_connect, /* connect_it */
1340 NULL, /* connecting */
1341 ZERO_NULL, /* doing */
1342 NULL, /* proto_getsock */
1343 Curl_http_getsock_do, /* doing_getsock */
1344 ZERO_NULL, /* domore_getsock */
1345 ZERO_NULL, /* perform_getsock */
1346 ws_disconnect, /* disconnect */
1347 Curl_http_write_resp, /* write_resp */
1348 Curl_http_write_resp_hd, /* write_resp_hd */
1349 ZERO_NULL, /* connection_check */
1350 ZERO_NULL, /* attach connection */
1351 PORT_HTTPS, /* defport */
1352 CURLPROTO_WSS, /* protocol */
1353 CURLPROTO_HTTP, /* family */
1354 PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */
1355 PROTOPT_USERPWDCTRL
1356 };
1357 #endif
1358
1359
1360 #else
1361
curl_ws_recv(CURL * curl,void * buffer,size_t buflen,size_t * nread,const struct curl_ws_frame ** metap)1362 CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
1363 size_t *nread,
1364 const struct curl_ws_frame **metap)
1365 {
1366 (void)curl;
1367 (void)buffer;
1368 (void)buflen;
1369 (void)nread;
1370 (void)metap;
1371 return CURLE_NOT_BUILT_IN;
1372 }
1373
curl_ws_send(CURL * curl,const void * buffer,size_t buflen,size_t * sent,curl_off_t fragsize,unsigned int flags)1374 CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
1375 size_t buflen, size_t *sent,
1376 curl_off_t fragsize,
1377 unsigned int flags)
1378 {
1379 (void)curl;
1380 (void)buffer;
1381 (void)buflen;
1382 (void)sent;
1383 (void)fragsize;
1384 (void)flags;
1385 return CURLE_NOT_BUILT_IN;
1386 }
1387
curl_ws_meta(CURL * data)1388 CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *data)
1389 {
1390 (void)data;
1391 return NULL;
1392 }
1393 #endif /* !CURL_DISABLE_WEBSOCKETS */
1394