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