xref: /curl/lib/ws.c (revision d6bae1cb)
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