Coverage Report

Created: 2025-12-05 06:38

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