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