Coverage Report

Created: 2023-12-08 06:48

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