Coverage Report

Created: 2026-09-04 07:16

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
4.59k
#define WSBIT_FIN          0x80
56
402
#define WSBIT_RSV1         0x40
57
402
#define WSBIT_RSV2         0x20
58
402
#define WSBIT_RSV3         0x10
59
402
#define WSBIT_RSV_MASK     (WSBIT_RSV1 | WSBIT_RSV2 | WSBIT_RSV3)
60
10.8k
#define WSBIT_OPCODE_CONT  0x0
61
1.46k
#define WSBIT_OPCODE_TEXT  0x1
62
1.00k
#define WSBIT_OPCODE_BIN   0x2
63
338
#define WSBIT_OPCODE_CLOSE 0x8
64
1.49k
#define WSBIT_OPCODE_PING  0x9
65
1.34k
#define WSBIT_OPCODE_PONG  0xa
66
#ifdef CURLVERBOSE
67
0
#define WSBIT_OPCODE_MASK  0xf
68
#endif
69
70
15.7k
#define WSBIT_MASK 0x80
71
72
/* buffer dimensioning */
73
3.95k
#define WS_CHUNK_SIZE  65535
74
3.95k
#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
4.39k
#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
14.4k
{
153
14.4k
  switch(firstbyte) {
154
  /* 0x00 - intermediate TEXT/BINARY fragment */
155
10.0k
  case WSBIT_OPCODE_CONT:
156
10.0k
    if(!(cont_flags & CURLWS_CONT)) {
157
315
      failf(data, "[WS] no ongoing fragmented message to resume");
158
315
      return 0;
159
315
    }
160
9.71k
    return cont_flags | CURLWS_CONT;
161
  /* 0x80 - final TEXT/BIN fragment */
162
534
  case (WSBIT_OPCODE_CONT | WSBIT_FIN):
163
534
    if(!(cont_flags & CURLWS_CONT)) {
164
28
      failf(data, "[WS] no ongoing fragmented message to resume");
165
28
      return 0;
166
28
    }
167
506
    return cont_flags & ~CURLWS_CONT;
168
  /* 0x01 - first TEXT fragment */
169
982
  case WSBIT_OPCODE_TEXT:
170
982
    if(cont_flags & CURLWS_CONT) {
171
51
      failf(data, "[WS] fragmented message interrupted by new TEXT msg");
172
51
      return 0;
173
51
    }
174
931
    return CURLWS_TEXT | CURLWS_CONT;
175
  /* 0x81 - unfragmented TEXT msg */
176
107
  case (WSBIT_OPCODE_TEXT | WSBIT_FIN):
177
107
    if(cont_flags & CURLWS_CONT) {
178
13
      failf(data, "[WS] fragmented message interrupted by new TEXT msg");
179
13
      return 0;
180
13
    }
181
94
    return CURLWS_TEXT;
182
  /* 0x02 - first BINARY fragment */
183
403
  case WSBIT_OPCODE_BIN:
184
403
    if(cont_flags & CURLWS_CONT) {
185
54
      failf(data, "[WS] fragmented message interrupted by new BINARY msg");
186
54
      return 0;
187
54
    }
188
349
    return CURLWS_BINARY | CURLWS_CONT;
189
  /* 0x82 - unfragmented BINARY msg */
190
122
  case (WSBIT_OPCODE_BIN | WSBIT_FIN):
191
122
    if(cont_flags & CURLWS_CONT) {
192
18
      failf(data, "[WS] fragmented message interrupted by new BINARY msg");
193
18
      return 0;
194
18
    }
195
104
    return CURLWS_BINARY;
196
  /* 0x08 - first CLOSE fragment */
197
41
  case WSBIT_OPCODE_CLOSE:
198
41
    failf(data, "[WS] invalid fragmented CLOSE frame");
199
41
    return 0;
200
  /* 0x88 - unfragmented CLOSE */
201
231
  case (WSBIT_OPCODE_CLOSE | WSBIT_FIN):
202
231
    return CURLWS_CLOSE;
203
  /* 0x09 - first PING fragment */
204
39
  case WSBIT_OPCODE_PING:
205
39
    failf(data, "[WS] invalid fragmented PING frame");
206
39
    return 0;
207
  /* 0x89 - unfragmented PING */
208
1.38k
  case (WSBIT_OPCODE_PING | WSBIT_FIN):
209
1.38k
    return CURLWS_PING;
210
  /* 0x0a - first PONG fragment */
211
40
  case WSBIT_OPCODE_PONG:
212
40
    failf(data, "[WS] invalid fragmented PONG frame");
213
40
    return 0;
214
  /* 0x8a - unfragmented PONG */
215
99
  case (WSBIT_OPCODE_PONG | WSBIT_FIN):
216
99
    return CURLWS_PONG;
217
  /* invalid first byte */
218
402
  default:
219
402
    if(firstbyte & WSBIT_RSV_MASK)
220
      /* any of the reserved bits 0x40/0x20/0x10 are set */
221
322
      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
402
    return 0;
226
14.4k
  }
227
14.4k
}
228
229
static CURLcode ws_frame_flags2firstbyte(struct Curl_easy *data,
230
                                         unsigned int flags,
231
                                         bool contfragment,
232
                                         uint8_t *pfirstbyte)
233
3.50k
{
234
3.50k
  *pfirstbyte = 0;
235
3.50k
  switch(flags & ~CURLWS_OFFSET) {
236
547
  case 0:
237
547
    if(contfragment) {
238
84
      CURL_TRC_WS(data, "no flags given; interpreting as continuation "
239
84
                  "fragment for compatibility");
240
84
      *pfirstbyte = (WSBIT_OPCODE_CONT | WSBIT_FIN);
241
84
      return CURLE_OK;
242
84
    }
243
463
    failf(data, "[WS] no flags given");
244
463
    return CURLE_BAD_FUNCTION_ARGUMENT;
245
231
  case CURLWS_CONT:
246
231
    if(contfragment) {
247
78
      infof(data, "[WS] setting CURLWS_CONT flag without message type is "
248
78
                  "supported for compatibility but highly discouraged");
249
78
      *pfirstbyte = WSBIT_OPCODE_CONT;
250
78
      return CURLE_OK;
251
78
    }
252
153
    failf(data, "[WS] No ongoing fragmented message to continue");
253
153
    return CURLE_BAD_FUNCTION_ARGUMENT;
254
295
  case CURLWS_TEXT:
255
295
    *pfirstbyte = contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN)
256
295
                               : (WSBIT_OPCODE_TEXT | WSBIT_FIN);
257
295
    return CURLE_OK;
258
79
  case (CURLWS_TEXT | CURLWS_CONT):
259
79
    *pfirstbyte = contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_TEXT;
260
79
    return CURLE_OK;
261
399
  case CURLWS_BINARY:
262
399
    *pfirstbyte = contfragment ? (WSBIT_OPCODE_CONT | WSBIT_FIN)
263
399
                               : (WSBIT_OPCODE_BIN | WSBIT_FIN);
264
399
    return CURLE_OK;
265
72
  case (CURLWS_BINARY | CURLWS_CONT):
266
72
    *pfirstbyte = contfragment ? WSBIT_OPCODE_CONT : WSBIT_OPCODE_BIN;
267
72
    return CURLE_OK;
268
66
  case CURLWS_CLOSE:
269
66
    *pfirstbyte = WSBIT_OPCODE_CLOSE | WSBIT_FIN;
270
66
    return CURLE_OK;
271
128
  case (CURLWS_CLOSE | CURLWS_CONT):
272
128
    failf(data, "[WS] CLOSE frame must not be fragmented");
273
128
    return CURLE_BAD_FUNCTION_ARGUMENT;
274
69
  case CURLWS_PING:
275
69
    *pfirstbyte = WSBIT_OPCODE_PING | WSBIT_FIN;
276
69
    return CURLE_OK;
277
135
  case (CURLWS_PING | CURLWS_CONT):
278
135
    failf(data, "[WS] PING frame must not be fragmented");
279
135
    return CURLE_BAD_FUNCTION_ARGUMENT;
280
1.20k
  case CURLWS_PONG:
281
1.20k
    *pfirstbyte = WSBIT_OPCODE_PONG | WSBIT_FIN;
282
1.20k
    return CURLE_OK;
283
139
  case (CURLWS_PONG | CURLWS_CONT):
284
139
    failf(data, "[WS] PONG frame must not be fragmented");
285
139
    return CURLE_BAD_FUNCTION_ARGUMENT;
286
141
  default:
287
141
    failf(data, "[WS] unknown flags: %x", flags);
288
141
    return CURLE_BAD_FUNCTION_ARGUMENT;
289
3.50k
  }
290
3.50k
}
291
292
static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data,
293
                        const char *msg)
294
16.6k
{
295
16.6k
  NOVERBOSE((void)msg);
296
16.6k
  switch(dec->head_len) {
297
0
  case 0:
298
0
    break;
299
133
  case 1:
300
133
    CURL_TRC_WS(data, "decoded %s [%s%s]", msg,
301
133
                ws_frame_name_of_op(dec->head[0]),
302
133
                (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL");
303
133
    break;
304
16.4k
  default:
305
16.4k
    if(dec->head_len < dec->head_total) {
306
360
      CURL_TRC_WS(data, "decoded %s [%s%s](%d/%d)", msg,
307
360
                  ws_frame_name_of_op(dec->head[0]),
308
360
                  (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
309
360
                  dec->head_len, dec->head_total);
310
360
    }
311
16.1k
    else {
312
16.1k
      CURL_TRC_WS(data, "decoded %s [%s%s payload=%"
313
16.1k
                  FMT_OFF_T "/%" FMT_OFF_T "]",
314
16.1k
                  msg, ws_frame_name_of_op(dec->head[0]),
315
16.1k
                  (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
316
16.1k
                  dec->payload_offset, dec->payload_len);
317
16.1k
    }
318
16.4k
    break;
319
16.6k
  }
320
16.6k
}
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
14.4k
{
335
14.4k
  dec->frame_age = 0;
336
14.4k
  dec->frame_flags = 0;
337
14.4k
  dec->payload_offset = 0;
338
14.4k
  dec->payload_len = 0;
339
14.4k
  dec->head_len = dec->head_total = 0;
340
14.4k
  dec->state = WS_DEC_INIT;
341
  /* dec->cont_flags must be carried over to next frame */
342
14.4k
}
343
344
static void ws_dec_reset(struct ws_decoder *dec)
345
3.05k
{
346
3.05k
  dec->frame_age = 0;
347
3.05k
  dec->frame_flags = 0;
348
3.05k
  dec->payload_offset = 0;
349
3.05k
  dec->payload_len = 0;
350
3.05k
  dec->head_len = dec->head_total = 0;
351
3.05k
  dec->state = WS_DEC_INIT;
352
3.05k
  dec->cont_flags = 0;
353
3.05k
}
354
355
static void ws_dec_init(struct ws_decoder *dec)
356
1.97k
{
357
1.97k
  ws_dec_reset(dec);
358
1.97k
}
359
360
static CURLcode ws_dec_read_head(struct ws_decoder *dec,
361
                                 struct Curl_easy *data,
362
                                 struct bufq *inraw)
363
15.4k
{
364
15.4k
  const uint8_t *inbuf;
365
15.4k
  size_t inlen;
366
367
32.1k
  while(Curl_bufq_peek(inraw, &inbuf, &inlen)) {
368
31.0k
    if(dec->head_len == 0) {
369
14.4k
      dec->head[0] = *inbuf;
370
14.4k
      Curl_bufq_skip(inraw, 1);
371
372
14.4k
      dec->frame_flags = ws_frame_firstbyte2flags(data, dec->head[0],
373
14.4k
                                                  dec->cont_flags);
374
14.4k
      if(!dec->frame_flags) {
375
1.00k
        ws_dec_reset(dec);
376
1.00k
        return CURLE_RECV_ERROR;
377
1.00k
      }
378
379
      /* fragmentation only applies to data frames (text/binary);
380
       * control frames (close/ping/pong) do not affect the CONT status */
381
13.4k
      if(dec->frame_flags & (CURLWS_TEXT | CURLWS_BINARY)) {
382
11.6k
        dec->cont_flags = dec->frame_flags;
383
11.6k
      }
384
385
13.4k
      dec->head_len = 1;
386
#if 0
387
      ws_dec_info(dec, data, "seeing opcode");
388
#endif
389
13.4k
      continue;
390
14.4k
    }
391
16.6k
    else if(dec->head_len == 1) {
392
13.3k
      dec->head[1] = *inbuf;
393
13.3k
      Curl_bufq_skip(inraw, 1);
394
13.3k
      dec->head_len = 2;
395
396
13.3k
      if(dec->head[1] & WSBIT_MASK) {
397
        /* A client MUST close a connection if it detects a masked frame. */
398
50
        failf(data, "[WS] masked input frame");
399
50
        ws_dec_reset(dec);
400
50
        return CURLE_RECV_ERROR;
401
50
      }
402
13.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
19
        failf(data, "[WS] received PING frame is too big");
406
19
        ws_dec_reset(dec);
407
19
        return CURLE_RECV_ERROR;
408
19
      }
409
13.2k
      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
1
        failf(data, "[WS] received PONG frame is too big");
412
1
        ws_dec_reset(dec);
413
1
        return CURLE_RECV_ERROR;
414
1
      }
415
13.2k
      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
13.2k
      if(dec->head[1] == 126) {
423
194
        dec->head_total = 4;
424
194
        continue;
425
194
      }
426
13.0k
      else if(dec->head[1] == 127) {
427
371
        dec->head_total = 10;
428
371
        continue;
429
371
      }
430
12.7k
      else {
431
12.7k
        dec->head_total = 2;
432
12.7k
      }
433
13.2k
    }
434
435
16.0k
    if(dec->head_len < dec->head_total) {
436
3.28k
      dec->head[dec->head_len] = *inbuf;
437
3.28k
      Curl_bufq_skip(inraw, 1);
438
3.28k
      ++dec->head_len;
439
3.28k
      if(dec->head_len < dec->head_total) {
440
#if 0
441
        ws_dec_info(dec, data, "decoding head");
442
#endif
443
2.73k
        continue;
444
2.73k
      }
445
3.28k
    }
446
    /* got the complete frame head */
447
13.2k
    DEBUGASSERT(dec->head_len == dec->head_total);
448
13.2k
    switch(dec->head_total) {
449
12.7k
    case 2:
450
12.7k
      dec->payload_len = dec->head[1];
451
12.7k
      break;
452
193
    case 4:
453
193
      dec->payload_len = (dec->head[2] << 8) | dec->head[3];
454
193
      break;
455
383
    case 10:
456
383
      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
349
      dec->payload_len =
461
349
        (curl_off_t)dec->head[2] << 56 |
462
349
        (curl_off_t)dec->head[3] << 48 |
463
349
        (curl_off_t)dec->head[4] << 40 |
464
349
        (curl_off_t)dec->head[5] << 32 |
465
349
        (curl_off_t)dec->head[6] << 24 |
466
349
        (curl_off_t)dec->head[7] << 16 |
467
349
        (curl_off_t)dec->head[8] <<  8 |
468
349
        dec->head[9];
469
349
      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
13.2k
    }
476
477
13.2k
    dec->frame_age = 0;
478
13.2k
    dec->payload_offset = 0;
479
13.2k
    ws_dec_info(dec, data, "head");
480
13.2k
    return CURLE_OK;
481
13.2k
  }
482
1.09k
  return CURLE_AGAIN;
483
15.4k
}
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.79k
{
491
2.79k
  const uint8_t *inbuf;
492
2.79k
  size_t inlen;
493
2.79k
  size_t nwritten;
494
2.79k
  CURLcode result;
495
2.79k
  size_t remain = curlx_sotouz_range(dec->payload_len - dec->payload_offset,
496
2.79k
                                     0, SIZE_MAX);
497
498
5.41k
  while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen) &&
499
2.63k
        !Curl_cwriter_is_paused(data)) {
500
2.63k
    if(inlen > remain)
501
1.25k
      inlen = remain;
502
2.63k
    result = write_cb(inbuf, inlen, dec->frame_age, dec->frame_flags,
503
2.63k
                      dec->payload_offset, dec->payload_len,
504
2.63k
                      write_ctx, &nwritten);
505
2.63k
    if(result)
506
13
      return result;
507
2.61k
    Curl_bufq_skip(inraw, nwritten);
508
2.61k
    dec->payload_offset += nwritten;
509
2.61k
    remain = curlx_sotouz_range(dec->payload_len - dec->payload_offset,
510
2.61k
                                0, SIZE_MAX);
511
2.61k
    CURL_TRC_WS(data, "passed %zu bytes payload, %zu remain",
512
2.61k
                nwritten, remain);
513
2.61k
  }
514
515
2.78k
  return remain ? CURLE_AGAIN : CURLE_OK;
516
2.79k
}
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
16.4k
{
524
16.4k
  CURLcode result;
525
526
16.4k
  if(Curl_bufq_is_empty(inraw))
527
0
    return CURLE_AGAIN;
528
529
16.4k
  switch(dec->state) {
530
14.4k
  case WS_DEC_INIT:
531
14.4k
    ws_dec_next_frame(dec);
532
14.4k
    dec->state = WS_DEC_HEAD;
533
14.4k
    FALLTHROUGH();
534
15.4k
  case WS_DEC_HEAD:
535
15.4k
    result = ws_dec_read_head(dec, data, inraw);
536
15.4k
    if(result) {
537
2.20k
      if(result != CURLE_AGAIN) {
538
1.11k
        failf(data, "[WS] decode frame error %d", (int)result);
539
1.11k
        break;  /* real error */
540
1.11k
      }
541
      /* incomplete ws frame head */
542
1.09k
      DEBUGASSERT(Curl_bufq_is_empty(inraw));
543
1.09k
      break;
544
1.09k
    }
545
    /* head parsing done */
546
13.2k
    dec->state = WS_DEC_PAYLOAD;
547
13.2k
    if(dec->payload_len == 0) {
548
11.4k
      size_t nwritten;
549
11.4k
      const uint8_t tmp = '\0';
550
      /* special case of a 0 length frame, need to write once */
551
11.4k
      result = write_cb(&tmp, 0, dec->frame_age, dec->frame_flags,
552
11.4k
                        0, 0, write_ctx, &nwritten);
553
11.4k
      if(result)
554
0
        return result;
555
11.4k
      dec->state = WS_DEC_INIT;
556
11.4k
      break;
557
11.4k
    }
558
1.85k
    FALLTHROUGH();
559
2.79k
  case WS_DEC_PAYLOAD:
560
2.79k
    result = ws_dec_pass_payload(dec, data, inraw, write_cb, write_ctx);
561
2.79k
    ws_dec_info(dec, data, "passing");
562
2.79k
    if(result)
563
1.40k
      return result;
564
    /* payload parsing done */
565
1.39k
    dec->state = WS_DEC_INIT;
566
1.39k
    break;
567
0
  default:
568
    /* we covered all enums above, but some code analyzers are wimps */
569
0
    result = CURLE_FAILED_INIT;
570
16.4k
  }
571
15.0k
  return result;
572
16.4k
}
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
12.6k
{
580
12.6k
  curl_off_t bytesleft = (payload_len - payload_offset - cur_len);
581
582
12.6k
  ws->recvframe.age = frame_age;
583
12.6k
  ws->recvframe.flags = frame_flags;
584
12.6k
  ws->recvframe.offset = payload_offset;
585
12.6k
  ws->recvframe.len = cur_len;
586
12.6k
  ws->recvframe.bytesleft = bytesleft;
587
12.6k
}
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.97k
{
598
1.97k
  struct ws_cw_ctx *ctx = writer->ctx;
599
1.97k
  (void)data;
600
1.97k
  Curl_bufq_init2(&ctx->buf, WS_CHUNK_SIZE, 1, BUFQ_OPT_SOFT_LIMIT);
601
1.97k
  return CURLE_OK;
602
1.97k
}
603
604
static void ws_cw_close(struct Curl_easy *data, struct Curl_cwriter *writer)
605
1.97k
{
606
1.97k
  struct ws_cw_ctx *ctx = writer->ctx;
607
1.97k
  (void)data;
608
1.97k
  Curl_bufq_free(&ctx->buf);
609
1.97k
}
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
1.34k
{
636
1.34k
  (void)data;
637
1.34k
  DEBUGASSERT(plen <= WS_MAX_CNTRL_LEN);
638
1.34k
  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
1.34k
  ws->pending.type = frame_type;
644
1.34k
  ws->pending.payload_len = plen;
645
1.34k
  memcpy(ws->pending.payload, payload, plen);
646
1.34k
  return CURLE_OK;
647
1.34k
}
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
14.0k
{
653
14.0k
  curl_off_t buffered, remain = payload_total - payload_offset;
654
14.0k
  if((payload_total < 0) || (payload_offset < 0) || (remain < 0))
655
0
    return -1;
656
14.0k
  buffered = curlx_uztoso(payload_buffered);
657
14.0k
  if(remain < buffered)
658
0
    return -1;
659
14.0k
  return remain - buffered;
660
14.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
5.14k
{
669
5.14k
  struct ws_cw_dec_ctx *ctx = user_data;
670
5.14k
  struct Curl_easy *data = ctx->data;
671
5.14k
  struct websocket *ws = ctx->ws;
672
5.14k
  bool auto_pong = !data->set.ws_no_auto_pong;
673
5.14k
  curl_off_t remain;
674
5.14k
  CURLcode result;
675
676
5.14k
  (void)frame_age;
677
5.14k
  *pnwritten = 0;
678
5.14k
  remain = ws_payload_remain(payload_len, payload_offset, buflen);
679
5.14k
  if(remain < 0) {
680
0
    DEBUGASSERT(0); /* parameter mismatch */
681
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
682
0
  }
683
684
5.14k
  if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
685
    /* auto-respond to PINGs, only works for single-frame payloads atm */
686
236
    CURL_TRC_WS(data, "auto PONG to [PING payload=%" FMT_OFF_T
687
236
                "/%" FMT_OFF_T "]", payload_offset, payload_len);
688
    /* send back the exact same content as a PONG */
689
236
    result = ws_enc_add_cntrl(data, ws, buf, buflen, CURLWS_PONG);
690
236
    if(result)
691
0
      return result;
692
236
  }
693
4.90k
  else if(buflen || !remain) {
694
    /* forward the decoded frame to the next client writer. */
695
4.90k
    update_meta(ws, frame_age, frame_flags, payload_offset,
696
4.90k
                payload_len, buflen);
697
698
4.90k
    CURL_TRC_WRITE(data, "[WS] pass %zu decoded bytes", buflen);
699
4.90k
    result = Curl_cwriter_write(data, ctx->next_writer,
700
4.90k
                                (ctx->cw_type | CLIENTWRITE_0LEN),
701
4.90k
                                (const char *)buf, buflen);
702
4.90k
    if(result)
703
0
      return result;
704
4.90k
  }
705
5.14k
  *pnwritten = buflen;
706
5.14k
  return CURLE_OK;
707
5.14k
}
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.38k
{
713
2.38k
  struct ws_cw_ctx *ctx = writer->ctx;
714
2.38k
  struct websocket *ws;
715
2.38k
  CURLcode result = CURLE_OK;
716
717
2.38k
  CURL_TRC_WRITE(data, "[WS] write(len=%zu, type=%d)", nbytes, type);
718
2.38k
  if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode)
719
175
    return Curl_cwriter_write(data, writer->next, type, buf, nbytes);
720
721
2.21k
  ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
722
2.21k
  if(!ws) {
723
0
    failf(data, "[WS] not a websocket transfer");
724
0
    return CURLE_FAILED_INIT;
725
0
  }
726
727
2.21k
  if(nbytes) {
728
1.83k
    size_t nwritten;
729
1.83k
    result = Curl_bufq_write(&ctx->buf, (const uint8_t *)buf,
730
1.83k
                             nbytes, &nwritten);
731
1.83k
    if(result) {
732
0
      infof(data, "[WS] error adding data to buffer %d", (int)result);
733
0
      return result;
734
0
    }
735
1.83k
  }
736
737
2.21k
  result = Curl_cwriter_flush(data, writer->next);
738
2.21k
  if(result)
739
0
    goto out;
740
741
6.67k
  while(!Curl_bufq_is_empty(&ctx->buf) && !Curl_cwriter_is_paused(data)) {
742
6.03k
    struct ws_cw_dec_ctx pass_ctx;
743
6.03k
    pass_ctx.data = data;
744
6.03k
    pass_ctx.ws = ws;
745
6.03k
    pass_ctx.next_writer = writer->next;
746
6.03k
    pass_ctx.cw_type = type;
747
6.03k
    result = ws_dec_pass(&ws->dec, data, &ctx->buf,
748
6.03k
                         ws_cw_dec_next, &pass_ctx);
749
6.03k
    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.38k
      result = CURLE_OK;
753
1.38k
      goto out;
754
1.38k
    }
755
4.65k
    else if(result) {
756
194
      failf(data, "[WS] decode payload error %d", (int)result);
757
194
      Curl_bufq_reset(&ctx->buf);
758
194
      goto out;
759
194
    }
760
6.03k
  }
761
762
637
  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
2.21k
out:
769
2.21k
  if(!result) {
770
2.01k
    result = ws_flush(data, ws, Curl_api_is_in_callback(data));
771
2.01k
    if(result == CURLE_AGAIN)
772
36
      result = CURLE_OK;
773
2.01k
  }
774
2.21k
  return result;
775
637
}
776
777
static CURLcode ws_cw_flush(struct Curl_easy *data,
778
                            struct Curl_cwriter *writer)
779
1.97k
{
780
1.97k
  CURLcode result = CURLE_OK;
781
782
1.97k
  CURL_TRC_WRITE(data, "[ws] flush");
783
1.97k
  if(!data->set.ws_raw_mode) {
784
1.93k
    struct ws_cw_ctx *ctx = writer->ctx;
785
1.93k
    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
1.93k
    result = Curl_cwriter_flush(data, writer->next);
791
1.93k
    if(result)
792
0
      goto out;
793
794
1.93k
    ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
795
1.93k
    if(!ws) {
796
0
      failf(data, "[WS] not a websocket transfer");
797
0
      return CURLE_FAILED_INIT;
798
0
    }
799
800
1.93k
    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
1.93k
  }
821
822
1.97k
out:
823
1.97k
  if(!result)
824
1.97k
    result = Curl_cwriter_flush(data, writer->next);
825
1.97k
  return result;
826
1.97k
}
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
4.71k
{
843
4.71k
  NOVERBOSE((void)enc);
844
4.71k
  NOVERBOSE((void)msg);
845
4.71k
  CURL_TRC_WS(data, "WS-ENC: %s [%s%s payload=%"
846
4.71k
              FMT_OFF_T "/%" FMT_OFF_T "]",
847
4.71k
              msg, ws_frame_name_of_op(enc->firstbyte),
848
4.71k
              (enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN",
849
4.71k
              enc->payload_len - enc->payload_remain, enc->payload_len);
850
4.71k
}
851
852
static void ws_enc_reset(struct ws_encoder *enc)
853
1.97k
{
854
1.97k
  enc->payload_remain = 0;
855
1.97k
  enc->xori = 0;
856
1.97k
  enc->contfragment = FALSE;
857
1.97k
}
858
859
static void ws_enc_init(struct ws_encoder *enc)
860
1.97k
{
861
1.97k
  ws_enc_reset(enc);
862
1.97k
}
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
3.50k
{
892
3.50k
  uint8_t firstb = 0;
893
3.50k
  uint8_t head[14];
894
3.50k
  CURLcode result;
895
3.50k
  size_t hlen, nwritten;
896
897
3.50k
  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
3.50k
  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
3.50k
  result = ws_frame_flags2firstbyte(data, flags, (bool)enc->contfragment,
911
3.50k
                                    &firstb);
912
3.50k
  if(result)
913
1.15k
    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
2.35k
  if(flags & (CURLWS_TEXT | CURLWS_BINARY)) {
918
845
    enc->contfragment = (curl_bit)((flags & CURLWS_CONT) ? TRUE : FALSE);
919
845
  }
920
921
2.35k
  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
2.35k
  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
2.35k
  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
2.35k
  head[0] = enc->firstbyte = firstb;
935
2.35k
  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
2.35k
  else if(payload_len >= 126) {
948
138
    head[1] = 126 | WSBIT_MASK;
949
138
    head[2] = (uint8_t)((payload_len >> 8) & 0xff);
950
138
    head[3] = (uint8_t)(payload_len & 0xff);
951
138
    hlen = 4;
952
138
  }
953
2.21k
  else {
954
2.21k
    head[1] = (uint8_t)payload_len | WSBIT_MASK;
955
2.21k
    hlen = 2;
956
2.21k
  }
957
958
2.35k
  enc->payload_remain = enc->payload_len = payload_len;
959
2.35k
  ws_enc_info(enc, data, "sending");
960
961
  /* 4 bytes random */
962
963
2.35k
  result = Curl_rand(data, (uint8_t *)&enc->mask, sizeof(enc->mask));
964
2.35k
  if(result)
965
0
    return result;
966
967
2.35k
#ifdef DEBUGBUILD
968
2.35k
  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
2.35k
#endif
972
973
  /* add 4 bytes mask */
974
2.35k
  memcpy(&head[hlen], &enc->mask, 4);
975
2.35k
  hlen += 4;
976
  /* reset for payload to come */
977
2.35k
  enc->xori = 0;
978
979
2.35k
  result = Curl_bufq_write(out, head, hlen, &nwritten);
980
2.35k
  if(result)
981
0
    return result;
982
2.35k
  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
2.35k
  return CURLE_OK;
988
2.35k
}
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
2.36k
{
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
2.36k
  if(ws->pending.type) {
1000
17
    CURLcode result = ws_enc_add_pending(data, ws);
1001
17
    if(result)
1002
0
      return result;
1003
17
  }
1004
2.36k
  return ws_enc_add_frame(data, enc, flags, payload_len, out);
1005
2.36k
}
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
2.36k
{
1012
2.36k
  CURLcode result;
1013
2.36k
  size_t i, len, n, remain;
1014
1015
2.36k
  *pnwritten = 0;
1016
2.36k
  if(Curl_bufq_is_full(out))
1017
0
    return CURLE_AGAIN;
1018
1019
  /* not the most performant way to do this */
1020
2.36k
  len = buflen;
1021
2.36k
  remain = curlx_sotouz_range(enc->payload_remain, 0, SIZE_MAX);
1022
2.36k
  if(remain < len)
1023
0
    len = remain;
1024
1025
1.22M
  for(i = 0; i < len; ++i) {
1026
1.21M
    uint8_t c = buf[i] ^ enc->mask[enc->xori];
1027
1.21M
    result = Curl_bufq_write(out, &c, 1, &n);
1028
1.21M
    if(result) {
1029
0
      if((result != CURLE_AGAIN) || !i)
1030
0
        return result;
1031
0
      break;
1032
0
    }
1033
1.21M
    enc->xori++;
1034
1.21M
    enc->xori &= 3;
1035
1.21M
  }
1036
2.36k
  *pnwritten = i;
1037
2.36k
  enc->payload_remain -= (curl_off_t)i;
1038
2.36k
  ws_enc_info(enc, data, "buffered");
1039
2.36k
  return CURLE_OK;
1040
2.36k
}
1041
1042
static CURLcode ws_enc_add_pending(struct Curl_easy *data,
1043
                                   struct websocket *ws)
1044
6.29k
{
1045
6.29k
  CURLcode result;
1046
6.29k
  size_t n;
1047
1048
6.29k
  if(!ws->pending.type) /* no pending frame here */
1049
5.14k
    return CURLE_OK;
1050
1.14k
  if(ws->enc.payload_remain) /* in the middle of another frame */
1051
0
    return CURLE_AGAIN;
1052
1053
1.14k
  result = ws_enc_add_frame(data, &ws->enc, ws->pending.type,
1054
1.14k
                            (curl_off_t)ws->pending.payload_len,
1055
1.14k
                            &ws->sendbuf);
1056
1.14k
  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
1.14k
  result = ws_enc_write_payload(&ws->enc, data, ws->pending.payload,
1062
1.14k
                                ws->pending.payload_len,
1063
1.14k
                                &ws->sendbuf, &n);
1064
1.14k
  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
1.14k
  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
1.14k
  DEBUGASSERT(!ws->enc.payload_remain);
1078
1.14k
  memset(&ws->pending, 0, sizeof(ws->pending));
1079
1080
1.14k
out:
1081
1.14k
  return result;
1082
1.14k
}
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
2.07k
{
1092
2.07k
  size_t n;
1093
2.07k
  CURLcode result = CURLE_OK;
1094
1095
2.07k
  DEBUGASSERT(!data->set.ws_raw_mode);
1096
2.07k
  *pnsent = 0;
1097
1098
2.07k
  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
1.35k
    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
1.35k
    if((curl_off_t)buflen >
1110
1.35k
       (ws->enc.payload_remain + (curl_off_t)ws->sendbuf_payload)) {
1111
      /* too large buflen beyond payload length of frame */
1112
73
      failf(data, "[WS] unaligned frame size (sending %zu instead of "
1113
73
            "%" FMT_OFF_T ")", buflen,
1114
73
            (curl_off_t)(ws->enc.payload_remain + ws->sendbuf_payload));
1115
73
      return CURLE_BAD_FUNCTION_ARGUMENT;
1116
73
    }
1117
1.35k
  }
1118
722
  else {
1119
722
    result = ws_flush(data, ws, Curl_api_is_in_callback(data));
1120
722
    if(result)
1121
0
      return result;
1122
1123
722
    result = ws_enc_write_head(data, ws, &ws->enc, flags,
1124
722
                               (flags & CURLWS_OFFSET) ?
1125
722
                               fragsize : (curl_off_t)buflen,
1126
722
                               &ws->sendbuf);
1127
722
    if(result) {
1128
720
      CURL_TRC_WS(data, "curl_ws_send(), error writing frame head %d",
1129
720
                  (int)result);
1130
720
      return result;
1131
720
    }
1132
722
  }
1133
1134
  /* While there is either sendbuf to flush OR more payload to encode... */
1135
2.32k
  while(!Curl_bufq_is_empty(&ws->sendbuf) || (buflen > ws->sendbuf_payload)) {
1136
    /* Try to add more payload to sendbuf */
1137
1.28k
    if(buflen > ws->sendbuf_payload) {
1138
1.05k
      size_t prev_len = Curl_bufq_len(&ws->sendbuf);
1139
1.05k
      result = ws_enc_write_payload(&ws->enc, data,
1140
1.05k
                                    buffer + ws->sendbuf_payload,
1141
1.05k
                                    buflen - ws->sendbuf_payload,
1142
1.05k
                                    &ws->sendbuf, &n);
1143
1.05k
      if(result && (result != CURLE_AGAIN))
1144
0
        return result;
1145
1.05k
      ws->sendbuf_payload += Curl_bufq_len(&ws->sendbuf) - prev_len;
1146
1.05k
      if(!ws->sendbuf_payload) {
1147
0
        return CURLE_AGAIN;
1148
0
      }
1149
1.05k
    }
1150
1151
    /* flush, blocking when in callback */
1152
1.28k
    result = ws_flush(data, ws, Curl_api_is_in_callback(data));
1153
1.28k
    if(!result && ws->sendbuf_payload > 0) {
1154
1.04k
      *pnsent += ws->sendbuf_payload;
1155
1.04k
      buffer += ws->sendbuf_payload;
1156
1.04k
      buflen -= ws->sendbuf_payload;
1157
1.04k
      ws->sendbuf_payload = 0;
1158
1.04k
    }
1159
244
    else if(result == CURLE_AGAIN) {
1160
244
      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
244
      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
244
        CURL_TRC_WS(data, "EAGAIN flushing sendbuf, payload_encoded: %zu/%zu",
1172
244
                    ws->sendbuf_payload, buflen);
1173
244
        DEBUGASSERT(*pnsent == 0);
1174
244
        return CURLE_AGAIN;
1175
244
      }
1176
244
    }
1177
0
    else
1178
0
      return result;  /* real error sending the data */
1179
1.28k
  }
1180
1.04k
  return CURLE_OK;
1181
1.28k
}
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
101
{
1191
101
  (void)data;
1192
101
  (void)reader;
1193
101
  return CURLE_OK;
1194
101
}
1195
1196
static void cr_ws_close(struct Curl_easy *data, struct Curl_creader *reader)
1197
101
{
1198
101
  (void)data;
1199
101
  (void)reader;
1200
101
}
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
269
{
1207
269
  struct cr_ws_ctx *ctx = reader->ctx;
1208
269
  CURLcode result = CURLE_OK;
1209
269
  size_t nread, n;
1210
269
  struct websocket *ws;
1211
269
  bool eos;
1212
1213
269
  *pnread = 0;
1214
269
  if(ctx->eos) {
1215
0
    *peos = TRUE;
1216
0
    return CURLE_OK;
1217
0
  }
1218
1219
269
  ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1220
269
  if(!ws) {
1221
0
    failf(data, "[WS] not a websocket transfer");
1222
0
    return CURLE_FAILED_INIT;
1223
0
  }
1224
1225
269
  if(Curl_bufq_is_empty(&ws->sendbuf)) {
1226
265
    if(ctx->read_eos) {
1227
0
      ctx->eos = TRUE;
1228
0
      *peos = TRUE;
1229
0
      return CURLE_OK;
1230
0
    }
1231
1232
265
    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
265
    result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos);
1239
265
    if(result)
1240
8
      return result;
1241
257
    ctx->read_eos = eos;
1242
1243
257
    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
257
    else if(!nread) {
1249
      /* nothing to convert, return this right away */
1250
90
      if(ctx->read_eos)
1251
90
        ctx->eos = TRUE;
1252
90
      *pnread = nread;
1253
90
      *peos = (bool)ctx->eos;
1254
90
      goto out;
1255
90
    }
1256
1257
167
    if(!ws->enc.payload_remain && Curl_bufq_is_empty(&ws->sendbuf)) {
1258
      /* encode the data as a new BINARY frame */
1259
167
      result = ws_enc_write_head(data, ws, &ws->enc, CURLWS_BINARY, nread,
1260
167
                                 &ws->sendbuf);
1261
167
      if(result)
1262
0
        goto out;
1263
167
    }
1264
1265
167
    result = ws_enc_write_payload(&ws->enc, data, (uint8_t *)buf,
1266
167
                                  nread, &ws->sendbuf, &n);
1267
167
    if(result)
1268
0
      goto out;
1269
167
    CURL_TRC_READ(data, "cr_ws_read, added %zu payload, len=%zu", nread, n);
1270
167
  }
1271
1272
171
  DEBUGASSERT(!Curl_bufq_is_empty(&ws->sendbuf));
1273
171
  *peos = FALSE;
1274
171
  result = Curl_bufq_cread(&ws->sendbuf, buf, blen, pnread);
1275
171
  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
261
out:
1282
261
  CURL_TRC_READ(data, "cr_ws_read(len=%zu) -> %d, nread=%zu, eos=%d",
1283
261
                blen, (int)result, *pnread, *peos);
1284
261
  return result;
1285
171
}
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.98k
{
1308
1.98k
  unsigned int i;
1309
1.98k
  CURLcode result = CURLE_OK;
1310
1.98k
  uint8_t rand[16];
1311
1.98k
  char *randstr;
1312
1.98k
  size_t randlen;
1313
1.98k
  char keyval[40];
1314
1.98k
  struct SingleRequest *k = &data->req;
1315
1.98k
  struct wsfield heads[] = {
1316
1.98k
    {
1317
      /* The request MUST contain an |Upgrade| header field whose value
1318
         MUST include the "websocket" keyword. */
1319
1.98k
      "Upgrade", "websocket"
1320
1.98k
    },
1321
1.98k
    {
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.98k
      "Sec-WebSocket-Version", "13",
1326
1.98k
    },
1327
1.98k
    {
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.98k
      "Sec-WebSocket-Key", NULL,
1334
1.98k
    }
1335
1.98k
  };
1336
1.98k
  heads[2].val = &keyval[0];
1337
1338
  /* 16 bytes random */
1339
1.98k
  result = Curl_rand(data, rand, sizeof(rand));
1340
1.98k
  if(result)
1341
0
    return result;
1342
1.98k
  result = curlx_base64_encode(rand, sizeof(rand), &randstr, &randlen);
1343
1.98k
  if(result)
1344
0
    return result;
1345
1.98k
  DEBUGASSERT(randlen < sizeof(keyval));
1346
1.98k
  if(randlen >= sizeof(keyval)) {
1347
0
    curlx_free(randstr);
1348
0
    return CURLE_FAILED_INIT;
1349
0
  }
1350
1.98k
  curlx_strcopy(keyval, sizeof(keyval), randstr, randlen);
1351
1.98k
  curlx_free(randstr);
1352
7.92k
  for(i = 0; !result && (i < CURL_ARRAYSIZE(heads)); i++) {
1353
5.94k
    if(!Curl_checkheaders(data, heads[i].name, strlen(heads[i].name))) {
1354
5.94k
      result = curlx_dyn_addf(req, "%s: %s\r\n", heads[i].name, heads[i].val);
1355
5.94k
    }
1356
5.94k
  }
1357
1.98k
  data->state.http_hd_upgrade = TRUE;
1358
1.98k
  k->upgr101 = UPGR101_WS;
1359
1.98k
  data->conn->bits.upgrade_in_progress = TRUE;
1360
1.98k
  return result;
1361
1.98k
}
1362
1363
static void ws_conn_dtor(void *key, size_t klen, void *entry)
1364
1.97k
{
1365
1.97k
  struct websocket *ws = entry;
1366
1.97k
  (void)key;
1367
1.97k
  (void)klen;
1368
1.97k
  Curl_bufq_free(&ws->recvbuf);
1369
1.97k
  Curl_bufq_free(&ws->sendbuf);
1370
1.97k
  curlx_free(ws);
1371
1.97k
}
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.97k
{
1380
1.97k
  struct SingleRequest *k = &data->req;
1381
1.97k
  struct websocket *ws;
1382
1.97k
  struct Curl_cwriter *ws_dec_writer = NULL;
1383
1.97k
  struct Curl_creader *ws_enc_reader = NULL;
1384
1.97k
  CURLcode result;
1385
1386
1.97k
  DEBUGASSERT(data->conn);
1387
1.97k
  ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1388
1.97k
  if(!ws) {
1389
1.97k
    size_t chunk_size = WS_CHUNK_SIZE;
1390
1.97k
    ws = curlx_calloc(1, sizeof(*ws));
1391
1.97k
    if(!ws)
1392
0
      return CURLE_OUT_OF_MEMORY;
1393
1.97k
#ifdef DEBUGBUILD
1394
1.97k
    {
1395
1.97k
      const char *p = getenv("CURL_WS_CHUNK_SIZE");
1396
1.97k
      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.97k
    }
1402
1.97k
#endif
1403
1.97k
    CURL_TRC_WS(data, "WS, using chunk size %zu", chunk_size);
1404
1.97k
    Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT,
1405
1.97k
                    BUFQ_OPT_SOFT_LIMIT);
1406
1.97k
    Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT,
1407
1.97k
                    BUFQ_OPT_SOFT_LIMIT);
1408
1.97k
    ws_dec_init(&ws->dec);
1409
1.97k
    ws_enc_init(&ws->enc);
1410
1.97k
    result = Curl_conn_meta_set(data->conn, CURL_META_PROTO_WS_CONN,
1411
1.97k
                                ws, ws_conn_dtor);
1412
1.97k
    if(result)
1413
0
      return result;
1414
1.97k
  }
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.97k
  infof(data, "[WS] Received 101, switch to WebSocket");
1439
1440
  /* Install our client writer that decodes WS frames payload */
1441
1.97k
  result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode,
1442
1.97k
                               CURL_CW_CONTENT_DECODE);
1443
1.97k
  if(result)
1444
0
    goto out;
1445
1.97k
  result = Curl_cwriter_add(data, ws_dec_writer);
1446
1.97k
  if(result)
1447
0
    goto out;
1448
1.97k
  ws_dec_writer = NULL; /* owned by transfer now */
1449
1450
1.97k
  k->header = FALSE; /* we will not get more response headers */
1451
1452
1.97k
  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
461
    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
461
    CURL_REQ_CLEAR_RECV(data); /* read no more content */
1465
461
  }
1466
1.51k
  else { /* !connect_only */
1467
1.51k
    if(data->set.method == HTTPREQ_PUT) {
1468
102
      CURL_TRC_WS(data, "UPLOAD set, add ws-encode reader");
1469
102
      result = Curl_creader_set_fread(data, -1);
1470
102
      if(result)
1471
0
        goto out;
1472
1473
102
      if(!data->set.ws_raw_mode) {
1474
        /* Add our client reader encoding WS BINARY frames */
1475
101
        result = Curl_creader_create(&ws_enc_reader, data, &ws_cr_encode,
1476
101
                                     CURL_CR_CONTENT_ENCODE);
1477
101
        if(result)
1478
0
          goto out;
1479
101
        result = Curl_creader_add(data, ws_enc_reader);
1480
101
        if(result)
1481
0
          goto out;
1482
101
        ws_enc_reader = NULL; /* owned by transfer now */
1483
101
      }
1484
1485
      /* start over with sending */
1486
102
      data->req.eos_read = FALSE;
1487
102
      data->req.upload_done = FALSE;
1488
102
      CURL_REQ_SET_SEND(data);
1489
102
    }
1490
1491
    /* Then pass any additional data to the writers */
1492
1.51k
    if(nread) {
1493
556
      result = Curl_client_write(data, CLIENTWRITE_BODY, mem, nread);
1494
556
      if(result)
1495
155
        goto out;
1496
556
    }
1497
1.51k
  }
1498
1499
1.82k
  k->upgr101 = UPGR101_RECEIVED;
1500
1.82k
  k->header = FALSE; /* we will not get more responses */
1501
1502
1.97k
out:
1503
1.97k
  if(ws_dec_writer)
1504
0
    Curl_cwriter_free(data, ws_dec_writer);
1505
1.97k
  if(ws_enc_reader)
1506
0
    Curl_creader_free(data, ws_enc_reader);
1507
1.97k
  if(result)
1508
155
    CURL_TRC_WS(data, "Curl_ws_accept() failed -> %d", (int)result);
1509
1.82k
  else
1510
1.82k
    CURL_TRC_WS(data, "websocket established, %s mode",
1511
1.97k
                data->set.connect_only ? "connect-only" : "callback");
1512
1.97k
  return result;
1513
1.82k
}
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
8.89k
{
1535
8.89k
  struct ws_collect *ctx = userp;
1536
8.89k
  struct Curl_easy *data = ctx->data;
1537
8.89k
  bool auto_pong = !data->set.ws_no_auto_pong;
1538
8.89k
  curl_off_t remain;
1539
8.89k
  CURLcode result = CURLE_OK;
1540
1541
8.89k
  *pnwritten = 0;
1542
8.89k
  remain = ws_payload_remain(payload_len, payload_offset, buflen);
1543
8.89k
  if(remain < 0) {
1544
0
    DEBUGASSERT(0); /* parameter mismatch */
1545
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
1546
0
  }
1547
1548
8.89k
  if(!ctx->bufidx) {
1549
    /* first write */
1550
8.88k
    ctx->frame_age = frame_age;
1551
8.88k
    ctx->frame_flags = frame_flags;
1552
8.88k
    ctx->payload_offset = payload_offset;
1553
8.88k
    ctx->payload_len = payload_len;
1554
8.88k
  }
1555
1556
8.89k
  if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
1557
    /* auto-respond to PINGs, only works for single-frame payloads atm */
1558
1.10k
    CURL_TRC_WS(data, "auto PONG to [PING payload=%" FMT_OFF_T
1559
1.10k
                "/%" FMT_OFF_T "]", payload_offset, payload_len);
1560
    /* send back the exact same content as a PONG */
1561
1.10k
    result = ws_enc_add_cntrl(ctx->data, ctx->ws, buf, buflen, CURLWS_PONG);
1562
1.10k
    if(result)
1563
0
      return result;
1564
1.10k
    *pnwritten = buflen;
1565
1.10k
  }
1566
7.78k
  else {
1567
7.78k
    size_t write_len;
1568
1569
7.78k
    ctx->written = TRUE;
1570
7.78k
    DEBUGASSERT(ctx->buflen >= ctx->bufidx);
1571
7.78k
    write_len = CURLMIN(buflen, ctx->buflen - ctx->bufidx);
1572
7.78k
    if(!write_len) {
1573
6.33k
      if(!buflen)  /* 0 length write, we accept that */
1574
6.31k
        return CURLE_OK;
1575
13
      return CURLE_AGAIN;  /* no more space */
1576
6.33k
    }
1577
1.45k
    memcpy(ctx->buffer + ctx->bufidx, buf, write_len);
1578
1.45k
    ctx->bufidx += write_len;
1579
1.45k
    *pnwritten = write_len;
1580
1.45k
  }
1581
2.56k
  return result;
1582
8.89k
}
1583
1584
static CURLcode nw_in_recv(void *reader_ctx,
1585
                           uint8_t *buf, size_t buflen,
1586
                           size_t *pnread)
1587
3.09k
{
1588
3.09k
  struct Curl_easy *data = reader_ctx;
1589
3.09k
  return Curl_easy_recv(data, buf, buflen, pnread);
1590
3.09k
}
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
10.3k
{
1596
10.3k
  struct Curl_eapi_guard guard;
1597
10.3k
  CURLcode result = CURLE_OK;
1598
1599
10.3k
  *recv = 0;
1600
10.3k
  *metap = NULL;
1601
10.3k
  if(CURL_EAPI_ENTER(&guard, curl, ws_recv, &result)) {
1602
10.3k
    struct Curl_easy *data = curl;
1603
10.3k
    struct connectdata *conn;
1604
10.3k
    struct websocket *ws;
1605
10.3k
    struct ws_collect ctx;
1606
1607
10.3k
    if(buflen && !buffer) {
1608
0
      result = CURLE_BAD_FUNCTION_ARGUMENT;
1609
0
      goto out;
1610
0
    }
1611
1612
10.3k
    conn = data->conn;
1613
10.3k
    if(!conn) {
1614
      /* Unhappy hack with lifetimes of transfers and connection */
1615
522
      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
522
      Curl_getconnectinfo(data, &conn);
1622
522
      if(!conn) {
1623
76
        failf(data, "[WS] connection not found");
1624
76
        result = CURLE_BAD_FUNCTION_ARGUMENT;
1625
76
        goto out;
1626
76
      }
1627
522
    }
1628
10.2k
    ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN);
1629
10.2k
    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
10.2k
    memset(&ctx, 0, sizeof(ctx));
1636
10.2k
    ctx.data = data;
1637
10.2k
    ctx.ws = ws;
1638
10.2k
    ctx.buffer = buffer;
1639
10.2k
    ctx.buflen = buflen;
1640
1641
11.9k
    while(1) {
1642
      /* receive more when our buffer is empty */
1643
11.9k
      if(Curl_bufq_is_empty(&ws->recvbuf)) {
1644
3.09k
        size_t n;
1645
3.09k
        result = Curl_bufq_slurp(&ws->recvbuf, nw_in_recv, data, &n);
1646
3.09k
        if(result)
1647
1.58k
          goto out;
1648
1.50k
        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
1.50k
        CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network",
1655
1.50k
                    Curl_bufq_len(&ws->recvbuf));
1656
1.50k
      }
1657
1658
10.3k
      result = ws_dec_pass(&ws->dec, data, &ws->recvbuf,
1659
10.3k
                           ws_client_collect, &ctx);
1660
10.3k
      if(result == CURLE_AGAIN) {
1661
1.11k
        if(!ctx.written) {
1662
576
          ws_dec_info(&ws->dec, data, "need more input");
1663
576
          continue;  /* nothing written, try more input */
1664
576
        }
1665
538
        break;
1666
1.11k
      }
1667
9.26k
      else if(result) {
1668
921
        goto out;
1669
921
      }
1670
8.34k
      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
7.23k
        break;
1675
7.23k
      }
1676
10.3k
    }
1677
1678
    /* update frame information to be passed back */
1679
7.77k
    update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset,
1680
7.77k
                ctx.payload_len, ctx.bufidx);
1681
7.77k
    *metap = &ws->recvframe;
1682
7.77k
    *recv = ws->recvframe.len;
1683
7.77k
    CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %"
1684
7.77k
                FMT_OFF_T ", %" FMT_OFF_T " left)",
1685
7.77k
                buflen, *recv, ws->recvframe.offset,
1686
7.77k
                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
7.77k
    if(!data->set.ws_raw_mode && ws->pending.type) {
1690
1.06k
      CURLcode r2 = ws_enc_add_pending(data, ws);
1691
1.06k
      if(!r2)
1692
1.06k
        (void)ws_flush(data, ws, Curl_api_is_in_callback(data));
1693
1.06k
    }
1694
7.77k
    result = CURLE_OK;
1695
7.77k
  }
1696
10.3k
out:
1697
10.3k
  CURL_EAPI_LEAVE(&guard);
1698
10.3k
  return result;
1699
10.3k
}
1700
1701
static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
1702
                         bool blocking)
1703
5.21k
{
1704
5.21k
  CURLcode result;
1705
1706
  /* If there is space, add any pending control frame */
1707
5.21k
  if(Curl_bufq_len(&ws->sendbuf) < ws->sendbuf.chunk_size) {
1708
5.21k
    result = ws_enc_add_pending(data, ws);
1709
5.21k
    if(result && (result != CURLE_AGAIN))
1710
0
      return result;
1711
5.21k
  }
1712
1713
5.21k
  if(!Curl_bufq_is_empty(&ws->sendbuf)) {
1714
2.42k
    const uint8_t *out;
1715
2.42k
    size_t outlen, n;
1716
2.42k
#ifdef DEBUGBUILD
1717
    /* Simulate a blocking send after this chunk has been sent */
1718
2.42k
    bool eagain_next = FALSE;
1719
2.42k
    size_t chunk_egain = 0;
1720
2.42k
    const char *p = getenv("CURL_WS_CHUNK_EAGAIN");
1721
2.42k
    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
2.42k
#endif
1727
1728
4.41k
    while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) {
1729
2.42k
#ifdef DEBUGBUILD
1730
2.42k
      if(eagain_next)
1731
0
        return CURLE_AGAIN;
1732
2.42k
      if(chunk_egain && (outlen > chunk_egain)) {
1733
0
        outlen = chunk_egain;
1734
0
        eagain_next = TRUE;
1735
0
      }
1736
2.42k
#endif
1737
2.42k
      if(blocking) {
1738
1
        result = ws_send_raw_blocking(data, ws, (const char *)out, outlen);
1739
1
        n = result ? 0 : outlen;
1740
1
      }
1741
2.42k
      else if(data->set.connect_only || Curl_api_is_in_callback(data))
1742
2.34k
        result = Curl_senddata(data, out, outlen, &n);
1743
78
      else {
1744
78
        result = Curl_xfer_send(data, out, outlen, FALSE, &n);
1745
78
        if(!result && !n && outlen)
1746
36
          result = CURLE_AGAIN;
1747
78
      }
1748
1749
2.42k
      if(result == CURLE_AGAIN) {
1750
434
        CURL_TRC_WS(data, "flush EAGAIN, %zu bytes remain in buffer",
1751
434
                    Curl_bufq_len(&ws->sendbuf));
1752
434
        return result;
1753
434
      }
1754
1.99k
      else if(result) {
1755
0
        failf(data, "[WS] flush, write error %d", (int)result);
1756
0
        return result;
1757
0
      }
1758
1.99k
      else {
1759
1.99k
        CURL_TRC_WS(data, "flushed %zu bytes", n);
1760
1.99k
        Curl_bufq_skip(&ws->sendbuf, n);
1761
1.99k
      }
1762
2.42k
    }
1763
2.42k
  }
1764
4.78k
  return CURLE_OK;
1765
5.21k
}
1766
1767
static CURLcode ws_send_raw_blocking(struct Curl_easy *data,
1768
                                     struct websocket *ws,
1769
                                     const char *buffer, size_t buflen)
1770
20
{
1771
20
  CURLcode result = CURLE_OK;
1772
20
  size_t nwritten;
1773
1774
20
  if(!data)
1775
0
    return result;
1776
1777
20
  (void)ws;
1778
41
  while(buflen) {
1779
27
    result = Curl_xfer_send(data, buffer, buflen, FALSE, &nwritten);
1780
27
    if(result)
1781
0
      return result;
1782
27
    DEBUGASSERT(nwritten <= buflen);
1783
27
    buffer += nwritten;
1784
27
    buflen -= nwritten;
1785
27
    if(buflen) {
1786
13
      curl_socket_t sock = data->conn->sock[FIRSTSOCKET];
1787
13
      timediff_t left_ms;
1788
13
      int ev;
1789
1790
13
      CURL_TRC_WS(data, "ws_send_raw_blocking() partial, %zu left to send",
1791
13
                  buflen);
1792
13
      left_ms = Curl_timeleft_ms(data);
1793
13
      if(left_ms < 0) {
1794
6
        failf(data, "[WS] Timeout waiting for socket becoming writable");
1795
6
        return CURLE_SEND_ERROR;
1796
6
      }
1797
1798
      /* POLLOUT socket */
1799
7
      if(sock == CURL_SOCKET_BAD)
1800
0
        return CURLE_SEND_ERROR;
1801
7
      ev = SOCKET_WRITABLE(sock, left_ms ? left_ms : 500);
1802
7
      if(ev < 0) {
1803
0
        failf(data, "[WS] Error while waiting for socket becoming writable");
1804
0
        return CURLE_SEND_ERROR;
1805
0
      }
1806
7
    }
1807
27
  }
1808
14
  return result;
1809
20
}
1810
1811
static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer,
1812
                            size_t buflen, size_t *pnwritten)
1813
30
{
1814
30
  struct websocket *ws;
1815
30
  CURLcode result;
1816
1817
30
  ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1818
30
  if(!ws) {
1819
0
    failf(data, "[WS] Not a websocket transfer");
1820
0
    return CURLE_SEND_ERROR;
1821
0
  }
1822
30
  if(!buflen)
1823
0
    return CURLE_OK;
1824
1825
30
  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
19
    result = ws_flush(data, ws, TRUE);
1831
19
    if(result)
1832
0
      return result;
1833
19
    result = ws_send_raw_blocking(data, ws, buffer, buflen);
1834
19
    if(!result)
1835
13
      *pnwritten = buflen;
1836
19
  }
1837
11
  else {
1838
    /* We need any pending data to be sent or EAGAIN this call. */
1839
11
    result = ws_flush(data, ws, FALSE);
1840
11
    if(result)
1841
0
      return result;
1842
11
    result = Curl_senddata(data, buffer, buflen, pnwritten);
1843
11
  }
1844
1845
30
  CURL_TRC_WS(data, "ws_send_raw(len=%zu) -> %d, %zu",
1846
30
              buflen, (int)result, *pnwritten);
1847
30
  return result;
1848
30
}
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
2.24k
{
1855
2.24k
  struct Curl_eapi_guard guard;
1856
2.24k
  CURLcode result = CURLE_OK;
1857
1858
2.24k
  if(CURL_EAPI_ENTER(&guard, curl, ws_send, &result)) {
1859
2.24k
    struct websocket *ws;
1860
2.24k
    const uint8_t *buffer = buffer_arg;
1861
2.24k
    struct Curl_easy *data = curl;
1862
2.24k
    size_t ndummy;
1863
2.24k
    size_t *pnsent = sent ? sent : &ndummy;
1864
1865
2.24k
    CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T
1866
2.24k
                ", flags=%x), raw=%d",
1867
2.24k
                buflen, fragsize, flags, data->set.ws_raw_mode);
1868
1869
2.24k
    *pnsent = 0;
1870
1871
2.24k
    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
2.24k
    if(!data->conn && data->set.connect_only) {
1878
46
      result = Curl_connect_only_attach(data);
1879
46
      if(result)
1880
46
        goto out;
1881
46
    }
1882
2.19k
    if(!data->conn) {
1883
0
      failf(data, "[WS] No associated connection");
1884
0
      result = CURLE_SEND_ERROR;
1885
0
      goto out;
1886
0
    }
1887
2.19k
    ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1888
2.19k
    if(!ws) {
1889
18
      failf(data, "[WS] Not a websocket transfer");
1890
18
      result = CURLE_SEND_ERROR;
1891
18
      goto out;
1892
18
    }
1893
1894
2.18k
    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
102
      result = ws_flush(data, ws, FALSE);
1898
102
      if(result)
1899
0
        goto out;
1900
1901
102
      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
102
      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
102
      if(fragsize || flags) {
1912
72
        failf(data, "[WS] fragsize and flags must be zero in raw mode");
1913
72
        result = CURLE_BAD_FUNCTION_ARGUMENT;
1914
72
        goto out;
1915
72
      }
1916
30
      result = ws_send_raw(data, buffer, buflen, pnsent);
1917
30
      goto out;
1918
102
    }
1919
1920
    /* Not RAW mode, we do the frame encoding */
1921
2.07k
    result = ws_enc_send(data, ws, buffer, buflen, fragsize, flags, pnsent);
1922
2.07k
    CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T
1923
2.07k
                ", flags=%x, raw=%d) -> %d, %zu",
1924
2.07k
                buflen, fragsize, flags, data->set.ws_raw_mode, (int)result,
1925
2.07k
                *pnsent);
1926
2.07k
  }
1927
2.24k
out:
1928
2.24k
  CURL_EAPI_LEAVE(&guard);
1929
2.24k
  return result;
1930
2.24k
}
1931
1932
static CURLcode ws_setup_conn(struct Curl_easy *data,
1933
                              struct connectdata *conn)
1934
2.52k
{
1935
  /* WebSocket is 1.1 only (for now) */
1936
2.52k
  data->state.http_neg.accept_09 = FALSE;
1937
2.52k
  data->state.http_neg.only_10 = FALSE;
1938
2.52k
  data->state.http_neg.wanted = CURL_HTTP_V1x;
1939
2.52k
  data->state.http_neg.allowed = CURL_HTTP_V1x;
1940
2.52k
  return Curl_http_setup_conn(data, conn);
1941
2.52k
}
1942
1943
const struct curl_ws_frame *curl_ws_meta(CURL *curl)
1944
5.14k
{
1945
  /* we only return something for websocket, called from within the callback
1946
     when not using raw mode */
1947
5.14k
  struct Curl_easy *data = curl;
1948
5.14k
  if(GOOD_EASY_HANDLE(data) && Curl_api_is_in_callback(data) &&
1949
5.14k
     data->conn && !data->set.ws_raw_mode) {
1950
4.97k
    struct websocket *ws;
1951
4.97k
    ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1952
4.97k
    if(ws)
1953
4.90k
      return &ws->recvframe;
1954
4.97k
  }
1955
236
  return NULL;
1956
5.14k
}
1957
1958
CURL_EXTERN CURLcode curl_ws_start_frame(CURL *curl,
1959
                                         unsigned int flags,
1960
                                         curl_off_t frame_len)
1961
1.77k
{
1962
1.77k
  struct Curl_eapi_guard guard;
1963
1.77k
  CURLcode result = CURLE_OK;
1964
1965
1.77k
  if(CURL_EAPI_ENTER(&guard, curl, ws_start_frame, &result)) {
1966
1.77k
    struct Curl_easy *data = curl;
1967
1.77k
    struct websocket *ws;
1968
1969
1.77k
    if(data->set.ws_raw_mode) {
1970
78
      failf(data, "cannot curl_ws_start_frame() with CURLWS_RAW_MODE enabled");
1971
78
      result = CURLE_FAILED_INIT;
1972
78
      goto out;
1973
78
    }
1974
1975
1.69k
    CURL_TRC_WS(data, "curl_ws_start_frame(flags=%x, frame_len=%" FMT_OFF_T,
1976
1.69k
                flags, frame_len);
1977
1978
1.69k
    if(!data->conn) {
1979
41
      failf(data, "[WS] No associated connection");
1980
41
      result = CURLE_SEND_ERROR;
1981
41
      goto out;
1982
41
    }
1983
1.65k
    ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
1984
1.65k
    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
1.65k
    if(ws->enc.payload_remain) {
1991
175
      failf(data, "[WS] previous frame not finished");
1992
175
      result = CURLE_SEND_ERROR;
1993
175
      goto out;
1994
175
    }
1995
1996
1.47k
    result = ws_enc_write_head(data, ws, &ws->enc, flags, frame_len,
1997
1.47k
                               &ws->sendbuf);
1998
1.47k
    if(result)
1999
439
      CURL_TRC_WS(data, "curl_start_frame(), error adding frame head %d",
2000
1.47k
                  (int)result);
2001
1.47k
  }
2002
1.77k
out:
2003
1.77k
  CURL_EAPI_LEAVE(&guard);
2004
1.77k
  return result;
2005
1.77k
}
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 */