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