xref: /curl/lib/ws.c (revision 266baf2d)
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     free(randstr);
723     return CURLE_FAILED_INIT;
724   }
725   strcpy(keyval, randstr);
726   free(randstr);
727   for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) {
728     if(!Curl_checkheaders(data, STRCONST(heads[i].name))) {
729 #ifdef USE_HYPER
730       char field[128];
731       msnprintf(field, sizeof(field), "%s %s", heads[i].name,
732                 heads[i].val);
733       result = Curl_hyper_header(data, req, field);
734 #else
735       (void)data;
736       result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name,
737                              heads[i].val);
738 #endif
739     }
740   }
741   k->upgr101 = UPGR101_WS;
742   return result;
743 }
744 
745 /*
746  * 'nread' is number of bytes of websocket data already in the buffer at
747  * 'mem'.
748  */
Curl_ws_accept(struct Curl_easy * data,const char * mem,size_t nread)749 CURLcode Curl_ws_accept(struct Curl_easy *data,
750                         const char *mem, size_t nread)
751 {
752   struct SingleRequest *k = &data->req;
753   struct websocket *ws;
754   struct Curl_cwriter *ws_dec_writer;
755   CURLcode result;
756 
757   DEBUGASSERT(data->conn);
758   ws = data->conn->proto.ws;
759   if(!ws) {
760     size_t chunk_size = WS_CHUNK_SIZE;
761     ws = calloc(1, sizeof(*ws));
762     if(!ws)
763       return CURLE_OUT_OF_MEMORY;
764     data->conn->proto.ws = ws;
765 #ifdef DEBUGBUILD
766     {
767       char *p = getenv("CURL_WS_CHUNK_SIZE");
768       if(p) {
769         long l = strtol(p, NULL, 10);
770         if(l > 0 && l <= (1*1024*1024)) {
771           chunk_size = (size_t)l;
772         }
773       }
774     }
775 #endif
776     DEBUGF(infof(data, "WS, using chunk size %zu", chunk_size));
777     Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT,
778                     BUFQ_OPT_SOFT_LIMIT);
779     Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT,
780                     BUFQ_OPT_SOFT_LIMIT);
781     ws_dec_init(&ws->dec);
782     ws_enc_init(&ws->enc);
783   }
784   else {
785     Curl_bufq_reset(&ws->recvbuf);
786     ws_dec_reset(&ws->dec);
787     ws_enc_reset(&ws->enc);
788   }
789   /* Verify the Sec-WebSocket-Accept response.
790 
791      The sent value is the base64 encoded version of a SHA-1 hash done on the
792      |Sec-WebSocket-Key| header field concatenated with
793      the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".
794   */
795 
796   /* If the response includes a |Sec-WebSocket-Extensions| header field and
797      this header field indicates the use of an extension that was not present
798      in the client's handshake (the server has indicated an extension not
799      requested by the client), the client MUST Fail the WebSocket Connection.
800   */
801 
802   /* If the response includes a |Sec-WebSocket-Protocol| header field
803      and this header field indicates the use of a subprotocol that was
804      not present in the client's handshake (the server has indicated a
805      subprotocol not requested by the client), the client MUST Fail
806      the WebSocket Connection. */
807 
808   /* 4 bytes random */
809 
810   result = Curl_rand(data, (unsigned char *)&ws->enc.mask,
811                      sizeof(ws->enc.mask));
812   if(result)
813     return result;
814   infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
815         ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]);
816 
817   /* Install our client writer that decodes WS frames payload */
818   result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode,
819                                CURL_CW_CONTENT_DECODE);
820   if(result)
821     return result;
822 
823   result = Curl_cwriter_add(data, ws_dec_writer);
824   if(result) {
825     Curl_cwriter_free(data, ws_dec_writer);
826     return result;
827   }
828 
829   if(data->set.connect_only) {
830     ssize_t nwritten;
831     /* In CONNECT_ONLY setup, the payloads from `mem` need to be received
832      * when using `curl_ws_recv` later on after this transfer is already
833      * marked as DONE. */
834     nwritten = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)mem,
835                                nread, &result);
836     if(nwritten < 0)
837       return result;
838     infof(data, "%zu bytes websocket payload", nread);
839   }
840   else { /* !connect_only */
841     /* And pass any additional data to the writers */
842     if(nread) {
843       result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)mem, nread);
844     }
845   }
846   k->upgr101 = UPGR101_RECEIVED;
847 
848   return result;
849 }
850 
851 struct ws_collect {
852   struct Curl_easy *data;
853   unsigned char *buffer;
854   size_t buflen;
855   size_t bufidx;
856   int frame_age;
857   int frame_flags;
858   curl_off_t payload_offset;
859   curl_off_t payload_len;
860   bool written;
861 };
862 
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)863 static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen,
864                                  int frame_age, int frame_flags,
865                                  curl_off_t payload_offset,
866                                  curl_off_t payload_len,
867                                  void *userp,
868                                  CURLcode *err)
869 {
870   struct ws_collect *ctx = userp;
871   size_t nwritten;
872   curl_off_t remain = (payload_len - (payload_offset + buflen));
873 
874   if(!ctx->bufidx) {
875     /* first write */
876     ctx->frame_age = frame_age;
877     ctx->frame_flags = frame_flags;
878     ctx->payload_offset = payload_offset;
879     ctx->payload_len = payload_len;
880   }
881 
882   if((frame_flags & CURLWS_PING) && !remain) {
883     /* auto-respond to PINGs, only works for single-frame payloads atm */
884     size_t bytes;
885     infof(ctx->data, "WS: auto-respond to PING with a PONG");
886     /* send back the exact same content as a PONG */
887     *err = curl_ws_send(ctx->data, buf, buflen, &bytes, 0, CURLWS_PONG);
888     if(*err)
889       return -1;
890     nwritten = bytes;
891   }
892   else {
893     ctx->written = TRUE;
894     DEBUGASSERT(ctx->buflen >= ctx->bufidx);
895     nwritten = CURLMIN(buflen, ctx->buflen - ctx->bufidx);
896     if(!nwritten) {
897       if(!buflen) {  /* 0 length write, we accept that */
898         *err = CURLE_OK;
899         return 0;
900       }
901       *err = CURLE_AGAIN;  /* no more space */
902       return -1;
903     }
904     *err = CURLE_OK;
905     memcpy(ctx->buffer + ctx->bufidx, buf, nwritten);
906     ctx->bufidx += nwritten;
907   }
908   return nwritten;
909 }
910 
nw_in_recv(void * reader_ctx,unsigned char * buf,size_t buflen,CURLcode * err)911 static ssize_t nw_in_recv(void *reader_ctx,
912                           unsigned char *buf, size_t buflen,
913                           CURLcode *err)
914 {
915   struct Curl_easy *data = reader_ctx;
916   size_t nread;
917 
918   *err = curl_easy_recv(data, buf, buflen, &nread);
919   if(*err)
920     return -1;
921   return (ssize_t)nread;
922 }
923 
curl_ws_recv(struct Curl_easy * data,void * buffer,size_t buflen,size_t * nread,const struct curl_ws_frame ** metap)924 CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer,
925                                   size_t buflen, size_t *nread,
926                                   const struct curl_ws_frame **metap)
927 {
928   struct connectdata *conn = data->conn;
929   struct websocket *ws;
930   bool done = FALSE; /* not filled passed buffer yet */
931   struct ws_collect ctx;
932   CURLcode result;
933 
934   if(!conn) {
935     /* Unhappy hack with lifetimes of transfers and connection */
936     if(!data->set.connect_only) {
937       failf(data, "CONNECT_ONLY is required");
938       return CURLE_UNSUPPORTED_PROTOCOL;
939     }
940 
941     Curl_getconnectinfo(data, &conn);
942     if(!conn) {
943       failf(data, "connection not found");
944       return CURLE_BAD_FUNCTION_ARGUMENT;
945     }
946   }
947   ws = conn->proto.ws;
948   if(!ws) {
949     failf(data, "connection is not setup for websocket");
950     return CURLE_BAD_FUNCTION_ARGUMENT;
951   }
952 
953   *nread = 0;
954   *metap = NULL;
955 
956   memset(&ctx, 0, sizeof(ctx));
957   ctx.data = data;
958   ctx.buffer = buffer;
959   ctx.buflen = buflen;
960 
961   while(!done) {
962     /* receive more when our buffer is empty */
963     if(Curl_bufq_is_empty(&ws->recvbuf)) {
964       ssize_t n = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &result);
965       if(n < 0) {
966         return result;
967       }
968       else if(n == 0) {
969         /* connection closed */
970         infof(data, "connection expectedly closed?");
971         return CURLE_GOT_NOTHING;
972       }
973       DEBUGF(infof(data, "curl_ws_recv, added %zu bytes from network",
974                    Curl_bufq_len(&ws->recvbuf)));
975     }
976 
977     result = ws_dec_pass(&ws->dec, data, &ws->recvbuf,
978                          ws_client_collect, &ctx);
979     if(result == CURLE_AGAIN) {
980       if(!ctx.written) {
981         ws_dec_info(&ws->dec, data, "need more input");
982         continue;  /* nothing written, try more input */
983       }
984       done = TRUE;
985       break;
986     }
987     else if(result) {
988       return result;
989     }
990     else if(ctx.written) {
991       /* The decoded frame is passed back to our caller.
992        * There are frames like PING were we auto-respond to and
993        * that we do not return. For these `ctx.written` is not set. */
994       done = TRUE;
995       break;
996     }
997   }
998 
999   /* update frame information to be passed back */
1000   update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset,
1001               ctx.payload_len, ctx.bufidx);
1002   *metap = &ws->frame;
1003   *nread = ws->frame.len;
1004   /* infof(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %"
1005            CURL_FORMAT_CURL_OFF_T ", %" CURL_FORMAT_CURL_OFF_T " left)",
1006            buflen, *nread, ws->frame.offset, ws->frame.bytesleft); */
1007   return CURLE_OK;
1008 }
1009 
ws_flush(struct Curl_easy * data,struct websocket * ws,bool complete)1010 static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
1011                          bool complete)
1012 {
1013   if(!Curl_bufq_is_empty(&ws->sendbuf)) {
1014     CURLcode result;
1015     const unsigned char *out;
1016     size_t outlen, n;
1017 
1018     while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) {
1019       if(data->set.connect_only)
1020         result = Curl_senddata(data, out, outlen, &n);
1021       else {
1022         result = Curl_xfer_send(data, out, outlen, &n);
1023         if(!result && !n && outlen)
1024           result = CURLE_AGAIN;
1025       }
1026 
1027       if(result) {
1028         if(result == CURLE_AGAIN) {
1029           if(!complete) {
1030             infof(data, "WS: flush EAGAIN, %zu bytes remain in buffer",
1031                   Curl_bufq_len(&ws->sendbuf));
1032             return result;
1033           }
1034           /* TODO: the current design does not allow for buffered writes.
1035            * We need to flush the buffer now. There is no ws_flush() later */
1036           n = 0;
1037           continue;
1038         }
1039         else if(result) {
1040           failf(data, "WS: flush, write error %d", result);
1041           return result;
1042         }
1043       }
1044       else {
1045         infof(data, "WS: flushed %zu bytes", n);
1046         Curl_bufq_skip(&ws->sendbuf, n);
1047       }
1048     }
1049   }
1050   return CURLE_OK;
1051 }
1052 
curl_ws_send(CURL * data,const void * buffer,size_t buflen,size_t * sent,curl_off_t fragsize,unsigned int flags)1053 CURL_EXTERN CURLcode curl_ws_send(CURL *data, const void *buffer,
1054                                   size_t buflen, size_t *sent,
1055                                   curl_off_t fragsize,
1056                                   unsigned int flags)
1057 {
1058   struct websocket *ws;
1059   ssize_t n;
1060   size_t nwritten, space;
1061   CURLcode result;
1062 
1063   *sent = 0;
1064   if(!data->conn && data->set.connect_only) {
1065     result = Curl_connect_only_attach(data);
1066     if(result)
1067       return result;
1068   }
1069   if(!data->conn) {
1070     failf(data, "No associated connection");
1071     return CURLE_SEND_ERROR;
1072   }
1073   if(!data->conn->proto.ws) {
1074     failf(data, "Not a websocket transfer");
1075     return CURLE_SEND_ERROR;
1076   }
1077   ws = data->conn->proto.ws;
1078 
1079   if(data->set.ws_raw_mode) {
1080     if(fragsize || flags) {
1081       DEBUGF(infof(data, "ws_send: "
1082                    "fragsize and flags cannot be non-zero in raw mode"));
1083       return CURLE_BAD_FUNCTION_ARGUMENT;
1084     }
1085     if(!buflen)
1086       /* nothing to do */
1087       return CURLE_OK;
1088     /* raw mode sends exactly what was requested, and this is from within
1089        the write callback */
1090     if(Curl_is_in_callback(data)) {
1091       result = Curl_xfer_send(data, buffer, buflen, &nwritten);
1092     }
1093     else
1094       result = Curl_senddata(data, buffer, buflen, &nwritten);
1095 
1096     infof(data, "WS: wanted to send %zu bytes, sent %zu bytes",
1097           buflen, nwritten);
1098     *sent = nwritten;
1099     return result;
1100   }
1101 
1102   /* Not RAW mode, buf we do the frame encoding */
1103   result = ws_flush(data, ws, FALSE);
1104   if(result)
1105     return result;
1106 
1107   /* TODO: the current design does not allow partial writes, afaict.
1108    * It is not clear how the application is supposed to react. */
1109   space = Curl_bufq_space(&ws->sendbuf);
1110   DEBUGF(infof(data, "curl_ws_send(len=%zu), sendbuf len=%zu space %zu",
1111                buflen, Curl_bufq_len(&ws->sendbuf), space));
1112   if(space < 14)
1113     return CURLE_AGAIN;
1114 
1115   if(flags & CURLWS_OFFSET) {
1116     if(fragsize) {
1117       /* a frame series 'fragsize' bytes big, this is the first */
1118       n = ws_enc_write_head(data, &ws->enc, flags, fragsize,
1119                             &ws->sendbuf, &result);
1120       if(n < 0)
1121         return result;
1122     }
1123     else {
1124       if((curl_off_t)buflen > ws->enc.payload_remain) {
1125         infof(data, "WS: unaligned frame size (sending %zu instead of %"
1126                     CURL_FORMAT_CURL_OFF_T ")",
1127               buflen, ws->enc.payload_remain);
1128       }
1129     }
1130   }
1131   else if(!ws->enc.payload_remain) {
1132     n = ws_enc_write_head(data, &ws->enc, flags, (curl_off_t)buflen,
1133                           &ws->sendbuf, &result);
1134     if(n < 0)
1135       return result;
1136   }
1137 
1138   n = ws_enc_write_payload(&ws->enc, data,
1139                            buffer, buflen, &ws->sendbuf, &result);
1140   if(n < 0)
1141     return result;
1142 
1143   *sent = (size_t)n;
1144   return ws_flush(data, ws, TRUE);
1145 }
1146 
ws_free(struct connectdata * conn)1147 static void ws_free(struct connectdata *conn)
1148 {
1149   if(conn && conn->proto.ws) {
1150     Curl_bufq_free(&conn->proto.ws->recvbuf);
1151     Curl_bufq_free(&conn->proto.ws->sendbuf);
1152     Curl_safefree(conn->proto.ws);
1153   }
1154 }
1155 
ws_setup_conn(struct Curl_easy * data,struct connectdata * conn)1156 static CURLcode ws_setup_conn(struct Curl_easy *data,
1157                               struct connectdata *conn)
1158 {
1159   /* websockets is 1.1 only (for now) */
1160   data->state.httpwant = CURL_HTTP_VERSION_1_1;
1161   return Curl_http_setup_conn(data, conn);
1162 }
1163 
1164 
ws_disconnect(struct Curl_easy * data,struct connectdata * conn,bool dead_connection)1165 static CURLcode ws_disconnect(struct Curl_easy *data,
1166                               struct connectdata *conn,
1167                               bool dead_connection)
1168 {
1169   (void)data;
1170   (void)dead_connection;
1171   ws_free(conn);
1172   return CURLE_OK;
1173 }
1174 
curl_ws_meta(struct Curl_easy * data)1175 CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
1176 {
1177   /* we only return something for websocket, called from within the callback
1178      when not using raw mode */
1179   if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) && data->conn &&
1180      data->conn->proto.ws && !data->set.ws_raw_mode)
1181     return &data->conn->proto.ws->frame;
1182   return NULL;
1183 }
1184 
1185 const struct Curl_handler Curl_handler_ws = {
1186   "WS",                                 /* scheme */
1187   ws_setup_conn,                        /* setup_connection */
1188   Curl_http,                            /* do_it */
1189   Curl_http_done,                       /* done */
1190   ZERO_NULL,                            /* do_more */
1191   Curl_http_connect,                    /* connect_it */
1192   ZERO_NULL,                            /* connecting */
1193   ZERO_NULL,                            /* doing */
1194   ZERO_NULL,                            /* proto_getsock */
1195   Curl_http_getsock_do,                 /* doing_getsock */
1196   ZERO_NULL,                            /* domore_getsock */
1197   ZERO_NULL,                            /* perform_getsock */
1198   ws_disconnect,                        /* disconnect */
1199   Curl_http_write_resp,                 /* write_resp */
1200   Curl_http_write_resp_hd,              /* write_resp_hd */
1201   ZERO_NULL,                            /* connection_check */
1202   ZERO_NULL,                            /* attach connection */
1203   PORT_HTTP,                            /* defport */
1204   CURLPROTO_WS,                         /* protocol */
1205   CURLPROTO_HTTP,                       /* family */
1206   PROTOPT_CREDSPERREQUEST |             /* flags */
1207   PROTOPT_USERPWDCTRL
1208 };
1209 
1210 #ifdef USE_SSL
1211 const struct Curl_handler Curl_handler_wss = {
1212   "WSS",                                /* scheme */
1213   ws_setup_conn,                        /* setup_connection */
1214   Curl_http,                            /* do_it */
1215   Curl_http_done,                       /* done */
1216   ZERO_NULL,                            /* do_more */
1217   Curl_http_connect,                    /* connect_it */
1218   NULL,                                 /* connecting */
1219   ZERO_NULL,                            /* doing */
1220   NULL,                                 /* proto_getsock */
1221   Curl_http_getsock_do,                 /* doing_getsock */
1222   ZERO_NULL,                            /* domore_getsock */
1223   ZERO_NULL,                            /* perform_getsock */
1224   ws_disconnect,                        /* disconnect */
1225   Curl_http_write_resp,                 /* write_resp */
1226   Curl_http_write_resp_hd,              /* write_resp_hd */
1227   ZERO_NULL,                            /* connection_check */
1228   ZERO_NULL,                            /* attach connection */
1229   PORT_HTTPS,                           /* defport */
1230   CURLPROTO_WSS,                        /* protocol */
1231   CURLPROTO_HTTP,                       /* family */
1232   PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */
1233   PROTOPT_USERPWDCTRL
1234 };
1235 #endif
1236 
1237 
1238 #else
1239 
curl_ws_recv(CURL * curl,void * buffer,size_t buflen,size_t * nread,const struct curl_ws_frame ** metap)1240 CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
1241                                   size_t *nread,
1242                                   const struct curl_ws_frame **metap)
1243 {
1244   (void)curl;
1245   (void)buffer;
1246   (void)buflen;
1247   (void)nread;
1248   (void)metap;
1249   return CURLE_NOT_BUILT_IN;
1250 }
1251 
curl_ws_send(CURL * curl,const void * buffer,size_t buflen,size_t * sent,curl_off_t fragsize,unsigned int flags)1252 CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
1253                                   size_t buflen, size_t *sent,
1254                                   curl_off_t fragsize,
1255                                   unsigned int flags)
1256 {
1257   (void)curl;
1258   (void)buffer;
1259   (void)buflen;
1260   (void)sent;
1261   (void)fragsize;
1262   (void)flags;
1263   return CURLE_NOT_BUILT_IN;
1264 }
1265 
curl_ws_meta(struct Curl_easy * data)1266 CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
1267 {
1268   (void)data;
1269   return NULL;
1270 }
1271 #endif /* USE_WEBSOCKETS */
1272