Coverage Report

Created: 2025-11-24 06:14

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