Coverage Report

Created: 2026-09-01 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/ws.c
Line
Count
Source
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 "urldata.h"
26
#include "ws.h"
27
28
#ifndef CURL_DISABLE_WEBSOCKETS
29
30
#include "url.h"
31
#include "bufq.h"
32
#include "curlx/dynbuf.h"
33
#include "rand.h"
34
#include "curlx/base64.h"
35
#include "cf-recvbuf.h"
36
#include "connect.h"
37
#include "sendf.h"
38
#include "curl_trc.h"
39
#include "multiif.h"
40
#include "easyif.h"
41
#include "transfer.h"
42
#include "select.h"
43
#include "curlx/strparse.h"
44
#include "curlx/strcopy.h"
45
46
/* RFC 6455 Section 5.2
47
48
    0 1 2 3 4 5 6 7
49
   +-+-+-+-+-------+
50
   |F|R|R|R| opcode|
51
   |I|S|S|S|  (4)  |
52
   |N|V|V|V|       |
53
   | |1|2|3|       |
54
 */
55
478
#define WSBIT_FIN          0x80
56
350
#define WSBIT_RSV1         0x40
57
350
#define WSBIT_RSV2         0x20
58
350
#define WSBIT_RSV3         0x10
59
350
#define WSBIT_RSV_MASK     (WSBIT_RSV1 | WSBIT_RSV2 | WSBIT_RSV3)
60
1.37k
#define WSBIT_OPCODE_CONT  0x0
61
224
#define WSBIT_OPCODE_TEXT  0x1
62
316
#define WSBIT_OPCODE_BIN   0x2
63
27
#define WSBIT_OPCODE_CLOSE 0x8
64
36
#define WSBIT_OPCODE_PING  0x9
65
49
#define WSBIT_OPCODE_PONG  0xa
66
#ifdef CURLVERBOSE
67
0
#define WSBIT_OPCODE_MASK  0xf
68
#endif
69
70
1.81k
#define WSBIT_MASK 0x80
71
72
/* buffer dimensioning */
73
2.02k
#define WS_CHUNK_SIZE  65535
74
2.02k
#define WS_CHUNK_COUNT 2
75
76
/* a client-side WS frame decoder, parsing frame headers and
77
 * payload, keeping track of current position and stats */
78
enum ws_dec_state {
79
  WS_DEC_INIT,
80
  WS_DEC_HEAD,
81
  WS_DEC_PAYLOAD
82
};
83
84
struct ws_decoder {
85
  int frame_age;        /* zero */
86
  int frame_flags;      /* See the CURLWS_* defines */
87
  curl_off_t payload_offset;   /* the offset parsing is at */
88
  curl_off_t payload_len;
89
  uint8_t head[10];
90
  int head_len, head_total;
91
  enum ws_dec_state state;
92
  int cont_flags;
93
};
94
95
/* a client-side WS frame encoder, generating frame headers and
96
 * converting payloads, tracking remaining data in current frame */
97
struct ws_encoder {
98
  curl_off_t payload_len;  /* payload length of current frame */
99
  curl_off_t payload_remain;  /* remaining payload of current */
100
  unsigned int xori; /* xor index */
101
  uint8_t mask[4]; /* 32-bit mask for this connection */
102
  uint8_t firstbyte; /* first byte of frame we encode */
103
  BIT(contfragment); /* set TRUE if the previous fragment sent was not final */
104
};
105
106
/* Control frames are allowed up to 125 characters, rfc6455, ch. 5.5 */
107
108
#define WS_MAX_CNTRL_LEN    125
108
109
struct ws_cntrl_frame {
110
  unsigned int type;
111
  size_t payload_len;
112
  uint8_t payload[WS_MAX_CNTRL_LEN];
113
};
114
115
/* A websocket connection with en- and decoder that treat frames
116
 * and keep track of boundaries. */
117
struct websocket {
118
  struct Curl_easy *data; /* used for write callback handling */
119
  struct ws_decoder dec;  /* decode of ws frames */
120
  struct ws_encoder enc;  /* encode of ws frames */
121
  struct bufq recvbuf;    /* raw data from the server */
122
  struct bufq sendbuf;    /* raw data to be sent to the server */
123
  struct curl_ws_frame recvframe;  /* the current WS FRAME received */
124
  struct ws_cntrl_frame pending; /* a control frame pending to be sent */
125
  size_t sendbuf_payload; /* number of payload bytes in sendbuf */
126
};
127
128
#ifdef CURLVERBOSE
129
static const char *ws_frame_name_of_op(uint8_t firstbyte)
130
0
{
131
0
  switch(firstbyte & WSBIT_OPCODE_MASK) {
132
0
  case WSBIT_OPCODE_CONT:
133
0
    return "CONT";
134
0
  case WSBIT_OPCODE_TEXT:
135
0
    return "TEXT";
136
0
  case WSBIT_OPCODE_BIN:
137
0
    return "BIN";
138
0
  case WSBIT_OPCODE_CLOSE:
139
0
    return "CLOSE";
140
0
  case WSBIT_OPCODE_PING:
141
0
    return "PING";
142
0
  case WSBIT_OPCODE_PONG:
143
0
    return "PONG";
144
0
  default:
145
0
    return "???";
146
0
  }
147
0
}
148
#endif
149
150
static int ws_frame_firstbyte2flags(struct Curl_easy *data,
151
                                    uint8_t firstbyte, int cont_flags)
152
1.92k
{
153
1.92k
  switch(firstbyte) {
154
  /* 0x00 - intermediate TEXT/BINARY fragment */
155
1.29k
  case WSBIT_OPCODE_CONT:
156
1.29k
    if(!(cont_flags & CURLWS_CONT)) {
157
123
      failf(data, "[WS] no ongoing fragmented message to resume");
158
123
      return 0;
159
123
    }
160
1.16k
    return cont_flags | CURLWS_CONT;
161
  /* 0x80 - final TEXT/BIN fragment */
162
25
  case (WSBIT_OPCODE_CONT | WSBIT_FIN):
163
25
    if(!(cont_flags & CURLWS_CONT)) {
164
14
      failf(data, "[WS] no ongoing fragmented message to resume");
165
14
      return 0;
166
14
    }
167
11
    return cont_flags & ~CURLWS_CONT;
168
  /* 0x01 - first TEXT fragment */
169
130
  case WSBIT_OPCODE_TEXT:
170
130
    if(cont_flags & CURLWS_CONT) {
171
4
      failf(data, "[WS] fragmented message interrupted by new TEXT msg");
172
4
      return 0;
173
4
    }
174
126
    return CURLWS_TEXT | CURLWS_CONT;
175
  /* 0x81 - unfragmented TEXT msg */
176
21
  case (WSBIT_OPCODE_TEXT | WSBIT_FIN):
177
21
    if(cont_flags & CURLWS_CONT) {
178
2
      failf(data, "[WS] fragmented message interrupted by new TEXT msg");
179
2
      return 0;
180
2
    }
181
19
    return CURLWS_TEXT;
182
  /* 0x02 - first BINARY fragment */
183
42
  case WSBIT_OPCODE_BIN:
184
42
    if(cont_flags & CURLWS_CONT) {
185
13
      failf(data, "[WS] fragmented message interrupted by new BINARY msg");
186
13
      return 0;
187
13
    }
188
29
    return CURLWS_BINARY | CURLWS_CONT;
189
  /* 0x82 - unfragmented BINARY msg */
190
9
  case (WSBIT_OPCODE_BIN | WSBIT_FIN):
191
9
    if(cont_flags & CURLWS_CONT) {
192
3
      failf(data, "[WS] fragmented message interrupted by new BINARY msg");
193
3
      return 0;
194
3
    }
195
6
    return CURLWS_BINARY;
196
  /* 0x08 - first CLOSE fragment */
197
6
  case WSBIT_OPCODE_CLOSE:
198
6
    failf(data, "[WS] invalid fragmented CLOSE frame");
199
6
    return 0;
200
  /* 0x88 - unfragmented CLOSE */
201
8
  case (WSBIT_OPCODE_CLOSE | WSBIT_FIN):
202
8
    return CURLWS_CLOSE;
203
  /* 0x09 - first PING fragment */
204
1
  case WSBIT_OPCODE_PING:
205
1
    failf(data, "[WS] invalid fragmented PING frame");
206
1
    return 0;
207
  /* 0x89 - unfragmented PING */
208
22
  case (WSBIT_OPCODE_PING | WSBIT_FIN):
209
22
    return CURLWS_PING;
210
  /* 0x0a - first PONG fragment */
211
6
  case WSBIT_OPCODE_PONG:
212
6
    failf(data, "[WS] invalid fragmented PONG frame");
213
6
    return 0;
214
  /* 0x8a - unfragmented PONG */
215
17
  case (WSBIT_OPCODE_PONG | WSBIT_FIN):
216
17
    return CURLWS_PONG;
217
  /* invalid first byte */
218
350
  default:
219
350
    if(firstbyte & WSBIT_RSV_MASK)
220
      /* any of the reserved bits 0x40/0x20/0x10 are set */
221
316
      failf(data, "[WS] invalid reserved bits: %02x", firstbyte);
222
34
    else
223
      /* any of the reserved opcodes 0x3-0x7 or 0xb-0xf is used */
224
34
      failf(data, "[WS] invalid opcode: %02x", firstbyte);
225
350
    return 0;
226
1.92k
  }
227
1.92k
}
228
229
static CURLcode ws_frame_flags2firstbyte(struct Curl_easy *data,
230
                                         unsigned int flags,
231
                                         bool contfragment,
232
                                         uint8_t *pfirstbyte)
233
701
{
234
701
  *pfirstbyte = 0;
235
701
  switch(flags & ~CURLWS_OFFSET) {
236
171
  case 0:
237
171
    if(contfragment) {
238
17
      CURL_TRC_WS(data, "no flags given; interpreting as continuation "
239
17
                  "fragment for compatibility");
240
17
      *pfirstbyte = (WSBIT_OPCODE_CONT | WSBIT_FIN);
241
17
      return CURLE_OK;
242
17
    }
243
154
    failf(data, "[WS] no flags given");
244
154
    return CURLE_BAD_FUNCTION_ARGUMENT;
245
46
  case CURLWS_CONT:
246
46
    if(contfragment) {
247
16
      infof(data, "[WS] setting CURLWS_CONT flag without message type is "
248
16
                  "supported for compatibility but highly discouraged");
249
16
      *pfirstbyte = WSBIT_OPCODE_CONT;
250
16
      return CURLE_OK;
251
16
    }
252
30
    failf(data, "[WS] No ongoing fragmented message to continue");
253
30
    return CURLE_BAD_FUNCTION_ARGUMENT;
254
57
  case CURLWS_TEXT:
255
57
    *pfirstbyte = contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN)
256
57
                               : (WSBIT_OPCODE_TEXT | WSBIT_FIN);
257
57
    return CURLE_OK;
258
16
  case (CURLWS_TEXT | CURLWS_CONT):
259
16
    *pfirstbyte = contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_TEXT;
260
16
    return CURLE_OK;
261
250
  case CURLWS_BINARY:
262
250
    *pfirstbyte = contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN)
263
250
                               : (WSBIT_OPCODE_BIN | WSBIT_FIN);
264
250
    return CURLE_OK;
265
14
  case (CURLWS_BINARY | CURLWS_CONT):
266
14
    *pfirstbyte = contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_BIN;
267
14
    return CURLE_OK;
268
13
  case CURLWS_CLOSE:
269
13
    *pfirstbyte = WSBIT_OPCODE_CLOSE | WSBIT_FIN;
270
13
    return CURLE_OK;
271
22
  case (CURLWS_CLOSE | CURLWS_CONT):
272
22
    failf(data, "[WS] CLOSE frame must not be fragmented");
273
22
    return CURLE_BAD_FUNCTION_ARGUMENT;
274
13
  case CURLWS_PING:
275
13
    *pfirstbyte = WSBIT_OPCODE_PING | WSBIT_FIN;
276
13
    return CURLE_OK;
277
23
  case (CURLWS_PING | CURLWS_CONT):
278
23
    failf(data, "[WS] PING frame must not be fragmented");
279
23
    return CURLE_BAD_FUNCTION_ARGUMENT;
280
26
  case CURLWS_PONG:
281
26
    *pfirstbyte = WSBIT_OPCODE_PONG | WSBIT_FIN;
282
26
    return CURLE_OK;
283
24
  case (CURLWS_PONG | CURLWS_CONT):
284
24
    failf(data, "[WS] PONG frame must not be fragmented");
285
24
    return CURLE_BAD_FUNCTION_ARGUMENT;
286
26
  default:
287
26
    failf(data, "[WS] unknown flags: %x", flags);
288
26
    return CURLE_BAD_FUNCTION_ARGUMENT;
289
701
  }
290
701
}
291
292
static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data,
293
                        const char *msg)
294
1.88k
{
295
1.88k
  NOVERBOSE((void)msg);
296
1.88k
  switch(dec->head_len) {
297
0
  case 0:
298
0
    break;
299
16
  case 1:
300
16
    CURL_TRC_WS(data, "decoded %s [%s%s]", msg,
301
16
                ws_frame_name_of_op(dec->head[0]),
302
16
                (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL");
303
16
    break;
304
1.86k
  default:
305
1.86k
    if(dec->head_len < dec->head_total) {
306
1
      CURL_TRC_WS(data, "decoded %s [%s%s](%d/%d)", msg,
307
1
                  ws_frame_name_of_op(dec->head[0]),
308
1
                  (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
309
1
                  dec->head_len, dec->head_total);
310
1
    }
311
1.86k
    else {
312
1.86k
      CURL_TRC_WS(data, "decoded %s [%s%s payload=%"
313
1.86k
                  FMT_OFF_T "/%" FMT_OFF_T "]",
314
1.86k
                  msg, ws_frame_name_of_op(dec->head[0]),
315
1.86k
                  (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
316
1.86k
                  dec->payload_offset, dec->payload_len);
317
1.86k
    }
318
1.86k
    break;
319
1.88k
  }
320
1.88k
}
321
322
static CURLcode ws_send_raw_blocking(struct Curl_easy *data,
323
                                     struct websocket *ws,
324
                                     const char *buffer, size_t buflen);
325
326
typedef CURLcode ws_write_payload(const uint8_t *buf, size_t buflen,
327
                                  int frame_age, int frame_flags,
328
                                  curl_off_t payload_offset,
329
                                  curl_off_t payload_len,
330
                                  void *userp,
331
                                  size_t *pnwritten);
332
333
static void ws_dec_next_frame(struct ws_decoder *dec)
334
1.92k
{
335
1.92k
  dec->frame_age = 0;
336
1.92k
  dec->frame_flags = 0;
337
1.92k
  dec->payload_offset = 0;
338
1.92k
  dec->payload_len = 0;
339
1.92k
  dec->head_len = dec->head_total = 0;
340
1.92k
  dec->state = WS_DEC_INIT;
341
  /* dec->cont_flags must be carried over to next frame */
342
1.92k
}
343
344
static void ws_dec_reset(struct ws_decoder *dec)
345
1.54k
{
346
1.54k
  dec->frame_age = 0;
347
1.54k
  dec->frame_flags = 0;
348
1.54k
  dec->payload_offset = 0;
349
1.54k
  dec->payload_len = 0;
350
1.54k
  dec->head_len = dec->head_total = 0;
351
1.54k
  dec->state = WS_DEC_INIT;
352
1.54k
  dec->cont_flags = 0;
353
1.54k
}
354
355
static void ws_dec_init(struct ws_decoder *dec)
356
1.01k
{
357
1.01k
  ws_dec_reset(dec);
358
1.01k
}
359
360
static CURLcode ws_dec_read_head(struct ws_decoder *dec,
361
                                 struct Curl_easy *data,
362
                                 struct bufq *inraw)
363
2.00k
{
364
2.00k
  const uint8_t *inbuf;
365
2.00k
  size_t inlen;
366
367
3.48k
  while(Curl_bufq_peek(inraw, &inbuf, &inlen)) {
368
3.39k
    if(dec->head_len == 0) {
369
1.92k
      dec->head[0] = *inbuf;
370
1.92k
      Curl_bufq_skip(inraw, 1);
371
372
1.92k
      dec->frame_flags = ws_frame_firstbyte2flags(data, dec->head[0],
373
1.92k
                                                  dec->cont_flags);
374
1.92k
      if(!dec->frame_flags) {
375
522
        ws_dec_reset(dec);
376
522
        return CURLE_RECV_ERROR;
377
522
      }
378
379
      /* fragmentation only applies to data frames (text/binary);
380
       * control frames (close/ping/pong) do not affect the CONT status */
381
1.40k
      if(dec->frame_flags & (CURLWS_TEXT | CURLWS_BINARY)) {
382
1.36k
        dec->cont_flags = dec->frame_flags;
383
1.36k
      }
384
385
1.40k
      dec->head_len = 1;
386
#if 0
387
      ws_dec_info(dec, data, "seeing opcode");
388
#endif
389
1.40k
      continue;
390
1.92k
    }
391
1.46k
    else if(dec->head_len == 1) {
392
1.39k
      dec->head[1] = *inbuf;
393
1.39k
      Curl_bufq_skip(inraw, 1);
394
1.39k
      dec->head_len = 2;
395
396
1.39k
      if(dec->head[1] & WSBIT_MASK) {
397
        /* A client MUST close a connection if it detects a masked frame. */
398
11
        failf(data, "[WS] masked input frame");
399
11
        ws_dec_reset(dec);
400
11
        return CURLE_RECV_ERROR;
401
11
      }
402
1.38k
      if(dec->frame_flags & CURLWS_PING && dec->head[1] > WS_MAX_CNTRL_LEN) {
403
        /* The maximum valid size of PING frames is 125 bytes.
404
           Accepting overlong pings would mean sending equivalent pongs! */
405
0
        failf(data, "[WS] received PING frame is too big");
406
0
        ws_dec_reset(dec);
407
0
        return CURLE_RECV_ERROR;
408
0
      }
409
1.38k
      if(dec->frame_flags & CURLWS_PONG && dec->head[1] > WS_MAX_CNTRL_LEN) {
410
        /* The maximum valid size of PONG frames is 125 bytes. */
411
0
        failf(data, "[WS] received PONG frame is too big");
412
0
        ws_dec_reset(dec);
413
0
        return CURLE_RECV_ERROR;
414
0
      }
415
1.38k
      if(dec->frame_flags & CURLWS_CLOSE && dec->head[1] > WS_MAX_CNTRL_LEN) {
416
0
        failf(data, "[WS] received CLOSE frame is too big");
417
0
        ws_dec_reset(dec);
418
0
        return CURLE_RECV_ERROR;
419
0
      }
420
421
      /* How long is the frame head? */
422
1.38k
      if(dec->head[1] == 126) {
423
16
        dec->head_total = 4;
424
16
        continue;
425
16
      }
426
1.36k
      else if(dec->head[1] == 127) {
427
5
        dec->head_total = 10;
428
5
        continue;
429
5
      }
430
1.36k
      else {
431
1.36k
        dec->head_total = 2;
432
1.36k
      }
433
1.38k
    }
434
435
1.43k
    if(dec->head_len < dec->head_total) {
436
70
      dec->head[dec->head_len] = *inbuf;
437
70
      Curl_bufq_skip(inraw, 1);
438
70
      ++dec->head_len;
439
70
      if(dec->head_len < dec->head_total) {
440
#if 0
441
        ws_dec_info(dec, data, "decoding head");
442
#endif
443
50
        continue;
444
50
      }
445
70
    }
446
    /* got the complete frame head */
447
1.38k
    DEBUGASSERT(dec->head_len == dec->head_total);
448
1.38k
    switch(dec->head_total) {
449
1.36k
    case 2:
450
1.36k
      dec->payload_len = dec->head[1];
451
1.36k
      break;
452
15
    case 4:
453
15
      dec->payload_len = (dec->head[2] << 8) | dec->head[3];
454
15
      break;
455
5
    case 10:
456
5
      if(dec->head[2] > 127) {
457
0
        failf(data, "[WS] frame length longer than 63 bits not supported");
458
0
        return CURLE_RECV_ERROR;
459
0
      }
460
5
      dec->payload_len =
461
5
        (curl_off_t)dec->head[2] << 56 |
462
5
        (curl_off_t)dec->head[3] << 48 |
463
5
        (curl_off_t)dec->head[4] << 40 |
464
5
        (curl_off_t)dec->head[5] << 32 |
465
5
        (curl_off_t)dec->head[6] << 24 |
466
5
        (curl_off_t)dec->head[7] << 16 |
467
5
        (curl_off_t)dec->head[8] <<  8 |
468
5
        dec->head[9];
469
5
      break;
470
0
    default:
471
      /* this should never happen */
472
0
      DEBUGASSERT(0);
473
0
      failf(data, "[WS] unexpected frame header length");
474
0
      return CURLE_RECV_ERROR;
475
1.38k
    }
476
477
1.38k
    dec->frame_age = 0;
478
1.38k
    dec->payload_offset = 0;
479
1.38k
    ws_dec_info(dec, data, "head");
480
1.38k
    return CURLE_OK;
481
1.38k
  }
482
91
  return CURLE_AGAIN;
483
2.00k
}
484
485
static CURLcode ws_dec_pass_payload(struct ws_decoder *dec,
486
                                    struct Curl_easy *data,
487
                                    struct bufq *inraw,
488
                                    ws_write_payload *write_cb,
489
                                    void *write_ctx)
490
473
{
491
473
  const uint8_t *inbuf;
492
473
  size_t inlen;
493
473
  size_t nwritten;
494
473
  CURLcode result;
495
473
  size_t remain = curlx_sotouz_range(dec->payload_len - dec->payload_offset,
496
473
                                     0, SIZE_MAX);
497
498
884
  while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen) &&
499
411
        !Curl_cwriter_is_paused(data)) {
500
411
    if(inlen > remain)
501
118
      inlen = remain;
502
411
    result = write_cb(inbuf, inlen, dec->frame_age, dec->frame_flags,
503
411
                      dec->payload_offset, dec->payload_len,
504
411
                      write_ctx, &nwritten);
505
411
    if(result)
506
0
      return result;
507
411
    Curl_bufq_skip(inraw, nwritten);
508
411
    dec->payload_offset += nwritten;
509
411
    remain = curlx_sotouz_range(dec->payload_len - dec->payload_offset,
510
411
                                0, SIZE_MAX);
511
411
    CURL_TRC_WS(data, "passed %zu bytes payload, %zu remain",
512
411
                nwritten, remain);
513
411
  }
514
515
473
  return remain ? CURLE_AGAIN : CURLE_OK;
516
473
}
517
518
static CURLcode ws_dec_pass(struct ws_decoder *dec,
519
                            struct Curl_easy *data,
520
                            struct bufq *inraw,
521
                            ws_write_payload *write_cb,
522
                            void *write_ctx)
523
2.24k
{
524
2.24k
  CURLcode result;
525
526
2.24k
  if(Curl_bufq_is_empty(inraw))
527
0
    return CURLE_AGAIN;
528
529
2.24k
  switch(dec->state) {
530
1.92k
  case WS_DEC_INIT:
531
1.92k
    ws_dec_next_frame(dec);
532
1.92k
    dec->state = WS_DEC_HEAD;
533
1.92k
    FALLTHROUGH();
534
2.00k
  case WS_DEC_HEAD:
535
2.00k
    result = ws_dec_read_head(dec, data, inraw);
536
2.00k
    if(result) {
537
624
      if(result != CURLE_AGAIN) {
538
533
        failf(data, "[WS] decode frame error %d", (int)result);
539
533
        break;  /* real error */
540
533
      }
541
      /* incomplete ws frame head */
542
91
      DEBUGASSERT(Curl_bufq_is_empty(inraw));
543
91
      break;
544
91
    }
545
    /* head parsing done */
546
1.38k
    dec->state = WS_DEC_PAYLOAD;
547
1.38k
    if(dec->payload_len == 0) {
548
1.14k
      size_t nwritten;
549
1.14k
      const uint8_t tmp = '\0';
550
      /* special case of a 0 length frame, need to write once */
551
1.14k
      result = write_cb(&tmp, 0, dec->frame_age, dec->frame_flags,
552
1.14k
                        0, 0, write_ctx, &nwritten);
553
1.14k
      if(result)
554
0
        return result;
555
1.14k
      dec->state = WS_DEC_INIT;
556
1.14k
      break;
557
1.14k
    }
558
238
    FALLTHROUGH();
559
473
  case WS_DEC_PAYLOAD:
560
473
    result = ws_dec_pass_payload(dec, data, inraw, write_cb, write_ctx);
561
473
    ws_dec_info(dec, data, "passing");
562
473
    if(result)
563
293
      return result;
564
    /* payload parsing done */
565
180
    dec->state = WS_DEC_INIT;
566
180
    break;
567
0
  default:
568
    /* we covered all enums above, but some code analyzers are wimps */
569
0
    result = CURLE_FAILED_INIT;
570
2.24k
  }
571
1.94k
  return result;
572
2.24k
}
573
574
static void update_meta(struct websocket *ws,
575
                        int frame_age, int frame_flags,
576
                        curl_off_t payload_offset,
577
                        curl_off_t payload_len,
578
                        size_t cur_len)
579
1.54k
{
580
1.54k
  curl_off_t bytesleft = (payload_len - payload_offset - cur_len);
581
582
1.54k
  ws->recvframe.age = frame_age;
583
1.54k
  ws->recvframe.flags = frame_flags;
584
1.54k
  ws->recvframe.offset = payload_offset;
585
1.54k
  ws->recvframe.len = cur_len;
586
1.54k
  ws->recvframe.bytesleft = bytesleft;
587
1.54k
}
588
589
/* WebSocket decoding client writer */
590
struct ws_cw_ctx {
591
  struct Curl_cwriter super;
592
  struct bufq buf;
593
};
594
595
static CURLcode ws_cw_init(struct Curl_easy *data,
596
                           struct Curl_cwriter *writer)
597
1.01k
{
598
1.01k
  struct ws_cw_ctx *ctx = writer->ctx;
599
1.01k
  (void)data;
600
1.01k
  Curl_bufq_init2(&ctx->buf, WS_CHUNK_SIZE, 1, BUFQ_OPT_SOFT_LIMIT);
601
1.01k
  return CURLE_OK;
602
1.01k
}
603
604
static void ws_cw_close(struct Curl_easy *data, struct Curl_cwriter *writer)
605
1.01k
{
606
1.01k
  struct ws_cw_ctx *ctx = writer->ctx;
607
1.01k
  (void)data;
608
1.01k
  Curl_bufq_free(&ctx->buf);
609
1.01k
}
610
611
struct ws_cw_dec_ctx {
612
  struct Curl_easy *data;
613
  struct websocket *ws;
614
  struct Curl_cwriter *next_writer;
615
  int cw_type;
616
};
617
618
static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
619
                         bool blocking);
620
static CURLcode ws_enc_send(struct Curl_easy *data,
621
                            struct websocket *ws,
622
                            const uint8_t *buffer,
623
                            size_t buflen,
624
                            curl_off_t fragsize,
625
                            unsigned int flags,
626
                            size_t *pnsent);
627
static CURLcode ws_enc_add_pending(struct Curl_easy *data,
628
                                   struct websocket *ws);
629
630
static CURLcode ws_enc_add_cntrl(struct Curl_easy *data,
631
                                 struct websocket *ws,
632
                                 const uint8_t *payload,
633
                                 size_t plen,
634
                                 unsigned int frame_type)
635
12
{
636
12
  (void)data;
637
12
  DEBUGASSERT(plen <= WS_MAX_CNTRL_LEN);
638
12
  if(plen > WS_MAX_CNTRL_LEN)
639
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
640
641
  /* Overwrite any pending frame with the new one, we keep
642
   * only one. */
643
12
  ws->pending.type = frame_type;
644
12
  ws->pending.payload_len = plen;
645
12
  memcpy(ws->pending.payload, payload, plen);
646
12
  return CURLE_OK;
647
12
}
648
649
static curl_off_t ws_payload_remain(curl_off_t payload_total,
650
                                    curl_off_t payload_offset,
651
                                    size_t payload_buffered)
652
1.55k
{
653
1.55k
  curl_off_t buffered, remain = payload_total - payload_offset;
654
1.55k
  if((payload_total < 0) || (payload_offset < 0) || (remain < 0))
655
0
    return -1;
656
1.55k
  buffered = curlx_uztoso(payload_buffered);
657
1.55k
  if(remain < buffered)
658
0
    return -1;
659
1.55k
  return remain - buffered;
660
1.55k
}
661
662
static CURLcode ws_cw_dec_next(const uint8_t *buf, size_t buflen,
663
                               int frame_age, int frame_flags,
664
                               curl_off_t payload_offset,
665
                               curl_off_t payload_len,
666
                               void *user_data,
667
                               size_t *pnwritten)
668
1.21k
{
669
1.21k
  struct ws_cw_dec_ctx *ctx = user_data;
670
1.21k
  struct Curl_easy *data = ctx->data;
671
1.21k
  struct websocket *ws = ctx->ws;
672
1.21k
  bool auto_pong = !data->set.ws_no_auto_pong;
673
1.21k
  curl_off_t remain;
674
1.21k
  CURLcode result;
675
676
1.21k
  (void)frame_age;
677
1.21k
  *pnwritten = 0;
678
1.21k
  remain = ws_payload_remain(payload_len, payload_offset, buflen);
679
1.21k
  if(remain < 0) {
680
0
    DEBUGASSERT(0); /* parameter mismatch */
681
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
682
0
  }
683
684
1.21k
  if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
685
    /* auto-respond to PINGs, only works for single-frame payloads atm */
686
7
    CURL_TRC_WS(data, "auto PONG to [PING payload=%" FMT_OFF_T
687
7
                "/%" FMT_OFF_T "]", payload_offset, payload_len);
688
    /* send back the exact same content as a PONG */
689
7
    result = ws_enc_add_cntrl(data, ws, buf, buflen, CURLWS_PONG);
690
7
    if(result)
691
0
      return result;
692
7
  }
693
1.20k
  else if(buflen || !remain) {
694
    /* forward the decoded frame to the next client writer. */
695
1.20k
    update_meta(ws, frame_age, frame_flags, payload_offset,
696
1.20k
                payload_len, buflen);
697
698
1.20k
    CURL_TRC_WRITE(data, "[WS] pass %zu decoded bytes", buflen);
699
1.20k
    result = Curl_cwriter_write(data, ctx->next_writer,
700
1.20k
                                (ctx->cw_type | CLIENTWRITE_0LEN),
701
1.20k
                                (const char *)buf, buflen);
702
1.20k
    if(result)
703
0
      return result;
704
1.20k
  }
705
1.21k
  *pnwritten = buflen;
706
1.21k
  return CURLE_OK;
707
1.21k
}
708
709
static CURLcode ws_cw_write(struct Curl_easy *data,
710
                            struct Curl_cwriter *writer, int type,
711
                            const char *buf, size_t nbytes)
712
866
{
713
866
  struct ws_cw_ctx *ctx = writer->ctx;
714
866
  struct websocket *ws;
715
866
  CURLcode result = CURLE_OK;
716
717
866
  CURL_TRC_WRITE(data, "[WS] write(len=%zu, type=%d)", nbytes, type);
718
866
  if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode)
719
67
    return Curl_cwriter_write(data, writer->next, type, buf, nbytes);
720
721
799
  ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
722
799
  if(!ws) {
723
0
    failf(data, "[WS] not a websocket transfer");
724
0
    return CURLE_FAILED_INIT;
725
0
  }
726
727
799
  if(nbytes) {
728
706
    size_t nwritten;
729
706
    result = Curl_bufq_write(&ctx->buf, (const uint8_t *)buf,
730
706
                             nbytes, &nwritten);
731
706
    if(result) {
732
0
      infof(data, "[WS] error adding data to buffer %d", (int)result);
733
0
      return result;
734
0
    }
735
706
  }
736
737
799
  result = Curl_cwriter_flush(data, writer->next);
738
799
  if(result)
739
0
    goto out;
740
741
1.83k
  while(!Curl_bufq_is_empty(&ctx->buf) && !Curl_cwriter_is_paused(data)) {
742
1.62k
    struct ws_cw_dec_ctx pass_ctx;
743
1.62k
    pass_ctx.data = data;
744
1.62k
    pass_ctx.ws = ws;
745
1.62k
    pass_ctx.next_writer = writer->next;
746
1.62k
    pass_ctx.cw_type = type;
747
1.62k
    result = ws_dec_pass(&ws->dec, data, &ctx->buf,
748
1.62k
                         ws_cw_dec_next, &pass_ctx);
749
1.62k
    if(result == CURLE_AGAIN) {
750
      /* insufficient amount of data, keep it for later.
751
       * we pretend to have written all since we have a copy */
752
294
      result = CURLE_OK;
753
294
      goto out;
754
294
    }
755
1.33k
    else if(result) {
756
297
      failf(data, "[WS] decode payload error %d", (int)result);
757
297
      Curl_bufq_reset(&ctx->buf);
758
297
      goto out;
759
297
    }
760
1.62k
  }
761
762
208
  if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) {
763
0
    failf(data, "[WS] decode ending with %zu frame bytes remaining",
764
0
          Curl_bufq_len(&ctx->buf));
765
0
    result = CURLE_RECV_ERROR;
766
0
  }
767
768
799
out:
769
799
  if(!result) {
770
502
    result = ws_flush(data, ws, Curl_api_is_in_callback(data));
771
502
    if(result == CURLE_AGAIN)
772
22
      result = CURLE_OK;
773
502
  }
774
799
  return result;
775
208
}
776
777
static CURLcode ws_cw_flush(struct Curl_easy *data,
778
                            struct Curl_cwriter *writer)
779
1.01k
{
780
1.01k
  CURLcode result = CURLE_OK;
781
782
1.01k
  CURL_TRC_WRITE(data, "[ws] flush");
783
1.01k
  if(!data->set.ws_raw_mode) {
784
966
    struct ws_cw_ctx *ctx = writer->ctx;
785
966
    struct websocket *ws;
786
787
    /* Frames should be written one by one, else the meta data does
788
     * not fit. Flush the next writer first, so it does not aggregate
789
     * our flushed data with anything it might have buffered. */
790
966
    result = Curl_cwriter_flush(data, writer->next);
791
966
    if(result)
792
0
      goto out;
793
794
966
    ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
795
966
    if(!ws) {
796
0
      failf(data, "[WS] not a websocket transfer");
797
0
      return CURLE_FAILED_INIT;
798
0
    }
799
800
966
    while(!Curl_bufq_is_empty(&ctx->buf) && !Curl_cwriter_is_paused(data)) {
801
0
      struct ws_cw_dec_ctx pass_ctx;
802
0
      pass_ctx.data = data;
803
0
      pass_ctx.ws = ws;
804
0
      pass_ctx.next_writer = writer->next;
805
0
      pass_ctx.cw_type = CLIENTWRITE_BODY;
806
0
      result = ws_dec_pass(&ws->dec, data, &ctx->buf,
807
0
                           ws_cw_dec_next, &pass_ctx);
808
0
      if(result == CURLE_AGAIN) {
809
        /* insufficient amount of data, keep it for later.
810
         * we pretend to have written all since we have a copy */
811
0
        result = CURLE_OK;
812
0
        goto out;
813
0
      }
814
0
      else if(result) {
815
0
        failf(data, "[WS] decode payload error %d", (int)result);
816
0
        Curl_bufq_reset(&ctx->buf);
817
0
        goto out;
818
0
      }
819
0
    }
820
966
  }
821
822
1.01k
out:
823
1.01k
  if(!result)
824
1.01k
    result = Curl_cwriter_flush(data, writer->next);
825
1.01k
  return result;
826
1.01k
}
827
828
/* WebSocket payload decoding client writer. */
829
static const struct Curl_cwtype ws_cw_decode = {
830
  "ws-decode",
831
  NULL,
832
  0,
833
  ws_cw_init,
834
  ws_cw_write,
835
  ws_cw_flush,
836
  ws_cw_close,
837
  sizeof(struct ws_cw_ctx)
838
};
839
840
static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data,
841
                        const char *msg)
842
848
{
843
848
  NOVERBOSE((void)enc);
844
848
  NOVERBOSE((void)msg);
845
848
  CURL_TRC_WS(data, "WS-ENC: %s [%s%s payload=%"
846
848
              FMT_OFF_T "/%" FMT_OFF_T "]",
847
848
              msg, ws_frame_name_of_op(enc->firstbyte),
848
848
              (enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN",
849
848
              enc->payload_len - enc->payload_remain, enc->payload_len);
850
848
}
851
852
static void ws_enc_reset(struct ws_encoder *enc)
853
1.01k
{
854
1.01k
  enc->payload_remain = 0;
855
1.01k
  enc->xori = 0;
856
1.01k
  enc->contfragment = FALSE;
857
1.01k
}
858
859
static void ws_enc_init(struct ws_encoder *enc)
860
1.01k
{
861
1.01k
  ws_enc_reset(enc);
862
1.01k
}
863
864
/* RFC 6455 Section 5.2
865
866
    0                   1                   2                   3
867
    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
868
   +-+-+-+-+-------+-+-------------+-------------------------------+
869
   |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
870
   |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
871
   |N|V|V|V|       |S|             |   (if payload len==126/127)   |
872
   | |1|2|3|       |K|             |                               |
873
   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
874
   |     Extended payload length continued, if payload len == 127  |
875
   + - - - - - - - - - - - - - - - +-------------------------------+
876
   |                               |Masking-key, if MASK set to 1  |
877
   +-------------------------------+-------------------------------+
878
   | Masking-key (continued)       |          Payload Data         |
879
   +-------------------------------- - - - - - - - - - - - - - - - +
880
   :                     Payload Data continued ...                :
881
   + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
882
   |                     Payload Data continued ...                |
883
   +---------------------------------------------------------------+
884
 */
885
886
static CURLcode ws_enc_add_frame(struct Curl_easy *data,
887
                                 struct ws_encoder *enc,
888
                                 unsigned int flags,
889
                                 curl_off_t payload_len,
890
                                 struct bufq *out)
891
701
{
892
701
  uint8_t firstb = 0;
893
701
  uint8_t head[14];
894
701
  CURLcode result;
895
701
  size_t hlen, nwritten;
896
897
701
  if(payload_len < 0) {
898
0
    failf(data, "[WS] starting new frame with negative payload length %"
899
0
                FMT_OFF_T, payload_len);
900
0
    return CURLE_SEND_ERROR;
901
0
  }
902
903
701
  if(enc->payload_remain > 0) {
904
    /* trying to write a new frame before the previous one is finished */
905
0
    failf(data, "[WS] starting new frame with %" FMT_OFF_T " bytes "
906
0
                "from last one remaining to be sent", enc->payload_remain);
907
0
    return CURLE_SEND_ERROR;
908
0
  }
909
910
701
  result = ws_frame_flags2firstbyte(data, flags, (bool)enc->contfragment,
911
701
                                    &firstb);
912
701
  if(result)
913
279
    return result;
914
915
  /* fragmentation only applies to data frames (text/binary);
916
   * control frames (close/ping/pong) do not affect the CONT status */
917
422
  if(flags & (CURLWS_TEXT | CURLWS_BINARY)) {
918
337
    enc->contfragment = (curl_bit)((flags & CURLWS_CONT) ? TRUE : FALSE);
919
337
  }
920
921
422
  if(flags & CURLWS_PING && payload_len > WS_MAX_CNTRL_LEN) {
922
0
    failf(data, "[WS] given PING frame is too big");
923
0
    return CURLE_TOO_LARGE;
924
0
  }
925
422
  if(flags & CURLWS_PONG && payload_len > WS_MAX_CNTRL_LEN) {
926
0
    failf(data, "[WS] given PONG frame is too big");
927
0
    return CURLE_TOO_LARGE;
928
0
  }
929
422
  if(flags & CURLWS_CLOSE && payload_len > WS_MAX_CNTRL_LEN) {
930
0
    failf(data, "[WS] given CLOSE frame is too big");
931
0
    return CURLE_TOO_LARGE;
932
0
  }
933
934
422
  head[0] = enc->firstbyte = firstb;
935
422
  if(payload_len > 65535) {
936
0
    head[1] = 127 | WSBIT_MASK;
937
0
    head[2] = (uint8_t)((payload_len >> 56) & 0xff);
938
0
    head[3] = (uint8_t)((payload_len >> 48) & 0xff);
939
0
    head[4] = (uint8_t)((payload_len >> 40) & 0xff);
940
0
    head[5] = (uint8_t)((payload_len >> 32) & 0xff);
941
0
    head[6] = (uint8_t)((payload_len >> 24) & 0xff);
942
0
    head[7] = (uint8_t)((payload_len >> 16) & 0xff);
943
0
    head[8] = (uint8_t)((payload_len >> 8) & 0xff);
944
0
    head[9] = (uint8_t)(payload_len & 0xff);
945
0
    hlen = 10;
946
0
  }
947
422
  else if(payload_len >= 126) {
948
112
    head[1] = 126 | WSBIT_MASK;
949
112
    head[2] = (uint8_t)((payload_len >> 8) & 0xff);
950
112
    head[3] = (uint8_t)(payload_len & 0xff);
951
112
    hlen = 4;
952
112
  }
953
310
  else {
954
310
    head[1] = (uint8_t)payload_len | WSBIT_MASK;
955
310
    hlen = 2;
956
310
  }
957
958
422
  enc->payload_remain = enc->payload_len = payload_len;
959
422
  ws_enc_info(enc, data, "sending");
960
961
  /* 4 bytes random */
962
963
422
  result = Curl_rand(data, (uint8_t *)&enc->mask, sizeof(enc->mask));
964
422
  if(result)
965
0
    return result;
966
967
422
#ifdef DEBUGBUILD
968
422
  if(getenv("CURL_WS_FORCE_ZERO_MASK"))
969
    /* force the bit mask to 0x00000000, effectively disabling masking */
970
0
    memset(&enc->mask, 0, sizeof(enc->mask));
971
422
#endif
972
973
  /* add 4 bytes mask */
974
422
  memcpy(&head[hlen], &enc->mask, 4);
975
422
  hlen += 4;
976
  /* reset for payload to come */
977
422
  enc->xori = 0;
978
979
422
  result = Curl_bufq_write(out, head, hlen, &nwritten);
980
422
  if(result)
981
0
    return result;
982
422
  if(nwritten != hlen) {
983
    /* We use a bufq with SOFT_LIMIT, writing should always succeed */
984
0
    DEBUGASSERT(0);
985
0
    return CURLE_SEND_ERROR;
986
0
  }
987
422
  return CURLE_OK;
988
422
}
989
990
static CURLcode ws_enc_write_head(struct Curl_easy *data,
991
                                  struct websocket *ws,
992
                                  struct ws_encoder *enc,
993
                                  unsigned int flags,
994
                                  curl_off_t payload_len,
995
                                  struct bufq *out)
996
689
{
997
  /* starting a new frame, we want a clean sendbuf.
998
   * Any pending control frame we can add now as part of the flush. */
999
689
  if(ws->pending.type) {
1000
3
    CURLcode result = ws_enc_add_pending(data, ws);
1001
3
    if(result)
1002
0
      return result;
1003
3
  }
1004
689
  return ws_enc_add_frame(data, enc, flags, payload_len, out);
1005
689
}
1006
1007
static CURLcode ws_enc_write_payload(struct ws_encoder *enc,
1008
                                     struct Curl_easy *data,
1009
                                     const uint8_t *buf, size_t buflen,
1010
                                     struct bufq *out, size_t *pnwritten)
1011
426
{
1012
426
  CURLcode result;
1013
426
  size_t i, len, n, remain;
1014
1015
426
  *pnwritten = 0;
1016
426
  if(Curl_bufq_is_full(out))
1017
0
    return CURLE_AGAIN;
1018
1019
  /* not the most performant way to do this */
1020
426
  len = buflen;
1021
426
  remain = curlx_sotouz_range(enc->payload_remain, 0, SIZE_MAX);
1022
426
  if(remain < len)
1023
0
    len = remain;
1024
1025
1.49M
  for(i = 0; i < len; ++i) {
1026
1.49M
    uint8_t c = buf[i] ^ enc->mask[enc->xori];
1027
1.49M
    result = Curl_bufq_write(out, &c, 1, &n);
1028
1.49M
    if(result) {
1029
0
      if((result != CURLE_AGAIN) || !i)
1030
0
        return result;
1031
0
      break;
1032
0
    }
1033
1.49M
    enc->xori++;
1034
1.49M
    enc->xori &= 3;
1035
1.49M
  }
1036
426
  *pnwritten = i;
1037
426
  enc->payload_remain -= (curl_off_t)i;
1038
426
  ws_enc_info(enc, data, "buffered");
1039
426
  return CURLE_OK;
1040
426
}
1041
1042
static CURLcode ws_enc_add_pending(struct Curl_easy *data,
1043
                                   struct websocket *ws)
1044
1.10k
{
1045
1.10k
  CURLcode result;
1046
1.10k
  size_t n;
1047
1048
1.10k
  if(!ws->pending.type) /* no pending frame here */
1049
1.09k
    return CURLE_OK;
1050
12
  if(ws->enc.payload_remain) /* in the middle of another frame */
1051
0
    return CURLE_AGAIN;
1052
1053
12
  result = ws_enc_add_frame(data, &ws->enc, ws->pending.type,
1054
12
                            (curl_off_t)ws->pending.payload_len,
1055
12
                            &ws->sendbuf);
1056
12
  if(result) {
1057
0
    CURL_TRC_WS(data, "ws_enc_cntrl(), error adding head: %d",
1058
0
                (int)result);
1059
0
    goto out;
1060
0
  }
1061
12
  result = ws_enc_write_payload(&ws->enc, data, ws->pending.payload,
1062
12
                                ws->pending.payload_len,
1063
12
                                &ws->sendbuf, &n);
1064
12
  if(result) {
1065
0
    CURL_TRC_WS(data, "ws_enc_cntrl(), error adding payload: %d",
1066
0
                (int)result);
1067
0
    goto out;
1068
0
  }
1069
12
  if(n != ws->pending.payload_len) {
1070
0
    DEBUGASSERT(0); /* buffer should always be able to take all */
1071
0
    CURL_TRC_WS(data, "ws_enc_cntrl(), error added only %zu/%zu payload,",
1072
0
                n, ws->pending.payload_len);
1073
0
    result = CURLE_SEND_ERROR;
1074
0
    goto out;
1075
0
  }
1076
  /* the frame should be complete now */
1077
12
  DEBUGASSERT(!ws->enc.payload_remain);
1078
12
  memset(&ws->pending, 0, sizeof(ws->pending));
1079
1080
12
out:
1081
12
  return result;
1082
12
}
1083
1084
static CURLcode ws_enc_send(struct Curl_easy *data,
1085
                            struct websocket *ws,
1086
                            const uint8_t *buffer,
1087
                            size_t buflen,
1088
                            curl_off_t fragsize,
1089
                            unsigned int flags,
1090
                            size_t *pnsent)
1091
467
{
1092
467
  size_t n;
1093
467
  CURLcode result = CURLE_OK;
1094
1095
467
  DEBUGASSERT(!data->set.ws_raw_mode);
1096
467
  *pnsent = 0;
1097
1098
467
  if(ws->enc.payload_remain || !Curl_bufq_is_empty(&ws->sendbuf)) {
1099
    /* a frame is ongoing with payload buffered or more payload
1100
     * that needs to be encoded into the buffer */
1101
268
    if(buflen < ws->sendbuf_payload) {
1102
      /* We have been called with LESS buffer data than before. This
1103
       * is not how it is supposed too work. */
1104
0
      failf(data, "[WS] curl_ws_send() called with smaller 'buflen' than "
1105
0
            "bytes already buffered in previous call, %zu vs %zu",
1106
0
            buflen, ws->sendbuf_payload);
1107
0
      return CURLE_BAD_FUNCTION_ARGUMENT;
1108
0
    }
1109
268
    if((curl_off_t)buflen >
1110
268
       (ws->enc.payload_remain + (curl_off_t)ws->sendbuf_payload)) {
1111
      /* too large buflen beyond payload length of frame */
1112
15
      failf(data, "[WS] unaligned frame size (sending %zu instead of "
1113
15
            "%" FMT_OFF_T ")", buflen,
1114
15
            (curl_off_t)(ws->enc.payload_remain + ws->sendbuf_payload));
1115
15
      return CURLE_BAD_FUNCTION_ARGUMENT;
1116
15
    }
1117
268
  }
1118
199
  else {
1119
199
    result = ws_flush(data, ws, Curl_api_is_in_callback(data));
1120
199
    if(result)
1121
0
      return result;
1122
1123
199
    result = ws_enc_write_head(data, ws, &ws->enc, flags,
1124
199
                               (flags & CURLWS_OFFSET) ?
1125
199
                               fragsize : (curl_off_t)buflen,
1126
199
                               &ws->sendbuf);
1127
199
    if(result) {
1128
198
      CURL_TRC_WS(data, "curl_ws_send(), error writing frame head %d",
1129
198
                  (int)result);
1130
198
      return result;
1131
198
    }
1132
199
  }
1133
1134
  /* While there is either sendbuf to flush OR more payload to encode... */
1135
458
  while(!Curl_bufq_is_empty(&ws->sendbuf) || (buflen > ws->sendbuf_payload)) {
1136
    /* Try to add more payload to sendbuf */
1137
254
    if(buflen > ws->sendbuf_payload) {
1138
209
      size_t prev_len = Curl_bufq_len(&ws->sendbuf);
1139
209
      result = ws_enc_write_payload(&ws->enc, data,
1140
209
                                    buffer + ws->sendbuf_payload,
1141
209
                                    buflen - ws->sendbuf_payload,
1142
209
                                    &ws->sendbuf, &n);
1143
209
      if(result && (result != CURLE_AGAIN))
1144
0
        return result;
1145
209
      ws->sendbuf_payload += Curl_bufq_len(&ws->sendbuf) - prev_len;
1146
209
      if(!ws->sendbuf_payload) {
1147
0
        return CURLE_AGAIN;
1148
0
      }
1149
209
    }
1150
1151
    /* flush, blocking when in callback */
1152
254
    result = ws_flush(data, ws, Curl_api_is_in_callback(data));
1153
254
    if(!result && ws->sendbuf_payload > 0) {
1154
204
      *pnsent += ws->sendbuf_payload;
1155
204
      buffer += ws->sendbuf_payload;
1156
204
      buflen -= ws->sendbuf_payload;
1157
204
      ws->sendbuf_payload = 0;
1158
204
    }
1159
50
    else if(result == CURLE_AGAIN) {
1160
50
      if(ws->sendbuf_payload > Curl_bufq_len(&ws->sendbuf)) {
1161
        /* blocked, part of payload bytes remain, report length
1162
         * that we managed to send. */
1163
0
        size_t flushed = (ws->sendbuf_payload - Curl_bufq_len(&ws->sendbuf));
1164
0
        *pnsent += flushed;
1165
0
        ws->sendbuf_payload -= flushed;
1166
0
        return CURLE_OK;
1167
0
      }
1168
50
      else {
1169
        /* blocked before sending headers or 1st payload byte. We cannot report
1170
         * OK on 0-length send (caller counts only payload) and EAGAIN */
1171
50
        CURL_TRC_WS(data, "EAGAIN flushing sendbuf, payload_encoded: %zu/%zu",
1172
50
                    ws->sendbuf_payload, buflen);
1173
50
        DEBUGASSERT(*pnsent == 0);
1174
50
        return CURLE_AGAIN;
1175
50
      }
1176
50
    }
1177
0
    else
1178
0
      return result;  /* real error sending the data */
1179
254
  }
1180
204
  return CURLE_OK;
1181
254
}
1182
1183
struct cr_ws_ctx {
1184
  struct Curl_creader super;
1185
  BIT(read_eos);  /* we read an EOS from the next reader */
1186
  BIT(eos);       /* we have returned an EOS */
1187
};
1188
1189
static CURLcode cr_ws_init(struct Curl_easy *data, struct Curl_creader *reader)
1190
174
{
1191
174
  (void)data;
1192
174
  (void)reader;
1193
174
  return CURLE_OK;
1194
174
}
1195
1196
static void cr_ws_close(struct Curl_easy *data, struct Curl_creader *reader)
1197
174
{
1198
174
  (void)data;
1199
174
  (void)reader;
1200
174
}
1201
1202
static CURLcode cr_ws_read(struct Curl_easy *data,
1203
                           struct Curl_creader *reader,
1204
                           char *buf, size_t blen,
1205
                           size_t *pnread, bool *peos)
1206
353
{
1207
353
  struct cr_ws_ctx *ctx = reader->ctx;
1208
353
  CURLcode result = CURLE_OK;
1209
353
  size_t nread, n;
1210
353
  struct websocket *ws;
1211
353
  bool eos;
1212
1213
353
  *pnread = 0;
1214
353
  if(ctx->eos) {
1215
0
    *peos = TRUE;
1216
0
    return CURLE_OK;
1217
0
  }
1218
1219
353
  ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1220
353
  if(!ws) {
1221
0
    failf(data, "[WS] not a websocket transfer");
1222
0
    return CURLE_FAILED_INIT;
1223
0
  }
1224
1225
353
  if(Curl_bufq_is_empty(&ws->sendbuf)) {
1226
340
    if(ctx->read_eos) {
1227
0
      ctx->eos = TRUE;
1228
0
      *peos = TRUE;
1229
0
      return CURLE_OK;
1230
0
    }
1231
1232
340
    if(ws->enc.payload_remain) {
1233
0
      CURL_TRC_WS(data, "current frame, %" FMT_OFF_T " remaining",
1234
0
                  ws->enc.payload_remain);
1235
0
      blen = curlx_sotouz_range(ws->enc.payload_remain, 0, blen);
1236
0
    }
1237
1238
340
    result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos);
1239
340
    if(result)
1240
6
      return result;
1241
334
    ctx->read_eos = eos;
1242
1243
334
    if(!Curl_bufq_is_empty(&ws->sendbuf)) {
1244
      /* client_read started a new frame, we disregard any eos reported */
1245
0
      ctx->read_eos = FALSE;
1246
0
      Curl_creader_clear_eos(data, reader->next);
1247
0
    }
1248
334
    else if(!nread) {
1249
      /* nothing to convert, return this right away */
1250
129
      if(ctx->read_eos)
1251
129
        ctx->eos = TRUE;
1252
129
      *pnread = nread;
1253
129
      *peos = (bool)ctx->eos;
1254
129
      goto out;
1255
129
    }
1256
1257
205
    if(!ws->enc.payload_remain && Curl_bufq_is_empty(&ws->sendbuf)) {
1258
      /* encode the data as a new BINARY frame */
1259
205
      result = ws_enc_write_head(data, ws, &ws->enc, CURLWS_BINARY, nread,
1260
205
                                 &ws->sendbuf);
1261
205
      if(result)
1262
0
        goto out;
1263
205
    }
1264
1265
205
    result = ws_enc_write_payload(&ws->enc, data, (uint8_t *)buf,
1266
205
                                  nread, &ws->sendbuf, &n);
1267
205
    if(result)
1268
0
      goto out;
1269
205
    CURL_TRC_READ(data, "cr_ws_read, added %zu payload, len=%zu", nread, n);
1270
205
  }
1271
1272
218
  DEBUGASSERT(!Curl_bufq_is_empty(&ws->sendbuf));
1273
218
  *peos = FALSE;
1274
218
  result = Curl_bufq_cread(&ws->sendbuf, buf, blen, pnread);
1275
218
  if(!result && ctx->read_eos && Curl_bufq_is_empty(&ws->sendbuf)) {
1276
    /* no more data, read all, done. */
1277
0
    ctx->eos = TRUE;
1278
0
    *peos = TRUE;
1279
0
  }
1280
1281
347
out:
1282
347
  CURL_TRC_READ(data, "cr_ws_read(len=%zu) -> %d, nread=%zu, eos=%d",
1283
347
                blen, (int)result, *pnread, *peos);
1284
347
  return result;
1285
218
}
1286
1287
static const struct Curl_crtype ws_cr_encode = {
1288
  "ws-encode",
1289
  cr_ws_init,
1290
  cr_ws_read,
1291
  cr_ws_close,
1292
  Curl_creader_def_needs_rewind,
1293
  Curl_creader_def_total_length,
1294
  Curl_creader_def_resume_from,
1295
  Curl_creader_def_cntrl,
1296
  Curl_creader_def_is_paused,
1297
  Curl_creader_def_done,
1298
  sizeof(struct cr_ws_ctx)
1299
};
1300
1301
struct wsfield {
1302
  const char *name;
1303
  const char *val;
1304
};
1305
1306
CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req)
1307
1.01k
{
1308
1.01k
  unsigned int i;
1309
1.01k
  CURLcode result = CURLE_OK;
1310
1.01k
  uint8_t rand[16];
1311
1.01k
  char *randstr;
1312
1.01k
  size_t randlen;
1313
1.01k
  char keyval[40];
1314
1.01k
  struct SingleRequest *k = &data->req;
1315
1.01k
  struct wsfield heads[] = {
1316
1.01k
    {
1317
      /* The request MUST contain an |Upgrade| header field whose value
1318
         MUST include the "websocket" keyword. */
1319
1.01k
      "Upgrade", "websocket"
1320
1.01k
    },
1321
1.01k
    {
1322
      /* The request MUST include a header field with the name
1323
         |Sec-WebSocket-Version|. The value of this header field MUST be
1324
         13. */
1325
1.01k
      "Sec-WebSocket-Version", "13",
1326
1.01k
    },
1327
1.01k
    {
1328
      /* The request MUST include a header field with the name
1329
         |Sec-WebSocket-Key|. The value of this header field MUST be a nonce
1330
         consisting of a randomly selected 16-byte value that has been
1331
         base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be
1332
         selected randomly for each connection. */
1333
1.01k
      "Sec-WebSocket-Key", NULL,
1334
1.01k
    }
1335
1.01k
  };
1336
1.01k
  heads[2].val = &keyval[0];
1337
1338
  /* 16 bytes random */
1339
1.01k
  result = Curl_rand(data, rand, sizeof(rand));
1340
1.01k
  if(result)
1341
0
    return result;
1342
1.01k
  result = curlx_base64_encode(rand, sizeof(rand), &randstr, &randlen);
1343
1.01k
  if(result)
1344
0
    return result;
1345
1.01k
  DEBUGASSERT(randlen < sizeof(keyval));
1346
1.01k
  if(randlen >= sizeof(keyval)) {
1347
0
    curlx_free(randstr);
1348
0
    return CURLE_FAILED_INIT;
1349
0
  }
1350
1.01k
  curlx_strcopy(keyval, sizeof(keyval), randstr, randlen);
1351
1.01k
  curlx_free(randstr);
1352
4.06k
  for(i = 0; !result && (i < CURL_ARRAYSIZE(heads)); i++) {
1353
3.04k
    if(!Curl_checkheaders(data, heads[i].name, strlen(heads[i].name))) {
1354
2.92k
      result = curlx_dyn_addf(req, "%s: %s\r\n", heads[i].name, heads[i].val);
1355
2.92k
    }
1356
3.04k
  }
1357
1.01k
  data->state.http_hd_upgrade = TRUE;
1358
1.01k
  k->upgr101 = UPGR101_WS;
1359
1.01k
  data->conn->bits.upgrade_in_progress = TRUE;
1360
1.01k
  return result;
1361
1.01k
}
1362
1363
static void ws_conn_dtor(void *key, size_t klen, void *entry)
1364
1.01k
{
1365
1.01k
  struct websocket *ws = entry;
1366
1.01k
  (void)key;
1367
1.01k
  (void)klen;
1368
1.01k
  Curl_bufq_free(&ws->recvbuf);
1369
1.01k
  Curl_bufq_free(&ws->sendbuf);
1370
1.01k
  curlx_free(ws);
1371
1.01k
}
1372
1373
/*
1374
 * 'nread' is number of bytes of websocket data already in the buffer at
1375
 * 'mem'.
1376
 */
1377
CURLcode Curl_ws_accept(struct Curl_easy *data,
1378
                        const char *mem, size_t nread)
1379
1.01k
{
1380
1.01k
  struct SingleRequest *k = &data->req;
1381
1.01k
  struct websocket *ws;
1382
1.01k
  struct Curl_cwriter *ws_dec_writer = NULL;
1383
1.01k
  struct Curl_creader *ws_enc_reader = NULL;
1384
1.01k
  CURLcode result;
1385
1386
1.01k
  DEBUGASSERT(data->conn);
1387
1.01k
  ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1388
1.01k
  if(!ws) {
1389
1.01k
    size_t chunk_size = WS_CHUNK_SIZE;
1390
1.01k
    ws = curlx_calloc(1, sizeof(*ws));
1391
1.01k
    if(!ws)
1392
0
      return CURLE_OUT_OF_MEMORY;
1393
1.01k
#ifdef DEBUGBUILD
1394
1.01k
    {
1395
1.01k
      const char *p = getenv("CURL_WS_CHUNK_SIZE");
1396
1.01k
      if(p) {
1397
0
        curl_off_t l;
1398
0
        if(!curlx_str_number(&p, &l, 1 * 1024 * 1024))
1399
0
          chunk_size = (size_t)l;
1400
0
      }
1401
1.01k
    }
1402
1.01k
#endif
1403
1.01k
    CURL_TRC_WS(data, "WS, using chunk size %zu", chunk_size);
1404
1.01k
    Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT,
1405
1.01k
                    BUFQ_OPT_SOFT_LIMIT);
1406
1.01k
    Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT,
1407
1.01k
                    BUFQ_OPT_SOFT_LIMIT);
1408
1.01k
    ws_dec_init(&ws->dec);
1409
1.01k
    ws_enc_init(&ws->enc);
1410
1.01k
    result = Curl_conn_meta_set(data->conn, CURL_META_PROTO_WS_CONN,
1411
1.01k
                                ws, ws_conn_dtor);
1412
1.01k
    if(result)
1413
0
      return result;
1414
1.01k
  }
1415
0
  else {
1416
0
    Curl_bufq_reset(&ws->recvbuf);
1417
0
    ws_dec_reset(&ws->dec);
1418
0
    ws_enc_reset(&ws->enc);
1419
0
  }
1420
  /* Verify the Sec-WebSocket-Accept response.
1421
1422
     The sent value is the base64 encoded version of a SHA-1 hash done on the
1423
     |Sec-WebSocket-Key| header field concatenated with
1424
     the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11". */
1425
1426
  /* If the response includes a |Sec-WebSocket-Extensions| header field and
1427
     this header field indicates the use of an extension that was not present
1428
     in the client's handshake (the server has indicated an extension not
1429
     requested by the client), the client MUST Fail the WebSocket Connection.
1430
   */
1431
1432
  /* If the response includes a |Sec-WebSocket-Protocol| header field
1433
     and this header field indicates the use of a subprotocol that was
1434
     not present in the client's handshake (the server has indicated a
1435
     subprotocol not requested by the client), the client MUST Fail
1436
     the WebSocket Connection. */
1437
1438
1.01k
  infof(data, "[WS] Received 101, switch to WebSocket");
1439
1440
  /* Install our client writer that decodes WS frames payload */
1441
1.01k
  result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode,
1442
1.01k
                               CURL_CW_CONTENT_DECODE);
1443
1.01k
  if(result)
1444
0
    goto out;
1445
1.01k
  result = Curl_cwriter_add(data, ws_dec_writer);
1446
1.01k
  if(result)
1447
0
    goto out;
1448
1.01k
  ws_dec_writer = NULL; /* owned by transfer now */
1449
1450
1.01k
  k->header = FALSE; /* we will not get more response headers */
1451
1452
1.01k
  if(data->set.connect_only) {
1453
    /* In CONNECT_ONLY setup, the payloads from `mem` need to be received
1454
     * when using `curl_ws_recv/curl_easy_recv` later on, after this transfer
1455
     * is already marked as DONE.
1456
     * Since `curl_easy_recv()` is also supposed to work, we need
1457
     * to buffer the data at connection level. See #22107 */
1458
65
    if(nread) {
1459
0
      result = Curl_cf_recvbuf_add(data, data->conn, FIRSTSOCKET,
1460
0
                                   (const uint8_t *)mem, nread);
1461
0
      if(result)
1462
0
        goto out;
1463
0
    }
1464
65
    CURL_REQ_CLEAR_RECV(data); /* read no more content */
1465
65
  }
1466
949
  else { /* !connect_only */
1467
949
    if(data->set.method == HTTPREQ_PUT) {
1468
181
      CURL_TRC_WS(data, "UPLOAD set, add ws-encode reader");
1469
181
      result = Curl_creader_set_fread(data, -1);
1470
181
      if(result)
1471
0
        goto out;
1472
1473
181
      if(!data->set.ws_raw_mode) {
1474
        /* Add our client reader encoding WS BINARY frames */
1475
174
        result = Curl_creader_create(&ws_enc_reader, data, &ws_cr_encode,
1476
174
                                     CURL_CR_CONTENT_ENCODE);
1477
174
        if(result)
1478
0
          goto out;
1479
174
        result = Curl_creader_add(data, ws_enc_reader);
1480
174
        if(result)
1481
0
          goto out;
1482
174
        ws_enc_reader = NULL; /* owned by transfer now */
1483
174
      }
1484
1485
      /* start over with sending */
1486
181
      data->req.eos_read = FALSE;
1487
181
      data->req.upload_done = FALSE;
1488
181
      CURL_REQ_SET_SEND(data);
1489
181
    }
1490
1491
    /* Then pass any additional data to the writers */
1492
949
    if(nread) {
1493
396
      result = Curl_client_write(data, CLIENTWRITE_BODY, mem, nread);
1494
396
      if(result)
1495
235
        goto out;
1496
396
    }
1497
949
  }
1498
1499
779
  k->upgr101 = UPGR101_RECEIVED;
1500
779
  k->header = FALSE; /* we will not get more responses */
1501
1502
1.01k
out:
1503
1.01k
  if(ws_dec_writer)
1504
0
    Curl_cwriter_free(data, ws_dec_writer);
1505
1.01k
  if(ws_enc_reader)
1506
0
    Curl_creader_free(data, ws_enc_reader);
1507
1.01k
  if(result)
1508
235
    CURL_TRC_WS(data, "Curl_ws_accept() failed -> %d", (int)result);
1509
779
  else
1510
779
    CURL_TRC_WS(data, "websocket established, %s mode",
1511
1.01k
                data->set.connect_only ? "connect-only" : "callback");
1512
1.01k
  return result;
1513
779
}
1514
1515
struct ws_collect {
1516
  struct Curl_easy *data;
1517
  struct websocket *ws;
1518
  uint8_t *buffer;
1519
  size_t buflen;
1520
  size_t bufidx;
1521
  int frame_age;
1522
  int frame_flags;
1523
  curl_off_t payload_offset;
1524
  curl_off_t payload_len;
1525
  bool written;
1526
};
1527
1528
static CURLcode ws_client_collect(const uint8_t *buf, size_t buflen,
1529
                                  int frame_age, int frame_flags,
1530
                                  curl_off_t payload_offset,
1531
                                  curl_off_t payload_len,
1532
                                  void *userp,
1533
                                  size_t *pnwritten)
1534
344
{
1535
344
  struct ws_collect *ctx = userp;
1536
344
  struct Curl_easy *data = ctx->data;
1537
344
  bool auto_pong = !data->set.ws_no_auto_pong;
1538
344
  curl_off_t remain;
1539
344
  CURLcode result = CURLE_OK;
1540
1541
344
  *pnwritten = 0;
1542
344
  remain = ws_payload_remain(payload_len, payload_offset, buflen);
1543
344
  if(remain < 0) {
1544
0
    DEBUGASSERT(0); /* parameter mismatch */
1545
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
1546
0
  }
1547
1548
344
  if(!ctx->bufidx) {
1549
    /* first write */
1550
344
    ctx->frame_age = frame_age;
1551
344
    ctx->frame_flags = frame_flags;
1552
344
    ctx->payload_offset = payload_offset;
1553
344
    ctx->payload_len = payload_len;
1554
344
  }
1555
1556
344
  if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
1557
    /* auto-respond to PINGs, only works for single-frame payloads atm */
1558
5
    CURL_TRC_WS(data, "auto PONG to [PING payload=%" FMT_OFF_T
1559
5
                "/%" FMT_OFF_T "]", payload_offset, payload_len);
1560
    /* send back the exact same content as a PONG */
1561
5
    result = ws_enc_add_cntrl(ctx->data, ctx->ws, buf, buflen, CURLWS_PONG);
1562
5
    if(result)
1563
0
      return result;
1564
5
    *pnwritten = buflen;
1565
5
  }
1566
339
  else {
1567
339
    size_t write_len;
1568
1569
339
    ctx->written = TRUE;
1570
339
    DEBUGASSERT(ctx->buflen >= ctx->bufidx);
1571
339
    write_len = CURLMIN(buflen, ctx->buflen - ctx->bufidx);
1572
339
    if(!write_len) {
1573
242
      if(!buflen)  /* 0 length write, we accept that */
1574
242
        return CURLE_OK;
1575
0
      return CURLE_AGAIN;  /* no more space */
1576
242
    }
1577
97
    memcpy(ctx->buffer + ctx->bufidx, buf, write_len);
1578
97
    ctx->bufidx += write_len;
1579
97
    *pnwritten = write_len;
1580
97
  }
1581
102
  return result;
1582
344
}
1583
1584
static CURLcode nw_in_recv(void *reader_ctx,
1585
                           uint8_t *buf, size_t buflen,
1586
                           size_t *pnread)
1587
349
{
1588
349
  struct Curl_easy *data = reader_ctx;
1589
349
  return Curl_easy_recv(data, buf, buflen, pnread);
1590
349
}
1591
1592
CURLcode curl_ws_recv(CURL *curl, void *buffer,
1593
                      size_t buflen, size_t *recv,
1594
                      const struct curl_ws_frame **metap)
1595
745
{
1596
745
  struct Curl_eapi_guard guard;
1597
745
  CURLcode result = CURLE_OK;
1598
1599
745
  *recv = 0;
1600
745
  *metap = NULL;
1601
745
  if(CURL_EAPI_ENTER(&guard, curl, ws_recv, &result)) {
1602
745
    struct Curl_easy *data = curl;
1603
745
    struct connectdata *conn;
1604
745
    struct websocket *ws;
1605
745
    struct ws_collect ctx;
1606
1607
745
    if(buflen && !buffer) {
1608
0
      result = CURLE_BAD_FUNCTION_ARGUMENT;
1609
0
      goto out;
1610
0
    }
1611
1612
745
    conn = data->conn;
1613
745
    if(!conn) {
1614
      /* Unhappy hack with lifetimes of transfers and connection */
1615
81
      if(!data->set.connect_only) {
1616
0
        failf(data, "[WS] CONNECT_ONLY is required");
1617
0
        result = CURLE_UNSUPPORTED_PROTOCOL;
1618
0
        goto out;
1619
0
      }
1620
1621
81
      Curl_getconnectinfo(data, &conn);
1622
81
      if(!conn) {
1623
22
        failf(data, "[WS] connection not found");
1624
22
        result = CURLE_BAD_FUNCTION_ARGUMENT;
1625
22
        goto out;
1626
22
      }
1627
81
    }
1628
723
    ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN);
1629
723
    if(!ws) {
1630
0
      failf(data, "[WS] connection is not setup for websocket");
1631
0
      result = CURLE_BAD_FUNCTION_ARGUMENT;
1632
0
      goto out;
1633
0
    }
1634
1635
723
    memset(&ctx, 0, sizeof(ctx));
1636
723
    ctx.data = data;
1637
723
    ctx.ws = ws;
1638
723
    ctx.buffer = buffer;
1639
723
    ctx.buflen = buflen;
1640
1641
759
    while(1) {
1642
      /* receive more when our buffer is empty */
1643
759
      if(Curl_bufq_is_empty(&ws->recvbuf)) {
1644
349
        size_t n;
1645
349
        result = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &n);
1646
349
        if(result)
1647
148
          goto out;
1648
201
        else if(n == 0) {
1649
          /* connection closed */
1650
0
          infof(data, "[WS] connection expectedly closed?");
1651
0
          result = CURLE_GOT_NOTHING;
1652
0
          goto out;
1653
0
        }
1654
201
        CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network",
1655
201
                    Curl_bufq_len(&ws->recvbuf));
1656
201
      }
1657
1658
611
      result = ws_dec_pass(&ws->dec, data, &ws->recvbuf,
1659
611
                           ws_client_collect, &ctx);
1660
611
      if(result == CURLE_AGAIN) {
1661
90
        if(!ctx.written) {
1662
31
          ws_dec_info(&ws->dec, data, "need more input");
1663
31
          continue;  /* nothing written, try more input */
1664
31
        }
1665
59
        break;
1666
90
      }
1667
521
      else if(result) {
1668
236
        goto out;
1669
236
      }
1670
285
      else if(ctx.written) {
1671
        /* The decoded frame is passed back to our caller.
1672
         * There are frames like PING were we auto-respond to and
1673
         * that we do not return. For these `ctx.written` is not set. */
1674
280
        break;
1675
280
      }
1676
611
    }
1677
1678
    /* update frame information to be passed back */
1679
339
    update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset,
1680
339
                ctx.payload_len, ctx.bufidx);
1681
339
    *metap = &ws->recvframe;
1682
339
    *recv = ws->recvframe.len;
1683
339
    CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %"
1684
339
                FMT_OFF_T ", %" FMT_OFF_T " left)",
1685
339
                buflen, *recv, ws->recvframe.offset,
1686
339
                ws->recvframe.bytesleft);
1687
    /* all's well, try to send any pending control. we do not know
1688
     * when the application will call `curl_ws_send()` again. */
1689
339
    if(!data->set.ws_raw_mode && ws->pending.type) {
1690
2
      CURLcode r2 = ws_enc_add_pending(data, ws);
1691
2
      if(!r2)
1692
2
        (void)ws_flush(data, ws, Curl_api_is_in_callback(data));
1693
2
    }
1694
339
    result = CURLE_OK;
1695
339
  }
1696
745
out:
1697
745
  CURL_EAPI_LEAVE(&guard);
1698
745
  return result;
1699
745
}
1700
1701
static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
1702
                         bool blocking)
1703
1.09k
{
1704
1.09k
  CURLcode result;
1705
1706
  /* If there is space, add any pending control frame */
1707
1.09k
  if(Curl_bufq_len(&ws->sendbuf) < ws->sendbuf.chunk_size) {
1708
1.09k
    result = ws_enc_add_pending(data, ws);
1709
1.09k
    if(result && (result != CURLE_AGAIN))
1710
0
      return result;
1711
1.09k
  }
1712
1713
1.09k
  if(!Curl_bufq_is_empty(&ws->sendbuf)) {
1714
286
    const uint8_t *out;
1715
286
    size_t outlen, n;
1716
286
#ifdef DEBUGBUILD
1717
    /* Simulate a blocking send after this chunk has been sent */
1718
286
    bool eagain_next = FALSE;
1719
286
    size_t chunk_egain = 0;
1720
286
    const char *p = getenv("CURL_WS_CHUNK_EAGAIN");
1721
286
    if(p) {
1722
0
      curl_off_t l;
1723
0
      if(!curlx_str_number(&p, &l, 1 * 1024 * 1024))
1724
0
        chunk_egain = (size_t)l;
1725
0
    }
1726
286
#endif
1727
1728
500
    while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) {
1729
286
#ifdef DEBUGBUILD
1730
286
      if(eagain_next)
1731
0
        return CURLE_AGAIN;
1732
286
      if(chunk_egain && (outlen > chunk_egain)) {
1733
0
        outlen = chunk_egain;
1734
0
        eagain_next = TRUE;
1735
0
      }
1736
286
#endif
1737
286
      if(blocking) {
1738
1
        result = ws_send_raw_blocking(data, ws, (const char *)out, outlen);
1739
1
        n = result ? 0 : outlen;
1740
1
      }
1741
285
      else if(data->set.connect_only || Curl_api_is_in_callback(data))
1742
256
        result = Curl_senddata(data, out, outlen, &n);
1743
29
      else {
1744
29
        result = Curl_xfer_send(data, out, outlen, FALSE, &n);
1745
29
        if(!result && !n && outlen)
1746
22
          result = CURLE_AGAIN;
1747
29
      }
1748
1749
286
      if(result == CURLE_AGAIN) {
1750
72
        CURL_TRC_WS(data, "flush EAGAIN, %zu bytes remain in buffer",
1751
72
                    Curl_bufq_len(&ws->sendbuf));
1752
72
        return result;
1753
72
      }
1754
214
      else if(result) {
1755
0
        failf(data, "[WS] flush, write error %d", (int)result);
1756
0
        return result;
1757
0
      }
1758
214
      else {
1759
214
        CURL_TRC_WS(data, "flushed %zu bytes", n);
1760
214
        Curl_bufq_skip(&ws->sendbuf, n);
1761
214
      }
1762
286
    }
1763
286
  }
1764
1.02k
  return CURLE_OK;
1765
1.09k
}
1766
1767
static CURLcode ws_send_raw_blocking(struct Curl_easy *data,
1768
                                     struct websocket *ws,
1769
                                     const char *buffer, size_t buflen)
1770
23
{
1771
23
  CURLcode result = CURLE_OK;
1772
23
  size_t nwritten;
1773
1774
23
  if(!data)
1775
0
    return result;
1776
1777
23
  (void)ws;
1778
47
  while(buflen) {
1779
46
    result = Curl_xfer_send(data, buffer, buflen, FALSE, &nwritten);
1780
46
    if(result)
1781
0
      return result;
1782
46
    DEBUGASSERT(nwritten <= buflen);
1783
46
    buffer += nwritten;
1784
46
    buflen -= nwritten;
1785
46
    if(buflen) {
1786
45
      curl_socket_t sock = data->conn->sock[FIRSTSOCKET];
1787
45
      timediff_t left_ms;
1788
45
      int ev;
1789
1790
45
      CURL_TRC_WS(data, "ws_send_raw_blocking() partial, %zu left to send",
1791
45
                  buflen);
1792
45
      left_ms = Curl_timeleft_ms(data);
1793
45
      if(left_ms < 0) {
1794
22
        failf(data, "[WS] Timeout waiting for socket becoming writable");
1795
22
        return CURLE_SEND_ERROR;
1796
22
      }
1797
1798
      /* POLLOUT socket */
1799
23
      if(sock == CURL_SOCKET_BAD)
1800
0
        return CURLE_SEND_ERROR;
1801
23
      ev = SOCKET_WRITABLE(sock, left_ms ? left_ms : 500);
1802
23
      if(ev < 0) {
1803
0
        failf(data, "[WS] Error while waiting for socket becoming writable");
1804
0
        return CURLE_SEND_ERROR;
1805
0
      }
1806
23
    }
1807
46
  }
1808
1
  return result;
1809
23
}
1810
1811
static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer,
1812
                            size_t buflen, size_t *pnwritten)
1813
34
{
1814
34
  struct websocket *ws;
1815
34
  CURLcode result;
1816
1817
34
  ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1818
34
  if(!ws) {
1819
0
    failf(data, "[WS] Not a websocket transfer");
1820
0
    return CURLE_SEND_ERROR;
1821
0
  }
1822
34
  if(!buflen)
1823
0
    return CURLE_OK;
1824
1825
34
  if(Curl_api_is_in_callback(data)) {
1826
    /* When invoked from inside callbacks, we do a blocking send as the
1827
     * callback will probably not implement partial writes that may then
1828
     * mess up the ws framing subsequently.
1829
     * We need any pending data to be flushed before sending. */
1830
22
    result = ws_flush(data, ws, TRUE);
1831
22
    if(result)
1832
0
      return result;
1833
22
    result = ws_send_raw_blocking(data, ws, buffer, buflen);
1834
22
    if(!result)
1835
0
      *pnwritten = buflen;
1836
22
  }
1837
12
  else {
1838
    /* We need any pending data to be sent or EAGAIN this call. */
1839
12
    result = ws_flush(data, ws, FALSE);
1840
12
    if(result)
1841
0
      return result;
1842
12
    result = Curl_senddata(data, buffer, buflen, pnwritten);
1843
12
  }
1844
1845
34
  CURL_TRC_WS(data, "ws_send_raw(len=%zu) -> %d, %zu",
1846
34
              buflen, (int)result, *pnwritten);
1847
34
  return result;
1848
34
}
1849
1850
CURLcode curl_ws_send(CURL *curl, const void *buffer_arg,
1851
                      size_t buflen, size_t *sent,
1852
                      curl_off_t fragsize,
1853
                      unsigned int flags)
1854
597
{
1855
597
  struct Curl_eapi_guard guard;
1856
597
  CURLcode result = CURLE_OK;
1857
1858
597
  if(CURL_EAPI_ENTER(&guard, curl, ws_send, &result)) {
1859
597
    struct websocket *ws;
1860
597
    const uint8_t *buffer = buffer_arg;
1861
597
    struct Curl_easy *data = curl;
1862
597
    size_t ndummy;
1863
597
    size_t *pnsent = sent ? sent : &ndummy;
1864
1865
597
    CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T
1866
597
                ", flags=%x), raw=%d",
1867
597
                buflen, fragsize, flags, data->set.ws_raw_mode);
1868
1869
597
    *pnsent = 0;
1870
1871
597
    if(!buffer && buflen) {
1872
0
      failf(data, "[WS] buffer is NULL when buflen is not");
1873
0
      result = CURLE_BAD_FUNCTION_ARGUMENT;
1874
0
      goto out;
1875
0
    }
1876
1877
597
    if(!data->conn && data->set.connect_only) {
1878
6
      result = Curl_connect_only_attach(data);
1879
6
      if(result)
1880
6
        goto out;
1881
6
    }
1882
591
    if(!data->conn) {
1883
0
      failf(data, "[WS] No associated connection");
1884
0
      result = CURLE_SEND_ERROR;
1885
0
      goto out;
1886
0
    }
1887
591
    ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1888
591
    if(!ws) {
1889
16
      failf(data, "[WS] Not a websocket transfer");
1890
16
      result = CURLE_SEND_ERROR;
1891
16
      goto out;
1892
16
    }
1893
1894
575
    if(data->set.ws_raw_mode) {
1895
      /* In raw mode, we write directly to the connection */
1896
      /* try flushing any content still waiting to be sent. */
1897
108
      result = ws_flush(data, ws, FALSE);
1898
108
      if(result)
1899
0
        goto out;
1900
1901
108
      if(!buffer) {
1902
0
        failf(data, "[WS] buffer is NULL in raw mode");
1903
0
        result = CURLE_BAD_FUNCTION_ARGUMENT;
1904
0
        goto out;
1905
0
      }
1906
108
      if(!sent) {
1907
0
        failf(data, "[WS] sent is NULL in raw mode");
1908
0
        result = CURLE_BAD_FUNCTION_ARGUMENT;
1909
0
        goto out;
1910
0
      }
1911
108
      if(fragsize || flags) {
1912
74
        failf(data, "[WS] fragsize and flags must be zero in raw mode");
1913
74
        result = CURLE_BAD_FUNCTION_ARGUMENT;
1914
74
        goto out;
1915
74
      }
1916
34
      result = ws_send_raw(data, buffer, buflen, pnsent);
1917
34
      goto out;
1918
108
    }
1919
1920
    /* Not RAW mode, we do the frame encoding */
1921
467
    result = ws_enc_send(data, ws, buffer, buflen, fragsize, flags, pnsent);
1922
467
    CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T
1923
467
                ", flags=%x, raw=%d) -> %d, %zu",
1924
467
                buflen, fragsize, flags, data->set.ws_raw_mode, (int)result,
1925
467
                *pnsent);
1926
467
  }
1927
597
out:
1928
597
  CURL_EAPI_LEAVE(&guard);
1929
597
  return result;
1930
597
}
1931
1932
static CURLcode ws_setup_conn(struct Curl_easy *data,
1933
                              struct connectdata *conn)
1934
1.03k
{
1935
  /* WebSocket is 1.1 only (for now) */
1936
1.03k
  data->state.http_neg.accept_09 = FALSE;
1937
1.03k
  data->state.http_neg.only_10 = FALSE;
1938
1.03k
  data->state.http_neg.wanted = CURL_HTTP_V1x;
1939
1.03k
  data->state.http_neg.allowed = CURL_HTTP_V1x;
1940
1.03k
  return Curl_http_setup_conn(data, conn);
1941
1.03k
}
1942
1943
const struct curl_ws_frame *curl_ws_meta(CURL *curl)
1944
1.33k
{
1945
  /* we only return something for websocket, called from within the callback
1946
     when not using raw mode */
1947
1.33k
  struct Curl_easy *data = curl;
1948
1.33k
  if(GOOD_EASY_HANDLE(data) && Curl_api_is_in_callback(data) &&
1949
1.33k
     data->conn && !data->set.ws_raw_mode) {
1950
1.26k
    struct websocket *ws;
1951
1.26k
    ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1952
1.26k
    if(ws)
1953
1.20k
      return &ws->recvframe;
1954
1.26k
  }
1955
130
  return NULL;
1956
1.33k
}
1957
1958
CURL_EXTERN CURLcode curl_ws_start_frame(CURL *curl,
1959
                                         unsigned int flags,
1960
                                         curl_off_t frame_len)
1961
395
{
1962
395
  struct Curl_eapi_guard guard;
1963
395
  CURLcode result = CURLE_OK;
1964
1965
395
  if(CURL_EAPI_ENTER(&guard, curl, ws_start_frame, &result)) {
1966
395
    struct Curl_easy *data = curl;
1967
395
    struct websocket *ws;
1968
1969
395
    if(data->set.ws_raw_mode) {
1970
79
      failf(data, "cannot curl_ws_start_frame() with CURLWS_RAW_MODE enabled");
1971
79
      result = CURLE_FAILED_INIT;
1972
79
      goto out;
1973
79
    }
1974
1975
316
    CURL_TRC_WS(data, "curl_ws_start_frame(flags=%x, frame_len=%" FMT_OFF_T,
1976
316
                flags, frame_len);
1977
1978
316
    if(!data->conn) {
1979
2
      failf(data, "[WS] No associated connection");
1980
2
      result = CURLE_SEND_ERROR;
1981
2
      goto out;
1982
2
    }
1983
314
    ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1984
314
    if(!ws) {
1985
0
      failf(data, "[WS] Not a websocket transfer");
1986
0
      result = CURLE_SEND_ERROR;
1987
0
      goto out;
1988
0
    }
1989
1990
314
    if(ws->enc.payload_remain) {
1991
29
      failf(data, "[WS] previous frame not finished");
1992
29
      result = CURLE_SEND_ERROR;
1993
29
      goto out;
1994
29
    }
1995
1996
285
    result = ws_enc_write_head(data, ws, &ws->enc, flags, frame_len,
1997
285
                               &ws->sendbuf);
1998
285
    if(result)
1999
81
      CURL_TRC_WS(data, "curl_start_frame(), error adding frame head %d",
2000
285
                  (int)result);
2001
285
  }
2002
395
out:
2003
395
  CURL_EAPI_LEAVE(&guard);
2004
395
  return result;
2005
395
}
2006
2007
const struct Curl_protocol Curl_protocol_ws = {
2008
  ws_setup_conn,                        /* setup_connection */
2009
  Curl_http,                            /* do_it */
2010
  Curl_http_done,                       /* done */
2011
  ZERO_NULL,                            /* do_more */
2012
  ZERO_NULL,                            /* connect_it */
2013
  ZERO_NULL,                            /* connecting */
2014
  ZERO_NULL,                            /* doing */
2015
  ZERO_NULL,                            /* proto_pollset */
2016
  Curl_http_doing_pollset,              /* doing_pollset */
2017
  ZERO_NULL,                            /* domore_pollset */
2018
  Curl_http_perform_pollset,            /* perform_pollset */
2019
  ZERO_NULL,                            /* disconnect */
2020
  Curl_http_write_resp,                 /* write_resp */
2021
  Curl_http_write_resp_hd,              /* write_resp_hd */
2022
  ZERO_NULL,                            /* connection_is_dead */
2023
  ZERO_NULL,                            /* attach connection */
2024
  Curl_http_follow,                     /* follow */
2025
};
2026
2027
#else
2028
2029
CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
2030
                      size_t *recv,
2031
                      const struct curl_ws_frame **metap)
2032
{
2033
  (void)curl;
2034
  (void)buffer;
2035
  (void)buflen;
2036
  (void)recv;
2037
  (void)metap;
2038
  return CURLE_NOT_BUILT_IN;
2039
}
2040
2041
CURLcode curl_ws_send(CURL *curl, const void *buffer,
2042
                      size_t buflen, size_t *sent,
2043
                      curl_off_t fragsize,
2044
                      unsigned int flags)
2045
{
2046
  (void)curl;
2047
  (void)buffer;
2048
  (void)buflen;
2049
  (void)sent;
2050
  (void)fragsize;
2051
  (void)flags;
2052
  return CURLE_NOT_BUILT_IN;
2053
}
2054
2055
const struct curl_ws_frame *curl_ws_meta(CURL *data)
2056
{
2057
  (void)data;
2058
  return NULL;
2059
}
2060
2061
CURL_EXTERN CURLcode curl_ws_start_frame(CURL *curl,
2062
                                         unsigned int flags,
2063
                                         curl_off_t frame_len)
2064
{
2065
  (void)curl;
2066
  (void)flags;
2067
  (void)frame_len;
2068
  return CURLE_NOT_BUILT_IN;
2069
}
2070
2071
#endif /* !CURL_DISABLE_WEBSOCKETS */