Coverage Report

Created: 2026-04-29 07:01

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