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