Coverage Report

Created: 2026-08-13 07:42

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