Coverage Report

Created: 2026-10-22 06:50

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