Coverage Report

Created: 2023-06-07 07:02

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