xref: /curl/lib/ws.c (revision fc3e1cbc)
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,struct dynbuf * req)679 CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *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       result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name,
733                              heads[i].val);
734     }
735   }
736   k->upgr101 = UPGR101_WS;
737   return result;
738 }
739 
740 /*
741  * 'nread' is number of bytes of websocket data already in the buffer at
742  * 'mem'.
743  */
Curl_ws_accept(struct Curl_easy * data,const char * mem,size_t nread)744 CURLcode Curl_ws_accept(struct Curl_easy *data,
745                         const char *mem, size_t nread)
746 {
747   struct SingleRequest *k = &data->req;
748   struct websocket *ws;
749   struct Curl_cwriter *ws_dec_writer;
750   CURLcode result;
751 
752   DEBUGASSERT(data->conn);
753   ws = data->conn->proto.ws;
754   if(!ws) {
755     size_t chunk_size = WS_CHUNK_SIZE;
756     ws = calloc(1, sizeof(*ws));
757     if(!ws)
758       return CURLE_OUT_OF_MEMORY;
759     data->conn->proto.ws = ws;
760 #ifdef DEBUGBUILD
761     {
762       char *p = getenv("CURL_WS_CHUNK_SIZE");
763       if(p) {
764         long l = strtol(p, NULL, 10);
765         if(l > 0 && l <= (1*1024*1024)) {
766           chunk_size = (size_t)l;
767         }
768       }
769     }
770 #endif
771     CURL_TRC_WS(data, "WS, using chunk size %zu", chunk_size);
772     Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT,
773                     BUFQ_OPT_SOFT_LIMIT);
774     Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT,
775                     BUFQ_OPT_SOFT_LIMIT);
776     ws_dec_init(&ws->dec);
777     ws_enc_init(&ws->enc);
778   }
779   else {
780     Curl_bufq_reset(&ws->recvbuf);
781     ws_dec_reset(&ws->dec);
782     ws_enc_reset(&ws->enc);
783   }
784   /* Verify the Sec-WebSocket-Accept response.
785 
786      The sent value is the base64 encoded version of a SHA-1 hash done on the
787      |Sec-WebSocket-Key| header field concatenated with
788      the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".
789   */
790 
791   /* If the response includes a |Sec-WebSocket-Extensions| header field and
792      this header field indicates the use of an extension that was not present
793      in the client's handshake (the server has indicated an extension not
794      requested by the client), the client MUST Fail the WebSocket Connection.
795   */
796 
797   /* If the response includes a |Sec-WebSocket-Protocol| header field
798      and this header field indicates the use of a subprotocol that was
799      not present in the client's handshake (the server has indicated a
800      subprotocol not requested by the client), the client MUST Fail
801      the WebSocket Connection. */
802 
803   /* 4 bytes random */
804 
805   result = Curl_rand(data, (unsigned char *)&ws->enc.mask,
806                      sizeof(ws->enc.mask));
807   if(result)
808     return result;
809   infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
810         ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]);
811 
812   /* Install our client writer that decodes WS frames payload */
813   result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode,
814                                CURL_CW_CONTENT_DECODE);
815   if(result)
816     return result;
817 
818   result = Curl_cwriter_add(data, ws_dec_writer);
819   if(result) {
820     Curl_cwriter_free(data, ws_dec_writer);
821     return result;
822   }
823 
824   if(data->set.connect_only) {
825     ssize_t nwritten;
826     /* In CONNECT_ONLY setup, the payloads from `mem` need to be received
827      * when using `curl_ws_recv` later on after this transfer is already
828      * marked as DONE. */
829     nwritten = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)mem,
830                                nread, &result);
831     if(nwritten < 0)
832       return result;
833     infof(data, "%zu bytes websocket payload", nread);
834   }
835   else { /* !connect_only */
836     /* And pass any additional data to the writers */
837     if(nread) {
838       result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)mem, nread);
839     }
840   }
841   k->upgr101 = UPGR101_RECEIVED;
842 
843   return result;
844 }
845 
846 struct ws_collect {
847   struct Curl_easy *data;
848   unsigned char *buffer;
849   size_t buflen;
850   size_t bufidx;
851   int frame_age;
852   int frame_flags;
853   curl_off_t payload_offset;
854   curl_off_t payload_len;
855   bool written;
856 };
857 
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)858 static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen,
859                                  int frame_age, int frame_flags,
860                                  curl_off_t payload_offset,
861                                  curl_off_t payload_len,
862                                  void *userp,
863                                  CURLcode *err)
864 {
865   struct ws_collect *ctx = userp;
866   size_t nwritten;
867   curl_off_t remain = (payload_len - (payload_offset + buflen));
868 
869   if(!ctx->bufidx) {
870     /* first write */
871     ctx->frame_age = frame_age;
872     ctx->frame_flags = frame_flags;
873     ctx->payload_offset = payload_offset;
874     ctx->payload_len = payload_len;
875   }
876 
877   if((frame_flags & CURLWS_PING) && !remain) {
878     /* auto-respond to PINGs, only works for single-frame payloads atm */
879     size_t bytes;
880     infof(ctx->data, "WS: auto-respond to PING with a PONG");
881     /* send back the exact same content as a PONG */
882     *err = curl_ws_send(ctx->data, buf, buflen, &bytes, 0, CURLWS_PONG);
883     if(*err)
884       return -1;
885     nwritten = bytes;
886   }
887   else {
888     ctx->written = TRUE;
889     DEBUGASSERT(ctx->buflen >= ctx->bufidx);
890     nwritten = CURLMIN(buflen, ctx->buflen - ctx->bufidx);
891     if(!nwritten) {
892       if(!buflen) {  /* 0 length write, we accept that */
893         *err = CURLE_OK;
894         return 0;
895       }
896       *err = CURLE_AGAIN;  /* no more space */
897       return -1;
898     }
899     *err = CURLE_OK;
900     memcpy(ctx->buffer + ctx->bufidx, buf, nwritten);
901     ctx->bufidx += nwritten;
902   }
903   return nwritten;
904 }
905 
nw_in_recv(void * reader_ctx,unsigned char * buf,size_t buflen,CURLcode * err)906 static ssize_t nw_in_recv(void *reader_ctx,
907                           unsigned char *buf, size_t buflen,
908                           CURLcode *err)
909 {
910   struct Curl_easy *data = reader_ctx;
911   size_t nread;
912 
913   *err = curl_easy_recv(data, buf, buflen, &nread);
914   if(*err)
915     return -1;
916   return (ssize_t)nread;
917 }
918 
curl_ws_recv(CURL * d,void * buffer,size_t buflen,size_t * nread,const struct curl_ws_frame ** metap)919 CURL_EXTERN CURLcode curl_ws_recv(CURL *d, void *buffer,
920                                   size_t buflen, size_t *nread,
921                                   const struct curl_ws_frame **metap)
922 {
923   struct Curl_easy *data = d;
924   struct connectdata *conn = data->conn;
925   struct websocket *ws;
926   struct ws_collect ctx;
927 
928   *nread = 0;
929   *metap = NULL;
930 
931   if(!conn) {
932     /* Unhappy hack with lifetimes of transfers and connection */
933     if(!data->set.connect_only) {
934       failf(data, "CONNECT_ONLY is required");
935       return CURLE_UNSUPPORTED_PROTOCOL;
936     }
937 
938     Curl_getconnectinfo(data, &conn);
939     if(!conn) {
940       failf(data, "connection not found");
941       return CURLE_BAD_FUNCTION_ARGUMENT;
942     }
943   }
944   ws = conn->proto.ws;
945   if(!ws) {
946     failf(data, "connection is not setup for websocket");
947     return CURLE_BAD_FUNCTION_ARGUMENT;
948   }
949 
950 
951   memset(&ctx, 0, sizeof(ctx));
952   ctx.data = data;
953   ctx.buffer = buffer;
954   ctx.buflen = buflen;
955 
956   while(1) {
957     CURLcode result;
958 
959     /* receive more when our buffer is empty */
960     if(Curl_bufq_is_empty(&ws->recvbuf)) {
961       ssize_t n = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &result);
962       if(n < 0) {
963         return result;
964       }
965       else if(n == 0) {
966         /* connection closed */
967         infof(data, "connection expectedly closed?");
968         return CURLE_GOT_NOTHING;
969       }
970       CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network",
971                   Curl_bufq_len(&ws->recvbuf));
972     }
973 
974     result = ws_dec_pass(&ws->dec, data, &ws->recvbuf,
975                          ws_client_collect, &ctx);
976     if(result == CURLE_AGAIN) {
977       if(!ctx.written) {
978         ws_dec_info(&ws->dec, data, "need more input");
979         continue;  /* nothing written, try more input */
980       }
981       break;
982     }
983     else if(result) {
984       return result;
985     }
986     else if(ctx.written) {
987       /* The decoded frame is passed back to our caller.
988        * There are frames like PING were we auto-respond to and
989        * that we do not return. For these `ctx.written` is not set. */
990       break;
991     }
992   }
993 
994   /* update frame information to be passed back */
995   update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset,
996               ctx.payload_len, ctx.bufidx);
997   *metap = &ws->frame;
998   *nread = ws->frame.len;
999   CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %"
1000                FMT_OFF_T ", %" FMT_OFF_T " left)",
1001                buflen, *nread, ws->frame.offset, ws->frame.bytesleft);
1002   return CURLE_OK;
1003 }
1004 
ws_flush(struct Curl_easy * data,struct websocket * ws,bool blocking)1005 static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
1006                          bool blocking)
1007 {
1008   if(!Curl_bufq_is_empty(&ws->sendbuf)) {
1009     CURLcode result;
1010     const unsigned char *out;
1011     size_t outlen, n;
1012 
1013     while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) {
1014       if(blocking) {
1015         result = ws_send_raw_blocking(data, ws, (char *)out, outlen);
1016         n = result ? 0 : outlen;
1017       }
1018       else if(data->set.connect_only || Curl_is_in_callback(data))
1019         result = Curl_senddata(data, out, outlen, &n);
1020       else {
1021         result = Curl_xfer_send(data, out, outlen, FALSE, &n);
1022         if(!result && !n && outlen)
1023           result = CURLE_AGAIN;
1024       }
1025 
1026       if(result == CURLE_AGAIN) {
1027         CURL_TRC_WS(data, "flush EAGAIN, %zu bytes remain in buffer",
1028                     Curl_bufq_len(&ws->sendbuf));
1029         return result;
1030       }
1031       else if(result) {
1032         failf(data, "WS: flush, write error %d", result);
1033         return result;
1034       }
1035       else {
1036         infof(data, "WS: flushed %zu bytes", n);
1037         Curl_bufq_skip(&ws->sendbuf, n);
1038       }
1039     }
1040   }
1041   return CURLE_OK;
1042 }
1043 
ws_send_raw_blocking(CURL * d,struct websocket * ws,const char * buffer,size_t buflen)1044 static CURLcode ws_send_raw_blocking(CURL *d, struct websocket *ws,
1045                                      const char *buffer, size_t buflen)
1046 {
1047   CURLcode result = CURLE_OK;
1048   size_t nwritten;
1049   struct Curl_easy *data = d;
1050 
1051   (void)ws;
1052   while(buflen) {
1053     result = Curl_xfer_send(data, buffer, buflen, FALSE, &nwritten);
1054     if(result)
1055       return result;
1056     DEBUGASSERT(nwritten <= buflen);
1057     buffer += nwritten;
1058     buflen -= nwritten;
1059     if(buflen) {
1060       curl_socket_t sock = data->conn->sock[FIRSTSOCKET];
1061       timediff_t left_ms;
1062       int ev;
1063 
1064       CURL_TRC_WS(data, "ws_send_raw_blocking() partial, %zu left to send",
1065                   buflen);
1066       left_ms = Curl_timeleft(data, NULL, FALSE);
1067       if(left_ms < 0) {
1068         failf(data, "Timeout waiting for socket becoming writable");
1069         return CURLE_SEND_ERROR;
1070       }
1071 
1072       /* POLLOUT socket */
1073       if(sock == CURL_SOCKET_BAD)
1074         return CURLE_SEND_ERROR;
1075       ev = Curl_socket_check(CURL_SOCKET_BAD, CURL_SOCKET_BAD, sock,
1076                              left_ms ? left_ms : 500);
1077       if(ev < 0) {
1078         failf(data, "Error while waiting for socket becoming writable");
1079         return CURLE_SEND_ERROR;
1080       }
1081     }
1082   }
1083   return result;
1084 }
1085 
ws_send_raw(struct Curl_easy * data,const void * buffer,size_t buflen,size_t * pnwritten)1086 static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer,
1087                             size_t buflen, size_t *pnwritten)
1088 {
1089   struct websocket *ws = data->conn->proto.ws;
1090   CURLcode result;
1091 
1092   if(!ws) {
1093     failf(data, "Not a websocket transfer");
1094     return CURLE_SEND_ERROR;
1095   }
1096   if(!buflen)
1097     return CURLE_OK;
1098 
1099   if(Curl_is_in_callback(data)) {
1100     /* When invoked from inside callbacks, we do a blocking send as the
1101      * callback will probably not implement partial writes that may then
1102      * mess up the ws framing subsequently.
1103      * We need any pending data to be flushed before sending. */
1104     result = ws_flush(data, ws, TRUE);
1105     if(result)
1106       return result;
1107     result = ws_send_raw_blocking(data, ws, buffer, buflen);
1108   }
1109   else {
1110     /* We need any pending data to be sent or EAGAIN this call. */
1111     result = ws_flush(data, ws, FALSE);
1112     if(result)
1113       return result;
1114     result = Curl_senddata(data, buffer, buflen, pnwritten);
1115   }
1116 
1117   CURL_TRC_WS(data, "ws_send_raw(len=%zu) -> %d, %zu",
1118               buflen, result, *pnwritten);
1119   return result;
1120 }
1121 
curl_ws_send(CURL * d,const void * buffer,size_t buflen,size_t * sent,curl_off_t fragsize,unsigned int flags)1122 CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer,
1123                                   size_t buflen, size_t *sent,
1124                                   curl_off_t fragsize,
1125                                   unsigned int flags)
1126 {
1127   struct websocket *ws;
1128   ssize_t n;
1129   size_t space, payload_added;
1130   CURLcode result;
1131   struct Curl_easy *data = d;
1132 
1133   CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T
1134               ", flags=%x), raw=%d",
1135               buflen, fragsize, flags, data->set.ws_raw_mode);
1136   *sent = 0;
1137   if(!data->conn && data->set.connect_only) {
1138     result = Curl_connect_only_attach(data);
1139     if(result)
1140       goto out;
1141   }
1142   if(!data->conn) {
1143     failf(data, "No associated connection");
1144     result = CURLE_SEND_ERROR;
1145     goto out;
1146   }
1147   if(!data->conn->proto.ws) {
1148     failf(data, "Not a websocket transfer");
1149     result = CURLE_SEND_ERROR;
1150     goto out;
1151   }
1152   ws = data->conn->proto.ws;
1153 
1154   /* try flushing any content still waiting to be sent. */
1155   result = ws_flush(data, ws, FALSE);
1156   if(result)
1157     goto out;
1158 
1159   if(data->set.ws_raw_mode) {
1160     /* In raw mode, we write directly to the connection */
1161     if(fragsize || flags) {
1162       failf(data, "ws_send, raw mode: fragsize and flags cannot be non-zero");
1163       return CURLE_BAD_FUNCTION_ARGUMENT;
1164     }
1165     result = ws_send_raw(data, buffer, buflen, sent);
1166     goto out;
1167   }
1168 
1169   /* Not RAW mode, buf we do the frame encoding */
1170   space = Curl_bufq_space(&ws->sendbuf);
1171   CURL_TRC_WS(data, "curl_ws_send(len=%zu), sendbuf=%zu space_left=%zu",
1172               buflen, Curl_bufq_len(&ws->sendbuf), space);
1173   if(space < 14) {
1174     result = CURLE_AGAIN;
1175     goto out;
1176   }
1177 
1178   if(flags & CURLWS_OFFSET) {
1179     if(fragsize) {
1180       /* a frame series 'fragsize' bytes big, this is the first */
1181       n = ws_enc_write_head(data, &ws->enc, flags, fragsize,
1182                             &ws->sendbuf, &result);
1183       if(n < 0)
1184         goto out;
1185     }
1186     else {
1187       if((curl_off_t)buflen > ws->enc.payload_remain) {
1188         infof(data, "WS: unaligned frame size (sending %zu instead of %"
1189                     FMT_OFF_T ")",
1190               buflen, ws->enc.payload_remain);
1191       }
1192     }
1193   }
1194   else if(!ws->enc.payload_remain) {
1195     n = ws_enc_write_head(data, &ws->enc, flags, (curl_off_t)buflen,
1196                           &ws->sendbuf, &result);
1197     if(n < 0)
1198       goto out;
1199   }
1200 
1201   n = ws_enc_write_payload(&ws->enc, data,
1202                            buffer, buflen, &ws->sendbuf, &result);
1203   if(n < 0)
1204     goto out;
1205   payload_added = (size_t)n;
1206 
1207   while(!result && (buflen || !Curl_bufq_is_empty(&ws->sendbuf))) {
1208     /* flush, blocking when in callback */
1209     result = ws_flush(data, ws, Curl_is_in_callback(data));
1210     if(!result) {
1211       DEBUGASSERT(payload_added <= buflen);
1212       /* all buffered data sent. Try sending the rest if there is any. */
1213       *sent += payload_added;
1214       buffer = (const char *)buffer + payload_added;
1215       buflen -= payload_added;
1216       payload_added = 0;
1217       if(buflen) {
1218         n = ws_enc_write_payload(&ws->enc, data,
1219                                  buffer, buflen, &ws->sendbuf, &result);
1220         if(n < 0)
1221           goto out;
1222         payload_added = Curl_bufq_len(&ws->sendbuf);
1223       }
1224     }
1225     else if(result == CURLE_AGAIN) {
1226       /* partially sent. how much of the call data has been part of it? what
1227       * should we report to out caller so it can retry/send the rest? */
1228       if(payload_added < buflen) {
1229         /* We did not add everything the caller wanted. Return just
1230          * the partial write to our buffer. */
1231         *sent = payload_added;
1232         result = CURLE_OK;
1233         goto out;
1234       }
1235       else if(!buflen) {
1236         /* We have no payload to report a partial write. EAGAIN would make
1237          * the caller repeat this and add the frame again.
1238          * Flush blocking seems the only way out of this. */
1239         *sent = (size_t)n;
1240         result = ws_flush(data, ws, TRUE);
1241         goto out;
1242       }
1243       /* We added the complete data to our sendbuf. Report one byte less as
1244        * sent. This partial success should make the caller invoke us again
1245        * with the last byte. */
1246       *sent = payload_added - 1;
1247       result = Curl_bufq_unwrite(&ws->sendbuf, 1);
1248       if(!result)
1249         result = CURLE_AGAIN;
1250     }
1251   }
1252 
1253 out:
1254   CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T
1255               ", flags=%x, raw=%d) -> %d, %zu",
1256               buflen, fragsize, flags, data->set.ws_raw_mode, result, *sent);
1257   return result;
1258 }
1259 
ws_free(struct connectdata * conn)1260 static void ws_free(struct connectdata *conn)
1261 {
1262   if(conn && conn->proto.ws) {
1263     Curl_bufq_free(&conn->proto.ws->recvbuf);
1264     Curl_bufq_free(&conn->proto.ws->sendbuf);
1265     Curl_safefree(conn->proto.ws);
1266   }
1267 }
1268 
ws_setup_conn(struct Curl_easy * data,struct connectdata * conn)1269 static CURLcode ws_setup_conn(struct Curl_easy *data,
1270                               struct connectdata *conn)
1271 {
1272   /* WebSockets is 1.1 only (for now) */
1273   data->state.httpwant = CURL_HTTP_VERSION_1_1;
1274   return Curl_http_setup_conn(data, conn);
1275 }
1276 
1277 
ws_disconnect(struct Curl_easy * data,struct connectdata * conn,bool dead_connection)1278 static CURLcode ws_disconnect(struct Curl_easy *data,
1279                               struct connectdata *conn,
1280                               bool dead_connection)
1281 {
1282   (void)data;
1283   (void)dead_connection;
1284   ws_free(conn);
1285   return CURLE_OK;
1286 }
1287 
curl_ws_meta(CURL * d)1288 CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *d)
1289 {
1290   /* we only return something for websocket, called from within the callback
1291      when not using raw mode */
1292   struct Curl_easy *data = d;
1293   if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->conn &&
1294      data->conn->proto.ws && !data->set.ws_raw_mode)
1295     return &data->conn->proto.ws->frame;
1296   return NULL;
1297 }
1298 
1299 const struct Curl_handler Curl_handler_ws = {
1300   "WS",                                 /* scheme */
1301   ws_setup_conn,                        /* setup_connection */
1302   Curl_http,                            /* do_it */
1303   Curl_http_done,                       /* done */
1304   ZERO_NULL,                            /* do_more */
1305   Curl_http_connect,                    /* connect_it */
1306   ZERO_NULL,                            /* connecting */
1307   ZERO_NULL,                            /* doing */
1308   ZERO_NULL,                            /* proto_getsock */
1309   Curl_http_getsock_do,                 /* doing_getsock */
1310   ZERO_NULL,                            /* domore_getsock */
1311   ZERO_NULL,                            /* perform_getsock */
1312   ws_disconnect,                        /* disconnect */
1313   Curl_http_write_resp,                 /* write_resp */
1314   Curl_http_write_resp_hd,              /* write_resp_hd */
1315   ZERO_NULL,                            /* connection_check */
1316   ZERO_NULL,                            /* attach connection */
1317   PORT_HTTP,                            /* defport */
1318   CURLPROTO_WS,                         /* protocol */
1319   CURLPROTO_HTTP,                       /* family */
1320   PROTOPT_CREDSPERREQUEST |             /* flags */
1321   PROTOPT_USERPWDCTRL
1322 };
1323 
1324 #ifdef USE_SSL
1325 const struct Curl_handler Curl_handler_wss = {
1326   "WSS",                                /* scheme */
1327   ws_setup_conn,                        /* setup_connection */
1328   Curl_http,                            /* do_it */
1329   Curl_http_done,                       /* done */
1330   ZERO_NULL,                            /* do_more */
1331   Curl_http_connect,                    /* connect_it */
1332   NULL,                                 /* connecting */
1333   ZERO_NULL,                            /* doing */
1334   NULL,                                 /* proto_getsock */
1335   Curl_http_getsock_do,                 /* doing_getsock */
1336   ZERO_NULL,                            /* domore_getsock */
1337   ZERO_NULL,                            /* perform_getsock */
1338   ws_disconnect,                        /* disconnect */
1339   Curl_http_write_resp,                 /* write_resp */
1340   Curl_http_write_resp_hd,              /* write_resp_hd */
1341   ZERO_NULL,                            /* connection_check */
1342   ZERO_NULL,                            /* attach connection */
1343   PORT_HTTPS,                           /* defport */
1344   CURLPROTO_WSS,                        /* protocol */
1345   CURLPROTO_HTTP,                       /* family */
1346   PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */
1347   PROTOPT_USERPWDCTRL
1348 };
1349 #endif
1350 
1351 
1352 #else
1353 
curl_ws_recv(CURL * curl,void * buffer,size_t buflen,size_t * nread,const struct curl_ws_frame ** metap)1354 CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
1355                                   size_t *nread,
1356                                   const struct curl_ws_frame **metap)
1357 {
1358   (void)curl;
1359   (void)buffer;
1360   (void)buflen;
1361   (void)nread;
1362   (void)metap;
1363   return CURLE_NOT_BUILT_IN;
1364 }
1365 
curl_ws_send(CURL * curl,const void * buffer,size_t buflen,size_t * sent,curl_off_t fragsize,unsigned int flags)1366 CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
1367                                   size_t buflen, size_t *sent,
1368                                   curl_off_t fragsize,
1369                                   unsigned int flags)
1370 {
1371   (void)curl;
1372   (void)buffer;
1373   (void)buflen;
1374   (void)sent;
1375   (void)fragsize;
1376   (void)flags;
1377   return CURLE_NOT_BUILT_IN;
1378 }
1379 
curl_ws_meta(CURL * data)1380 CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *data)
1381 {
1382   (void)data;
1383   return NULL;
1384 }
1385 #endif /* !CURL_DISABLE_WEBSOCKETS */
1386