Coverage Report

Created: 2026-08-13 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libevent/ws.c
Line
Count
Source
1
#include "event2/event-config.h"
2
#include "evconfig-private.h"
3
4
#include "event2/buffer.h"
5
#include "event2/bufferevent.h"
6
#include "event2/event.h"
7
#include "event2/http.h"
8
#include "event2/ws.h"
9
#include "util-internal.h"
10
#include "mm-internal.h"
11
#include "sha1.h"
12
#include "event2/bufferevent.h"
13
#include "sys/queue.h"
14
#include "http-internal.h"
15
#include "bufferevent-internal.h"
16
17
#include <assert.h>
18
#include <inttypes.h>
19
#include <string.h>
20
#include <stdbool.h>
21
22
#ifndef _WIN32
23
#include <sys/socket.h>
24
#include <sys/stat.h>
25
#else /* _WIN32 */
26
#include <winsock2.h>
27
#include <ws2tcpip.h>
28
#endif /* _WIN32 */
29
30
#ifdef EVENT__HAVE_ARPA_INET_H
31
#include <arpa/inet.h>
32
#endif
33
#ifdef EVENT__HAVE_NETINET_IN_H
34
#include <netinet/in.h>
35
#endif
36
#ifdef EVENT__HAVE_NETINET_IN6_H
37
#include <netinet/in6.h>
38
#endif
39
40
1.01k
#define WS_UUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
41
/*
42
 * We limit the size of received WS frames to 10 MiB,
43
 * as a DoS prevention measure.
44
 */
45
static const size_t WS_MAX_RECV_FRAME_SZ = 10485760;
46
/*
47
 * We also limit the total size of a fragmented message to 10 MiB so that
48
 * clients cannot bypass the per-frame cap by streaming unbounded fragments.
49
 */
50
static const size_t WS_MAX_RECV_MSG_SZ = 10485760;
51
52
struct evws_connection {
53
  TAILQ_ENTRY(evws_connection) next;
54
55
  struct bufferevent *bufev;
56
57
  ws_on_msg_cb cb;
58
  void *cb_arg;
59
60
  ws_on_close_cb cbclose;
61
  void *cbclose_arg;
62
63
  /* for server connections, the http server they are connected with */
64
  struct evhttp *http_server;
65
66
  struct evbuffer *incomplete_frames;
67
  bool closed;
68
};
69
70
enum WebSocketFrameType {
71
  ERROR_FRAME = 0xFF,
72
  INCOMPLETE_DATA = 0xFE,
73
74
  CLOSING_FRAME = 0x8,
75
76
  INCOMPLETE_FRAME = 0x81,
77
78
  TEXT_FRAME = 0x1,
79
  BINARY_FRAME = 0x2,
80
81
  PING_FRAME = 0x9,
82
  PONG_FRAME = 0xA
83
};
84
85
86
static void evws_send(struct evws_connection *evws,
87
  enum WebSocketFrameType frame_type, const char *packet_str, size_t str_len);
88
89
/*
90
 * Clean up a WebSockets connection object
91
 */
92
93
void
94
evws_connection_free(struct evws_connection *evws)
95
1.01k
{
96
  /* notify interested parties that this connection is going down */
97
1.01k
  if (evws->cbclose != NULL)
98
0
    (*evws->cbclose)(evws, evws->cbclose_arg);
99
100
1.01k
  if (evws->http_server != NULL) {
101
1.01k
    struct evhttp *http = evws->http_server;
102
1.01k
    TAILQ_REMOVE(&http->ws_sessions, evws, next);
103
1.01k
    http->connection_cnt--;
104
1.01k
  }
105
106
1.01k
  if (evws->bufev != NULL) {
107
1.01k
    bufferevent_free(evws->bufev);
108
1.01k
  }
109
1.01k
  if (evws->incomplete_frames != NULL) {
110
515
    evbuffer_free(evws->incomplete_frames);
111
515
  }
112
113
1.01k
  mm_free(evws);
114
1.01k
}
115
116
static const char basis_64[] =
117
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
118
119
static int
120
Base64encode(char *encoded, const char *string, int len)
121
1.01k
{
122
1.01k
  int i;
123
1.01k
  char *p;
124
125
1.01k
  p = encoded;
126
7.11k
  for (i = 0; i < len - 2; i += 3) {
127
6.10k
    *p++ = basis_64[(string[i] >> 2) & 0x3F];
128
6.10k
    *p++ = basis_64[((string[i] & 0x3) << 4) |
129
6.10k
            ((int)(string[i + 1] & 0xF0) >> 4)];
130
6.10k
    *p++ = basis_64[((string[i + 1] & 0xF) << 2) |
131
6.10k
            ((int)(string[i + 2] & 0xC0) >> 6)];
132
6.10k
    *p++ = basis_64[string[i + 2] & 0x3F];
133
6.10k
  }
134
1.01k
  if (i < len) {
135
1.01k
    *p++ = basis_64[(string[i] >> 2) & 0x3F];
136
1.01k
    if (i == (len - 1)) {
137
0
      *p++ = basis_64[((string[i] & 0x3) << 4)];
138
0
      *p++ = '=';
139
1.01k
    } else {
140
1.01k
      *p++ = basis_64[((string[i] & 0x3) << 4) |
141
1.01k
              ((int)(string[i + 1] & 0xF0) >> 4)];
142
1.01k
      *p++ = basis_64[((string[i + 1] & 0xF) << 2)];
143
1.01k
    }
144
1.01k
    *p++ = '=';
145
1.01k
  }
146
147
1.01k
  *p++ = '\0';
148
1.01k
  return p - encoded;
149
1.01k
}
150
151
static char *
152
ws_gen_accept_key(const char *ws_key, char out[32])
153
1.01k
{
154
1.01k
  char buf[1024];
155
1.01k
  char digest[20];
156
157
1.01k
  snprintf(buf, sizeof(buf), "%s" WS_UUID, ws_key);
158
159
1.01k
  builtin_SHA1(digest, buf, strlen(buf));
160
1.01k
  Base64encode(out, digest, sizeof(digest));
161
1.01k
  return out;
162
1.01k
}
163
164
static void
165
close_after_write_cb(struct bufferevent *bev, void *ctx)
166
0
{
167
0
  if (evbuffer_get_length(bufferevent_get_output(bev)) == 0) {
168
0
    evws_connection_free(ctx);
169
0
  }
170
0
}
171
172
static void
173
close_event_cb(struct bufferevent *bev, short what, void *ctx)
174
0
{
175
0
  evws_connection_free(ctx);
176
0
}
177
178
void
179
evws_close(struct evws_connection *evws, uint16_t reason)
180
196k
{
181
196k
  uint8_t fr[4] = {0x8 | 0x80, 2, 0};
182
196k
  struct evbuffer *output;
183
196k
  uint16_t *u16;
184
185
196k
  if (evws->closed)
186
195k
    return;
187
582
  evws->closed = true;
188
189
582
  u16 = (uint16_t *)&fr[2];
190
582
  *u16 = htons((int16_t)reason);
191
582
  output = bufferevent_get_output(evws->bufev);
192
582
  evbuffer_add(output, fr, 4);
193
194
  /* wait for close frame writing complete and close connection */
195
582
  bufferevent_setcb(
196
582
    evws->bufev, NULL, close_after_write_cb, close_event_cb, evws);
197
582
}
198
199
static void
200
evws_force_disconnect_(struct evws_connection *evws)
201
196k
{
202
196k
  evws_close(evws, WS_CR_NONE);
203
196k
}
204
205
static int
206
ws_message_limit_exceeded_(struct evws_connection *evws, size_t msg_len)
207
3.32M
{
208
3.32M
  size_t buffered = 0;
209
210
3.32M
  if (evws->incomplete_frames != NULL) {
211
3.32M
    buffered = evbuffer_get_length(evws->incomplete_frames);
212
3.32M
  }
213
214
3.32M
  if (msg_len > WS_MAX_RECV_MSG_SZ - buffered) {
215
0
    evws_close(evws, WS_CR_DATA_TOO_BIG);
216
0
    return 1;
217
0
  }
218
219
3.32M
  return 0;
220
3.32M
}
221
222
/* parse base frame according to
223
 * https://www.rfc-editor.org/rfc/rfc6455#section-5.2
224
 */
225
static enum WebSocketFrameType
226
get_ws_frame(unsigned char *in_buffer, size_t buf_len,
227
  unsigned char **payload_ptr, size_t *out_len)
228
3.59M
{
229
3.59M
  unsigned char opcode;
230
3.59M
  unsigned char fin;
231
3.59M
  unsigned char masked;
232
3.59M
  size_t payload_len;
233
3.59M
  size_t pos;
234
3.59M
  int length_field;
235
236
3.59M
  if (buf_len < 2) {
237
227
    return INCOMPLETE_DATA;
238
227
  }
239
240
3.59M
  opcode = in_buffer[0] & 0x0F;
241
3.59M
  fin = (in_buffer[0] >> 7) & 0x01;
242
3.59M
  masked = (in_buffer[1] >> 7) & 0x01;
243
244
3.59M
  payload_len = 0;
245
3.59M
  pos = 2;
246
3.59M
  length_field = in_buffer[1] & (~0x80);
247
248
3.59M
  if (length_field <= 125) {
249
3.54M
    payload_len = length_field;
250
3.54M
  } else if (length_field == 126) { /* msglen is 16bit */
251
5.47k
    uint16_t tmp16;
252
5.47k
    if (buf_len < 4)
253
2
      return INCOMPLETE_DATA;
254
5.47k
    memcpy(&tmp16, in_buffer + pos, 2);
255
5.47k
    payload_len = ntohs(tmp16);
256
5.47k
    pos += 2;
257
38.5k
  } else if (length_field == 127) { /* msglen is 64bit */
258
38.5k
    int i;
259
38.5k
    uint64_t tmp64 = 0;
260
38.5k
    if (buf_len < 10)
261
13
      return INCOMPLETE_DATA;
262
    /* swap bytes from big endian to host byte order */
263
347k
    for (i = 56; i >= 0; i -= 8) {
264
308k
      tmp64 |= (uint64_t)in_buffer[pos++] << i;
265
308k
    }
266
38.5k
    if (tmp64 > WS_MAX_RECV_FRAME_SZ) {
267
      /* Implementation limitation, we support up to 10 MiB
268
       * length, as a DoS prevention measure.
269
       */
270
36.5k
      event_warn("%s: frame length %" PRIu64 " exceeds %" PRIu64 "\n",
271
36.5k
        __func__, tmp64, (uint64_t)WS_MAX_RECV_FRAME_SZ);
272
      /* Calling code needs these values; do the best we can here.
273
       * Caller will close the connection anyway.
274
       */
275
36.5k
      *payload_ptr = in_buffer + pos;
276
36.5k
      *out_len = 0;
277
36.5k
      return ERROR_FRAME;
278
36.5k
    }
279
2.05k
    payload_len = (size_t)tmp64;
280
2.05k
  }
281
3.55M
  if (buf_len < payload_len + pos + (masked ? 4u : 0u)) {
282
275
    return INCOMPLETE_DATA;
283
275
  }
284
285
  /* According to RFC it seems that unmasked data should be prohibited
286
   * but we support it for nonconformant clients
287
   */
288
3.55M
  if (masked) {
289
62.3k
    unsigned char *c, *mask;
290
62.3k
    size_t i;
291
292
62.3k
    mask = in_buffer + pos; /* first 4 bytes are mask bytes */
293
62.3k
    pos += 4;
294
295
    /* unmask data */
296
62.3k
    c = in_buffer + pos;
297
24.9M
    for (i = 0; i < payload_len; i++) {
298
24.8M
      c[i] = c[i] ^ mask[i % 4u];
299
24.8M
    }
300
62.3k
  }
301
302
3.55M
  *payload_ptr = in_buffer + pos;
303
3.55M
  *out_len = payload_len;
304
305
  /* are reserved for further frames */
306
3.55M
  if ((opcode >= 3 && opcode <= 7) || (opcode >= 0xb))
307
113k
    return ERROR_FRAME;
308
309
3.44M
  if (opcode <= 0x3 && !fin) {
310
3.30M
    return INCOMPLETE_FRAME;
311
3.30M
  }
312
135k
  return opcode;
313
3.44M
}
314
315
316
static void
317
ws_evhttp_read_cb(struct bufferevent *bufev, void *arg)
318
1.01k
{
319
1.01k
  struct evws_connection *evws = arg;
320
1.01k
  unsigned char *payload;
321
1.01k
  enum WebSocketFrameType type;
322
1.01k
  size_t msg_len, in_len, header_sz;
323
1.01k
  struct evbuffer *input = bufferevent_get_input(evws->bufev);
324
325
1.01k
  bufferevent_incref_and_lock_(evws->bufev);
326
3.59M
  while ((in_len = evbuffer_get_length(input))) {
327
3.59M
    unsigned char *data = evbuffer_pullup(input, in_len);
328
3.59M
    if (data == NULL) {
329
0
      goto bailout;
330
0
    }
331
332
3.59M
    type = get_ws_frame(data, in_len, &payload, &msg_len);
333
3.59M
    if (type == INCOMPLETE_DATA) {
334
      /* incomplete data received, wait for next chunk */
335
517
      goto bailout;
336
517
    }
337
3.59M
    header_sz = payload - data;
338
3.59M
    evbuffer_drain(input, header_sz);
339
3.59M
    data = evbuffer_pullup(input, -1);
340
341
3.59M
    switch (type) {
342
19.4k
    case TEXT_FRAME:
343
25.9k
    case BINARY_FRAME:
344
25.9k
      if (evws->incomplete_frames != NULL) {
345
19.3k
        if (ws_message_limit_exceeded_(evws, msg_len)) {
346
0
          break;
347
0
        }
348
        /* we already have incomplete frames in internal buffer
349
         * and need to concatenate them with final one */
350
19.3k
        evbuffer_add(evws->incomplete_frames, data, msg_len);
351
352
19.3k
        data = evbuffer_pullup(evws->incomplete_frames, -1);
353
354
19.3k
        evws->cb(evws, type, data,
355
19.3k
          evbuffer_get_length(evws->incomplete_frames), evws->cb_arg);
356
19.3k
        evbuffer_free(evws->incomplete_frames);
357
19.3k
        evws->incomplete_frames = NULL;
358
19.3k
      } else {
359
6.62k
        evws->cb(evws, type, data, msg_len, evws->cb_arg);
360
6.62k
      }
361
25.9k
      break;
362
3.30M
    case INCOMPLETE_FRAME:
363
      /* we received full frame until get fin and need to
364
       * postpone callback until all data arrives */
365
3.30M
      if (evws->incomplete_frames == NULL) {
366
19.8k
        evws->incomplete_frames = evbuffer_new();
367
19.8k
      }
368
3.30M
      if (evws->incomplete_frames == NULL) {
369
0
        evws_force_disconnect_(evws);
370
0
        break;
371
0
      }
372
3.30M
      if (ws_message_limit_exceeded_(evws, msg_len)) {
373
0
        break;
374
0
      }
375
3.30M
      evbuffer_remove_buffer(input, evws->incomplete_frames, msg_len);
376
3.30M
      continue;
377
40.7k
    case CLOSING_FRAME:
378
190k
    case ERROR_FRAME:
379
190k
      evws_force_disconnect_(evws);
380
190k
      break;
381
29.5k
    case PING_FRAME:
382
63.7k
    case PONG_FRAME:
383
      /* ping or pong frame */
384
63.7k
      break;
385
5.55k
    default:
386
5.55k
      event_warn("%s: unexpected frame type %d\n", __func__, type);
387
5.55k
      evws_force_disconnect_(evws);
388
3.59M
    }
389
285k
    evbuffer_drain(input, msg_len);
390
285k
  }
391
392
1.01k
bailout:
393
1.01k
  bufferevent_decref_and_unlock_(evws->bufev);
394
1.01k
}
395
396
static void
397
ws_evhttp_error_cb(struct bufferevent *bufev, short what, void *arg)
398
0
{
399
  /* when client just disappears after connection (wscat closed by Cmd+Q) */
400
0
  if (what & BEV_EVENT_EOF) {
401
0
    close_after_write_cb(bufev, arg);
402
0
  }
403
0
}
404
405
struct evws_connection *
406
evws_new_session(
407
  struct evhttp_request *req, ws_on_msg_cb cb, void *arg, int options)
408
1.01k
{
409
1.01k
  struct evws_connection *evws = NULL;
410
1.01k
  struct evkeyvalq *in_hdrs;
411
1.01k
  const char *upgrade, *connection, *ws_key, *ws_protocol;
412
1.01k
  struct evkeyvalq *out_hdrs;
413
1.01k
  struct evhttp_connection *evcon;
414
1.01k
  struct evhttp *ws_http_server;
415
1.01k
  int req_owned = 1;
416
417
1.01k
  in_hdrs = evhttp_request_get_input_headers(req);
418
1.01k
  upgrade = evhttp_find_header(in_hdrs, "Upgrade");
419
1.01k
  if (upgrade == NULL || evutil_ascii_strcasecmp(upgrade, "websocket"))
420
0
    goto error;
421
422
1.01k
  connection = evhttp_find_header(in_hdrs, "Connection");
423
1.01k
  if (connection == NULL || evutil_ascii_strcasestr(connection, "Upgrade") == NULL)
424
0
    goto error;
425
426
1.01k
  ws_key = evhttp_find_header(in_hdrs, "Sec-WebSocket-Key");
427
1.01k
  if (ws_key == NULL)
428
0
    goto error;
429
430
1.01k
  out_hdrs = evhttp_request_get_output_headers(req);
431
1.01k
  evhttp_add_header(out_hdrs, "Upgrade", "websocket");
432
1.01k
  evhttp_add_header(out_hdrs, "Connection", "Upgrade");
433
434
1.01k
  evhttp_add_header(out_hdrs, "Sec-WebSocket-Accept",
435
1.01k
    ws_gen_accept_key(ws_key, (char[32]){0}));
436
437
1.01k
  ws_protocol = evhttp_find_header(in_hdrs, "Sec-WebSocket-Protocol");
438
1.01k
  if (ws_protocol != NULL)
439
0
    evhttp_add_header(out_hdrs, "Sec-WebSocket-Protocol", ws_protocol);
440
441
1.01k
  if ((evws = mm_calloc(1, sizeof(struct evws_connection))) == NULL) {
442
0
    event_warn("%s: calloc failed", __func__);
443
0
    goto error;
444
0
  }
445
446
1.01k
  evws->cb = cb;
447
1.01k
  evws->cb_arg = arg;
448
449
1.01k
  evcon = evhttp_request_get_connection(req);
450
1.01k
  ws_http_server = evcon->http_server;
451
452
  /* evhttp_start_ws_ frees both req and evcon on success */
453
1.01k
  evws->bufev = evhttp_start_ws_(req);
454
1.01k
  if (evws->bufev == NULL) {
455
0
    goto error;
456
0
  }
457
1.01k
  req_owned = 0;
458
459
1.01k
  if (options & BEV_OPT_THREADSAFE) {
460
0
    if (bufferevent_enable_locking_(evws->bufev, NULL) < 0)
461
0
      goto error;
462
0
  }
463
464
1.01k
  bufferevent_setcb(
465
1.01k
    evws->bufev, ws_evhttp_read_cb, NULL, ws_evhttp_error_cb, evws);
466
467
1.01k
  evws->http_server = ws_http_server;
468
1.01k
  TAILQ_INSERT_TAIL(&evws->http_server->ws_sessions, evws, next);
469
1.01k
  evws->http_server->connection_cnt++;
470
471
1.01k
  return evws;
472
473
0
error:
474
0
  if (evws)
475
0
    evws_connection_free(evws);
476
477
0
  if (req_owned)
478
0
    evhttp_send_reply(req, HTTP_BADREQUEST, NULL, NULL);
479
0
  return NULL;
480
1.01k
}
481
482
static void
483
make_ws_frame(struct evbuffer *output, enum WebSocketFrameType frame_type,
484
  unsigned char *msg, size_t len)
485
0
{
486
0
  size_t pos = 0;
487
0
  unsigned char header[16] = {0};
488
489
0
  header[pos++] = (unsigned char)frame_type | 0x80; /* fin */
490
0
  if (len <= 125) {
491
0
    header[pos++] = len;
492
0
  } else if (len <= 65535) {
493
0
    header[pos++] = 126;         /* 16 bit length */
494
0
    header[pos++] = (len >> 8) & 0xFF; /* rightmost first */
495
0
    header[pos++] = len & 0xFF;
496
0
  } else {        /* >2^16-1 */
497
0
    int i;
498
0
    const uint64_t tmp64 = len;
499
0
    header[pos++] = 127;            /* 64 bit length */
500
    /* swap bytes from host byte order to big endian */
501
0
    for (i = 56; i >= 0; i -= 8) {
502
0
      header[pos++] = tmp64 >> i & 0xFFu;
503
0
    }
504
0
  }
505
0
  evbuffer_add(output, header, pos);
506
0
  evbuffer_add(output, msg, len);
507
0
}
508
509
static void
510
evws_send(struct evws_connection *evws, enum WebSocketFrameType frame_type,
511
  const char *packet_str, size_t str_len)
512
0
{
513
0
  struct evbuffer *output;
514
515
0
  bufferevent_lock(evws->bufev);
516
0
  output = bufferevent_get_output(evws->bufev);
517
0
  make_ws_frame(output, frame_type, (unsigned char *)packet_str, str_len);
518
0
  bufferevent_unlock(evws->bufev);
519
0
}
520
521
void
522
evws_send_text(struct evws_connection *evws, const char *packet_str)
523
0
{
524
0
  evws_send(evws, TEXT_FRAME, packet_str, strlen(packet_str));
525
0
}
526
527
void
528
evws_send_binary(
529
  struct evws_connection *evws, const char *packet_data, size_t packet_len)
530
0
{
531
0
  evws_send(evws, BINARY_FRAME, packet_data, packet_len);
532
0
}
533
534
void
535
evws_connection_set_closecb(
536
  struct evws_connection *evws, ws_on_close_cb cb, void *cbarg)
537
0
{
538
0
  evws->cbclose = cb;
539
0
  evws->cbclose_arg = cbarg;
540
0
}
541
542
struct bufferevent *
543
evws_connection_get_bufferevent(struct evws_connection *evws)
544
0
{
545
0
  return evws->bufev;
546
0
}