Coverage Report

Created: 2026-08-31 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libsoup/libsoup/websocket/soup-websocket-connection.c
Line
Count
Source
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2
/*
3
 * soup-websocket-connection.c: This file was originally part of Cockpit.
4
 *
5
 * Copyright 2013, 2014 Red Hat, Inc.
6
 *
7
 * Cockpit is free software; you can redistribute it and/or modify it
8
 * under the terms of the GNU Lesser General Public License as published by
9
 * the Free Software Foundation; either version 2.1 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * Cockpit is distributed in the hope that it will be useful, but
13
 * WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with this library; If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include "config.h"
22
23
#include <string.h>
24
25
#include "soup-websocket-connection.h"
26
#include "soup-websocket-connection-private.h"
27
#include "soup-enum-types.h"
28
#include "soup-io-stream.h"
29
#include "soup-uri-utils-private.h"
30
#include "soup-websocket-extension.h"
31
32
/*
33
 * SoupWebsocketConnection:
34
 *
35
 * A WebSocket connection.
36
 *
37
 * A [class@WebsocketConnection] is a WebSocket connection to a peer.
38
 * This API is modeled after the W3C API for interacting with
39
 * WebSockets.
40
 *
41
 * The [property@WebsocketConnection:state] property will indicate the
42
 * state of the connection.
43
 *
44
 * Use [method@WebsocketConnection.send] to send a message to the peer.
45
 * When a message is received the [signal@WebsocketConnection::message]
46
 * signal will fire.
47
 *
48
 * The [method@WebsocketConnection.close] function will perform an
49
 * orderly close of the connection. The
50
 * [signal@WebsocketConnection::closed] signal will fire once the
51
 * connection closes, whether it was initiated by this side or the
52
 * peer.
53
 *
54
 * Connect to the [signal@WebsocketConnection::closing] signal to detect
55
 * when either peer begins closing the connection.
56
 */
57
58
/**
59
 * SoupWebsocketConnectionClass:
60
 * @message: default handler for the [signal@WebsocketConnection::message] signal
61
 * @error: default handler for the [signal@WebsocketConnection::error] signal
62
 * @closing: the default handler for the [signal@WebsocketConnection:closing] signal
63
 * @closed: default handler for the [signal@WebsocketConnection::closed] signal
64
 * @pong: default handler for the [signal@WebsocketConnection::pong] signal
65
 *
66
 * The abstract base class for [class@WebsocketConnection].
67
 */
68
69
enum {
70
  PROP_0,
71
  PROP_IO_STREAM,
72
  PROP_CONNECTION_TYPE,
73
  PROP_URI,
74
  PROP_ORIGIN,
75
  PROP_PROTOCOL,
76
  PROP_STATE,
77
  PROP_MAX_INCOMING_PAYLOAD_SIZE,
78
  PROP_KEEPALIVE_INTERVAL,
79
  PROP_KEEPALIVE_PONG_TIMEOUT,
80
  PROP_EXTENSIONS,
81
  PROP_MAX_TOTAL_MESSAGE_SIZE,
82
83
        LAST_PROPERTY
84
};
85
86
static GParamSpec *properties[LAST_PROPERTY] = { NULL, };
87
88
enum {
89
  MESSAGE,
90
  ERROR,
91
  CLOSING,
92
  CLOSED,
93
  PONG,
94
  NUM_SIGNALS
95
};
96
97
static guint signals[NUM_SIGNALS] = { 0, };
98
99
typedef enum {
100
  SOUP_WEBSOCKET_QUEUE_NORMAL = 0,
101
  SOUP_WEBSOCKET_QUEUE_URGENT = 1 << 0,
102
  SOUP_WEBSOCKET_QUEUE_LAST = 1 << 1,
103
} SoupWebsocketQueueFlags;
104
105
typedef struct {
106
  GBytes *data;
107
  gsize sent;
108
  gsize amount;
109
  SoupWebsocketQueueFlags flags;
110
  gboolean pending;
111
} Frame;
112
113
struct _SoupWebsocketConnection {
114
        GObject parent_instance;
115
};
116
117
typedef struct {
118
  GIOStream *io_stream;
119
  SoupWebsocketConnectionType connection_type;
120
  GUri *uri;
121
  char *origin;
122
  char *protocol;
123
  guint64 max_incoming_payload_size;
124
  guint64 max_total_message_size;
125
  guint keepalive_interval;
126
  guint keepalive_pong_timeout;
127
  guint64 last_keepalive_seq_num;
128
129
  /* Each keepalive ping uses a unique payload. This hash table uses such
130
   * a ping payload as a key to the corresponding GSource that will
131
   * timeout if the pong is not received in time.
132
   */
133
  GHashTable *outstanding_pongs;
134
135
  gushort peer_close_code;
136
  char *peer_close_data;
137
  gboolean close_sent;
138
  gboolean close_received;
139
  gboolean dirty_close;
140
  GSource *close_timeout;
141
142
  gboolean io_closing;
143
  gboolean io_closed;
144
145
  GPollableInputStream *input;
146
  GSource *input_source;
147
  GByteArray *incoming;
148
149
  GPollableOutputStream *output;
150
  GSource *output_source;
151
  GQueue outgoing;
152
153
  /* Current message being assembled */
154
  guint8 message_opcode;
155
  GByteArray *message_data;
156
157
  /* Only for use by the libsoup test suite. Can be removed at any point.
158
   * Activating this violates RFC 6455 Section 5.5.2 which stipulates that
159
   * a ping MUST always be replied with a pong.
160
   */
161
  gboolean suppress_pongs_for_tests;
162
163
  GSource *keepalive_timeout;
164
165
  GList *extensions;
166
} SoupWebsocketConnectionPrivate;
167
168
0
#define MAX_INCOMING_PAYLOAD_SIZE_DEFAULT   128 * 1024
169
0
#define READ_BUFFER_SIZE 1024
170
0
#define MASK_LENGTH 4
171
172
/* If a pong payload begins with these bytes, we assume it is a pong from one of
173
 * our keepalive pings.
174
 */
175
0
#define KEEPALIVE_PAYLOAD_PREFIX "libsoup-keepalive-"
176
177
0
G_DEFINE_FINAL_TYPE_WITH_PRIVATE (SoupWebsocketConnection, soup_websocket_connection, G_TYPE_OBJECT)
178
0
179
0
static void queue_frame (SoupWebsocketConnection *self, SoupWebsocketQueueFlags flags,
180
0
       gpointer data, gsize len, gsize amount);
181
0
182
0
static void emit_error_and_close (SoupWebsocketConnection *self,
183
0
          GError *error, gboolean prejudice);
184
0
185
0
static void protocol_error_and_close (SoupWebsocketConnection *self);
186
0
187
0
static gboolean on_web_socket_input (GObject *pollable_stream,
188
0
             gpointer user_data);
189
0
static gboolean on_web_socket_output (GObject *pollable_stream,
190
0
              gpointer user_data);
191
0
192
0
/* Code below is based on g_utf8_validate() implementation,
193
0
 * but handling NULL characters as valid, as expected by
194
0
 * WebSockets and compliant with RFC 3629.
195
0
 */
196
0
#define VALIDATE_BYTE(mask, expect)                             \
197
0
        G_STMT_START {                                          \
198
0
          if (G_UNLIKELY((*(guchar *)p & (mask)) != (expect)))  \
199
0
                  return FALSE;                                 \
200
0
        } G_STMT_END
201
202
/* see IETF RFC 3629 Section 4 */
203
static gboolean
204
utf8_validate (const char *str,
205
               gsize max_len)
206
207
0
{
208
0
        const gchar *p;
209
210
0
        for (p = str; ((p - str) < max_len); p++) {
211
0
                if (*(guchar *)p < 128)
212
0
                        /* done */;
213
0
                else {
214
0
                        if (*(guchar *)p < 0xe0) { /* 110xxxxx */
215
0
                                if (G_UNLIKELY (max_len - (p - str) < 2))
216
0
                                        return FALSE;
217
218
0
                                if (G_UNLIKELY (*(guchar *)p < 0xc2))
219
0
                                        return FALSE;
220
0
                        } else {
221
0
                                if (*(guchar *)p < 0xf0) { /* 1110xxxx */
222
0
                                        if (G_UNLIKELY (max_len - (p - str) < 3))
223
0
                                                return FALSE;
224
225
0
                                        switch (*(guchar *)p++ & 0x0f) {
226
0
                                        case 0:
227
0
                                                VALIDATE_BYTE(0xe0, 0xa0); /* 0xa0 ... 0xbf */
228
0
                                                break;
229
0
                                        case 0x0d:
230
0
                                                VALIDATE_BYTE(0xe0, 0x80); /* 0x80 ... 0x9f */
231
0
                                                break;
232
0
                                        default:
233
0
                                                VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
234
0
                                        }
235
0
                                } else if (*(guchar *)p < 0xf5) { /* 11110xxx excluding out-of-range */
236
0
                                        if (G_UNLIKELY (max_len - (p - str) < 4))
237
0
                                                return FALSE;
238
239
0
                                        switch (*(guchar *)p++ & 0x07) {
240
0
                                        case 0:
241
0
                                                VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
242
0
                                                if (G_UNLIKELY((*(guchar *)p & 0x30) == 0))
243
0
                                                        return FALSE;
244
0
                                                break;
245
0
                                        case 4:
246
0
                                                VALIDATE_BYTE(0xf0, 0x80); /* 0x80 ... 0x8f */
247
0
                                                break;
248
0
                                        default:
249
0
                                                VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
250
0
                                        }
251
0
                                        p++;
252
0
                                        VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
253
0
                                } else {
254
0
                                        return FALSE;
255
0
                                }
256
0
                        }
257
258
0
                        p++;
259
0
                        VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
260
0
                }
261
0
        }
262
263
0
        return TRUE;
264
0
}
265
266
#undef VALIDATE_BYTE
267
268
static void
269
frame_free (gpointer data)
270
0
{
271
0
  Frame *frame = data;
272
273
0
  if (frame) {
274
0
    g_bytes_unref (frame->data);
275
0
    g_slice_free (Frame, frame);
276
0
  }
277
0
}
278
279
static void
280
soup_websocket_connection_init (SoupWebsocketConnection *self)
281
0
{
282
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
283
284
0
  priv->incoming = g_byte_array_sized_new (1024);
285
0
  g_queue_init (&priv->outgoing);
286
0
}
287
288
static void
289
on_iostream_closed (GObject *source,
290
                    GAsyncResult *result,
291
                    gpointer user_data)
292
0
{
293
0
  SoupWebsocketConnection *self = user_data;
294
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
295
0
  GError *error = NULL;
296
297
  /* We treat connection as closed even if close fails */
298
0
  priv->io_closed = TRUE;
299
0
  g_io_stream_close_finish (priv->io_stream, result, &error);
300
301
0
  if (error) {
302
0
    g_debug ("error closing web socket stream: %s", error->message);
303
0
    if (!priv->dirty_close)
304
0
      g_signal_emit (self, signals[ERROR], 0, error);
305
0
    priv->dirty_close = TRUE;
306
0
    g_error_free (error);
307
0
  }
308
309
0
  g_assert (soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_CLOSED);
310
0
  g_debug ("closed: completed io stream close");
311
0
  g_signal_emit (self, signals[CLOSED], 0);
312
313
0
  g_object_unref (self);
314
0
}
315
316
static void
317
soup_websocket_connection_start_input_source (SoupWebsocketConnection *self)
318
0
{
319
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
320
321
0
  if (priv->input_source)
322
0
    return;
323
324
0
  priv->input_source = g_pollable_input_stream_create_source (priv->input, NULL);
325
0
  g_source_set_static_name (priv->input_source, "SoupWebsocketConnection input");
326
0
  g_source_set_callback (priv->input_source, (GSourceFunc)on_web_socket_input, self, NULL);
327
0
  g_source_attach (priv->input_source, g_main_context_get_thread_default ());
328
0
}
329
330
static void
331
soup_websocket_connection_stop_input_source (SoupWebsocketConnection *self)
332
0
{
333
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
334
335
0
  if (priv->input_source) {
336
0
    g_debug ("stopping input source");
337
0
    g_source_destroy (priv->input_source);
338
0
    g_clear_pointer (&priv->input_source, g_source_unref);
339
0
  }
340
0
}
341
342
static void
343
soup_websocket_connection_start_output_source (SoupWebsocketConnection *self)
344
0
{
345
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
346
347
0
  if (priv->output_source)
348
0
    return;
349
350
0
  priv->output_source = g_pollable_output_stream_create_source (priv->output, NULL);
351
0
  g_source_set_static_name (priv->output_source, "SoupWebsocketConnection output");
352
0
  g_source_set_callback (priv->output_source, (GSourceFunc)on_web_socket_output, self, NULL);
353
0
  g_source_attach (priv->output_source, g_main_context_get_thread_default ());
354
0
}
355
356
static void
357
soup_websocket_connection_stop_output_source (SoupWebsocketConnection *self)
358
0
{
359
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
360
361
0
  if (priv->output_source) {
362
0
    g_debug ("stopping output source");
363
0
    g_source_destroy (priv->output_source);
364
0
    g_clear_pointer (&priv->output_source, g_source_unref);
365
0
  }
366
0
}
367
368
static void
369
keepalive_stop_timeout (SoupWebsocketConnection *self)
370
0
{
371
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
372
373
0
  if (priv->keepalive_timeout) {
374
0
    g_source_destroy (priv->keepalive_timeout);
375
0
    g_clear_pointer (&priv->keepalive_timeout, g_source_unref);
376
0
  }
377
0
}
378
379
static void
380
keepalive_stop_outstanding_pongs (SoupWebsocketConnection *self)
381
0
{
382
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
383
384
0
        g_clear_pointer (&priv->outstanding_pongs, g_hash_table_destroy);
385
0
}
386
387
static void
388
close_io_stop_timeout (SoupWebsocketConnection *self)
389
0
{
390
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
391
392
0
  if (priv->close_timeout) {
393
0
    g_source_destroy (priv->close_timeout);
394
0
    g_clear_pointer (&priv->close_timeout, g_source_unref);
395
0
  }
396
0
}
397
398
static void
399
close_io_stream (SoupWebsocketConnection *self)
400
0
{
401
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
402
403
0
  keepalive_stop_timeout (self);
404
0
  keepalive_stop_outstanding_pongs (self);
405
0
  close_io_stop_timeout (self);
406
407
0
  if (!priv->io_closing) {
408
0
    soup_websocket_connection_stop_input_source (self);
409
0
    soup_websocket_connection_stop_output_source (self);
410
0
    priv->io_closing = TRUE;
411
0
    g_debug ("closing io stream");
412
0
    g_io_stream_close_async (priv->io_stream, G_PRIORITY_DEFAULT,
413
0
           NULL, on_iostream_closed, g_object_ref (self));
414
0
  }
415
416
0
  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATE]);
417
0
}
418
419
static void
420
shutdown_wr_io_stream (SoupWebsocketConnection *self)
421
0
{
422
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
423
0
  GSocket *socket;
424
0
  GIOStream *base_iostream;
425
0
  GError *error = NULL;
426
427
0
  soup_websocket_connection_stop_output_source (self);
428
429
0
  base_iostream = SOUP_IS_IO_STREAM (priv->io_stream) ?
430
0
    soup_io_stream_get_base_iostream (SOUP_IO_STREAM (priv->io_stream)) :
431
0
    priv->io_stream;
432
433
0
  if (G_IS_SOCKET_CONNECTION (base_iostream)) {
434
0
    socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (base_iostream));
435
0
    g_socket_shutdown (socket, FALSE, TRUE, &error);
436
0
    if (error != NULL) {
437
0
      g_debug ("error shutting down io stream: %s", error->message);
438
0
      g_error_free (error);
439
0
    }
440
0
  }
441
442
0
  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATE]);
443
0
}
444
445
static gboolean
446
on_timeout_close_io (gpointer user_data)
447
0
{
448
0
  SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (user_data);
449
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
450
451
0
  priv->close_timeout = 0;
452
453
0
  g_debug ("peer did not close io when expected");
454
0
  close_io_stream (self);
455
456
0
  return FALSE;
457
0
}
458
459
static void
460
close_io_after_timeout (SoupWebsocketConnection *self)
461
0
{
462
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
463
0
  const int timeout = 5;
464
465
0
  if (priv->close_timeout)
466
0
    return;
467
468
0
  g_debug ("waiting %d seconds for peer to close io", timeout);
469
0
  priv->close_timeout = g_timeout_source_new_seconds (timeout);
470
0
  g_source_set_static_name (priv->close_timeout, "SoupWebsocketConnection close timeout");
471
0
  g_source_set_callback (priv->close_timeout, on_timeout_close_io, self, NULL);
472
0
  g_source_attach (priv->close_timeout, g_main_context_get_thread_default ());
473
0
}
474
475
static void
476
xor_with_mask (const guint8 *mask,
477
         guint8 *data,
478
         gsize len)
479
0
{
480
0
  gsize n;
481
482
  /* Do the masking */
483
0
  for (n = 0; n < len; n++)
484
0
    data[n] ^= mask[n & 3];
485
0
}
486
487
static void
488
send_message (SoupWebsocketConnection *self,
489
        SoupWebsocketQueueFlags flags,
490
        guint8 opcode,
491
        const guint8 *data,
492
        gsize length)
493
0
{
494
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
495
0
  gsize buffered_amount;
496
0
  GByteArray *bytes;
497
0
  gsize frame_len;
498
0
  guint8 *outer;
499
0
  guint8 mask_offset = 0;
500
0
  GBytes *filtered_bytes;
501
0
  GList *l;
502
0
  GError *error = NULL;
503
504
0
  if (!(soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_OPEN)) {
505
0
    g_debug ("Ignoring message since the connection is closed or is closing");
506
0
    return;
507
0
  }
508
509
0
  bytes = g_byte_array_sized_new (14 + length);
510
0
  outer = bytes->data;
511
0
  outer[0] = 0x80 | opcode;
512
513
0
  filtered_bytes = g_bytes_new_static (data, length);
514
0
  for (l = priv->extensions; l != NULL; l = g_list_next (l)) {
515
0
    SoupWebsocketExtension *extension;
516
517
0
    extension = (SoupWebsocketExtension *)l->data;
518
0
    filtered_bytes = soup_websocket_extension_process_outgoing_message (extension, outer, filtered_bytes, &error);
519
0
    if (error) {
520
0
      g_byte_array_free (bytes, TRUE);
521
0
      emit_error_and_close (self, error, FALSE);
522
0
      return;
523
0
    }
524
0
  }
525
526
0
  data = g_bytes_get_data (filtered_bytes, &length);
527
0
  buffered_amount = length;
528
529
  /* If control message, check payload size */
530
0
  if (opcode & 0x08) {
531
0
    if (length > 125) {
532
0
      g_debug ("WebSocket control message payload exceeds size limit");
533
0
      protocol_error_and_close (self);
534
0
      g_byte_array_free (bytes, TRUE);
535
0
      g_bytes_unref (filtered_bytes);
536
0
      return;
537
0
    }
538
539
0
    buffered_amount = 0;
540
0
  }
541
542
0
  if (length < 126) {
543
0
    outer[1] = (0xFF & length); /* mask | 7-bit-len */
544
0
    bytes->len = 2;
545
0
  } else if (length < 65536) {
546
0
    outer[1] = 126; /* mask | 16-bit-len */
547
0
    outer[2] = (length >> 8) & 0xFF;
548
0
    outer[3] = (length >> 0) & 0xFF;
549
0
    bytes->len = 4;
550
0
  } else {
551
0
    outer[1] = 127; /* mask | 64-bit-len */
552
0
#if GLIB_SIZEOF_SIZE_T > 4
553
0
    outer[2] = (length >> 56) & 0xFF;
554
0
    outer[3] = (length >> 48) & 0xFF;
555
0
    outer[4] = (length >> 40) & 0xFF;
556
0
    outer[5] = (length >> 32) & 0xFF;
557
#else
558
    outer[2] = outer[3] = outer[4] = outer[5] = 0;
559
#endif
560
0
    outer[6] = (length >> 24) & 0xFF;
561
0
    outer[7] = (length >> 16) & 0xFF;
562
0
    outer[8] = (length >> 8) & 0xFF;
563
0
    outer[9] = (length >> 0) & 0xFF;
564
0
    bytes->len = 10;
565
0
  }
566
567
  /* The server side doesn't need to mask, so we don't. There's
568
   * probably a client somewhere that's not expecting it.
569
   */
570
0
  if (priv->connection_type == SOUP_WEBSOCKET_CONNECTION_CLIENT) {
571
0
    guint32 rnd = g_random_int ();
572
0
    outer[1] |= 0x80;
573
0
    mask_offset = bytes->len;
574
0
    memcpy (outer + mask_offset, &rnd, sizeof (rnd));
575
0
    bytes->len += MASK_LENGTH;
576
0
  }
577
578
0
  g_byte_array_append (bytes, data, length);
579
580
0
  if (priv->connection_type == SOUP_WEBSOCKET_CONNECTION_CLIENT)
581
0
    xor_with_mask (bytes->data + mask_offset, bytes->data + mask_offset + MASK_LENGTH, length);
582
583
0
  frame_len = bytes->len;
584
0
  queue_frame (self, flags, g_byte_array_free (bytes, FALSE),
585
0
         frame_len, buffered_amount);
586
0
  g_bytes_unref (filtered_bytes);
587
0
  g_debug ("queued %d frame of len %u", (int)opcode, (guint)frame_len);
588
0
}
589
590
static void
591
send_close (SoupWebsocketConnection *self,
592
      SoupWebsocketQueueFlags flags,
593
      gushort code,
594
      const char *reason)
595
0
{
596
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
597
  /* Note that send_message truncates as expected */
598
0
  char buffer[128];
599
0
  gsize len = 0;
600
601
0
  if (code != 0) {
602
0
    buffer[len++] = code >> 8;
603
0
    buffer[len++] = code & 0xFF;
604
0
    if (reason)
605
0
      len += g_strlcpy (buffer + len, reason, sizeof (buffer) - len);
606
0
  }
607
608
0
  send_message (self, flags, 0x08, (guint8 *)buffer, len);
609
0
  priv->close_sent = TRUE;
610
611
0
  keepalive_stop_timeout (self);
612
0
  keepalive_stop_outstanding_pongs (self);
613
0
}
614
615
static void
616
emit_error_and_close (SoupWebsocketConnection *self,
617
          GError *error,
618
          gboolean prejudice)
619
0
{
620
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
621
0
  gboolean ignore = FALSE;
622
0
  gushort code;
623
624
0
  if (soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_CLOSED) {
625
0
    g_error_free (error);
626
0
    return;
627
0
  }
628
629
0
  if (error && error->domain == SOUP_WEBSOCKET_ERROR)
630
0
    code = error->code;
631
0
  else
632
0
    code = SOUP_WEBSOCKET_CLOSE_GOING_AWAY;
633
634
0
  priv->dirty_close = TRUE;
635
0
  g_signal_emit (self, signals[ERROR], 0, error);
636
0
  g_error_free (error);
637
638
  /* If already closing, just ignore this stuff */
639
0
  switch (soup_websocket_connection_get_state (self)) {
640
0
  case SOUP_WEBSOCKET_STATE_CLOSED:
641
0
    ignore = TRUE;
642
0
    break;
643
0
  case SOUP_WEBSOCKET_STATE_CLOSING:
644
0
    ignore = !prejudice;
645
0
    break;
646
0
  default:
647
0
    break;
648
0
  }
649
650
0
  if (ignore) {
651
0
    g_debug ("already closing/closed, ignoring error");
652
0
  } else if (prejudice) {
653
0
    g_debug ("forcing close due to error");
654
0
    close_io_stream (self);
655
0
  } else {
656
0
    g_debug ("requesting close due to error");
657
0
    send_close (self, SOUP_WEBSOCKET_QUEUE_URGENT | SOUP_WEBSOCKET_QUEUE_LAST, code, NULL);
658
0
  }
659
0
}
660
661
static void
662
protocol_error_and_close_full (SoupWebsocketConnection *self,
663
                               gboolean prejudice)
664
0
{
665
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
666
0
  GError *error;
667
668
0
  error = g_error_new_literal (SOUP_WEBSOCKET_ERROR,
669
0
             SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR,
670
0
             priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ?
671
0
             "Received invalid WebSocket response from the client" :
672
0
             "Received invalid WebSocket response from the server");
673
0
  emit_error_and_close (self, error, prejudice);
674
0
}
675
676
static void
677
protocol_error_and_close (SoupWebsocketConnection *self)
678
0
{
679
0
  protocol_error_and_close_full (self, FALSE);
680
0
}
681
682
static void
683
bad_data_error_and_close (SoupWebsocketConnection *self)
684
0
{
685
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
686
0
  GError *error;
687
688
0
  error = g_error_new_literal (SOUP_WEBSOCKET_ERROR,
689
0
             SOUP_WEBSOCKET_CLOSE_BAD_DATA,
690
0
             priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ?
691
0
             "Received invalid WebSocket data from the client" :
692
0
             "Received invalid WebSocket data from the server");
693
0
  emit_error_and_close (self, error, FALSE);
694
0
}
695
696
static void
697
too_big_incoming_payload_error_and_close (SoupWebsocketConnection *self,
698
                                          guint64 payload_len)
699
0
{
700
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
701
0
  GError *error;
702
703
0
  error = g_error_new_literal (SOUP_WEBSOCKET_ERROR,
704
0
             SOUP_WEBSOCKET_CLOSE_TOO_BIG,
705
0
             priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ?
706
0
             "Received WebSocket payload from the client larger than configured max-incoming-payload-size" :
707
0
             "Received WebSocket payload from the server larger than configured max-incoming-payload-size");
708
0
  g_debug ("%s is trying to frame of size %" G_GUINT64_FORMAT " or greater, but max supported size is %" G_GUINT64_FORMAT,
709
0
     priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ? "server" : "client",
710
0
           payload_len, priv->max_incoming_payload_size);
711
0
  emit_error_and_close (self, error, TRUE);
712
0
}
713
714
static void
715
too_big_message_error_and_close (SoupWebsocketConnection *self,
716
                                 guint64 len)
717
0
{
718
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
719
0
  GError *error;
720
721
0
  error = g_error_new_literal (SOUP_WEBSOCKET_ERROR,
722
0
             SOUP_WEBSOCKET_CLOSE_TOO_BIG,
723
0
             priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ?
724
0
             "Received WebSocket payload from the client larger than configured max-total-message-size" :
725
0
             "Received WebSocket payload from the server larger than configured max-total-message-size");
726
0
  g_debug ("%s received message of size %" G_GUINT64_FORMAT " or greater, but max supported size is %" G_GUINT64_FORMAT,
727
0
           priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ? "server" : "client",
728
0
           len, priv->max_total_message_size);
729
0
  emit_error_and_close (self, error, TRUE);
730
0
}
731
732
static void
733
close_connection (SoupWebsocketConnection *self,
734
                  gushort                  code,
735
                  const char              *data)
736
0
{
737
0
  SoupWebsocketQueueFlags flags;
738
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
739
740
0
  if (priv->close_sent) {
741
0
    g_debug ("close code already sent");
742
0
    return;
743
0
  }
744
745
  /* Validate the closing code received by the peer */
746
0
  switch (code) {
747
0
  case SOUP_WEBSOCKET_CLOSE_NORMAL:
748
0
  case SOUP_WEBSOCKET_CLOSE_GOING_AWAY:
749
0
  case SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR:
750
0
  case SOUP_WEBSOCKET_CLOSE_UNSUPPORTED_DATA:
751
0
  case SOUP_WEBSOCKET_CLOSE_BAD_DATA:
752
0
  case SOUP_WEBSOCKET_CLOSE_POLICY_VIOLATION:
753
0
  case SOUP_WEBSOCKET_CLOSE_TOO_BIG:
754
0
    break;
755
0
  case SOUP_WEBSOCKET_CLOSE_NO_EXTENSION:
756
0
    if (priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER) {
757
0
      g_debug ("Wrong closing code %d received for a server connection",
758
0
               code);
759
0
    }
760
0
    break;
761
0
  case SOUP_WEBSOCKET_CLOSE_SERVER_ERROR:
762
0
    if (priv->connection_type != SOUP_WEBSOCKET_CONNECTION_SERVER) {
763
0
      g_debug ("Wrong closing code %d received for a non server connection",
764
0
               code);
765
0
    }
766
0
    break;
767
0
  case SOUP_WEBSOCKET_CLOSE_NO_STATUS:
768
    /* This is special case to send a close message with no body */
769
0
    code = 0;
770
0
    break;
771
0
  default:
772
0
    if (code < 3000 || code >= 5000) {
773
0
      g_debug ("Wrong closing code %d received", code);
774
0
      protocol_error_and_close (self);
775
0
      return;
776
0
    }
777
0
  }
778
779
0
  g_signal_emit (self, signals[CLOSING], 0);
780
781
0
  if (priv->close_received)
782
0
    g_debug ("responding to close request");
783
784
0
  flags = 0;
785
0
  if (priv->close_received)
786
0
    flags |= SOUP_WEBSOCKET_QUEUE_LAST;
787
0
  send_close (self, flags, code, data);
788
0
  close_io_after_timeout (self);
789
0
}
790
791
static void
792
receive_close (SoupWebsocketConnection *self,
793
         const guint8 *data,
794
         gsize len)
795
0
{
796
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
797
798
0
  priv->peer_close_code = 0;
799
0
  g_clear_pointer (&priv->peer_close_data, g_free);
800
0
  priv->close_received = TRUE;
801
802
0
  switch (len) {
803
0
  case 0:
804
    /* Send a clean close when having an empty payload */
805
0
    priv->peer_close_code = SOUP_WEBSOCKET_CLOSE_NO_STATUS;
806
0
    close_connection (self, 1000, NULL);
807
0
    return;
808
0
  case 1:
809
    /* Send a protocol error since the close code is incomplete */
810
0
    protocol_error_and_close (self);
811
0
    return;
812
0
  default:
813
    /* Store the code/data payload */
814
0
    priv->peer_close_code = (guint16)data[0] << 8 | data[1];
815
0
    break;
816
0
  }
817
818
        /* 1005, 1006 and 1015 are reserved values and MUST NOT be set as a status code in a Close control frame by an endpoint */
819
0
        switch (priv->peer_close_code) {
820
0
        case SOUP_WEBSOCKET_CLOSE_NO_STATUS:
821
0
        case SOUP_WEBSOCKET_CLOSE_ABNORMAL:
822
0
        case SOUP_WEBSOCKET_CLOSE_TLS_HANDSHAKE:
823
0
                g_debug ("received a broken close frame containing reserved status code %u", priv->peer_close_code);
824
0
                protocol_error_and_close (self);
825
0
                return;
826
0
        default:
827
0
                break;
828
0
        }
829
830
0
  if (len > 2) {
831
0
    data += 2;
832
0
    len -= 2;
833
    
834
0
    if (!utf8_validate ((const char *)data, len)) {
835
0
      g_debug ("received non-UTF8 close data: %d '%.*s' %d", (int)len, (int)len, (char *)data, (int)data[0]);
836
0
      protocol_error_and_close (self);
837
0
      return;
838
0
    }
839
840
0
    priv->peer_close_data = g_strndup ((char *)data, len);
841
0
  }
842
843
  /* Once we receive close response on server, close immediately */
844
0
  if (priv->close_sent) {
845
0
    shutdown_wr_io_stream (self);
846
0
    if (priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER)
847
0
      close_io_stream (self);
848
0
  } else {
849
0
    close_connection (self, priv->peer_close_code, priv->peer_close_data);
850
0
  }
851
0
}
852
853
static void
854
receive_ping (SoupWebsocketConnection *self,
855
                      const guint8 *data,
856
                      gsize len)
857
0
{
858
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
859
860
0
        if (!priv->suppress_pongs_for_tests) {
861
                /* Send back a pong with same data */
862
0
                g_debug ("received ping, responding");
863
0
                send_message (self, SOUP_WEBSOCKET_QUEUE_URGENT, 0x0A, data, len);
864
0
        }
865
0
}
866
867
static void
868
receive_pong (SoupWebsocketConnection *self,
869
                      const guint8 *data,
870
                      gsize len)
871
0
{
872
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
873
0
  GByteArray *bytes;
874
875
0
  bytes = g_byte_array_sized_new (len + 1);
876
0
  g_byte_array_append (bytes, data, len);
877
  /* Always null terminate, as a convenience */
878
0
  g_byte_array_append (bytes, (guchar *)"\0", 1);
879
  /* But don't include the null terminator in the byte count */
880
0
  bytes->len--;
881
882
        /* g_str_has_prefix() and g_hash_table_remove() are safe to use since we
883
         * just made sure to null terminate bytes->data
884
         */
885
0
        if (priv->keepalive_pong_timeout > 0 && g_str_has_prefix ((const gchar *)bytes->data, KEEPALIVE_PAYLOAD_PREFIX)) {
886
0
                if (priv->outstanding_pongs && g_hash_table_remove (priv->outstanding_pongs, bytes->data))
887
0
                        g_debug ("received keepalive pong");
888
0
                else
889
0
                        g_debug ("received unknown keepalive pong");
890
0
        } else {
891
0
                g_debug ("received pong message");
892
0
        }
893
894
0
  g_signal_emit (self, signals[PONG], 0, bytes);
895
0
  g_byte_array_unref (bytes);
896
897
0
}
898
899
static void
900
process_contents (SoupWebsocketConnection *self,
901
      gboolean control,
902
      gboolean fin,
903
      guint8 opcode,
904
      GBytes *payload_data)
905
0
{
906
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
907
0
  GBytes *message;
908
0
  gconstpointer payload;
909
0
  gsize payload_len;
910
911
0
  payload = g_bytes_get_data (payload_data, &payload_len);
912
913
0
  if (priv->close_sent && priv->close_received)
914
0
    return;
915
916
0
  if (control) {
917
    /* Control frames must never be fragmented */
918
0
    if (!fin) {
919
0
      g_debug ("received fragmented control frame");
920
0
      protocol_error_and_close (self);
921
0
      return;
922
0
    }
923
924
0
    g_debug ("received control frame %d with %d payload", (int)opcode, (int)payload_len);
925
926
0
    switch (opcode) {
927
0
    case 0x08:
928
0
      receive_close (self, payload, payload_len);
929
0
      break;
930
0
    case 0x09:
931
0
      receive_ping (self, payload, payload_len);
932
0
      break;
933
0
    case 0x0A:
934
0
      receive_pong (self, payload, payload_len);
935
0
      break;
936
0
    default:
937
0
      g_debug ("received unsupported control frame: %d", (int)opcode);
938
0
      protocol_error_and_close (self);
939
0
      return;
940
0
    }
941
0
  } else if (priv->close_received) {
942
0
    g_debug ("received message after close was received");
943
0
        } else if (priv->close_sent && priv->dirty_close) {
944
0
                g_debug ("received message after close due to error was sent");
945
0
  } else {
946
    /* A message frame */
947
948
0
    if (!fin && opcode) {
949
      /* Initial fragment of a message */
950
0
      if (priv->message_data) {
951
0
        g_debug ("received out of order initial message fragment");
952
0
        protocol_error_and_close (self);
953
0
        return;
954
0
      }
955
0
      g_debug ("received initial fragment frame %d with %d payload", (int)opcode, (int)payload_len);
956
0
    } else if (!fin && !opcode) {
957
      /* Middle fragment of a message */
958
0
      if (!priv->message_data) {
959
0
        g_debug ("received out of order middle message fragment");
960
0
        protocol_error_and_close (self);
961
0
        return;
962
0
      }
963
0
      g_debug ("received middle fragment frame with %d payload", (int)payload_len);
964
0
    } else if (fin && !opcode) {
965
      /* Last fragment of a message */
966
0
      if (!priv->message_data) {
967
0
        g_debug ("received out of order ending message fragment");
968
0
        protocol_error_and_close (self);
969
0
        return;
970
0
      }
971
0
      g_debug ("received last fragment frame with %d payload", (int)payload_len);
972
0
    } else {
973
      /* An unfragmented message */
974
0
      g_assert (opcode != 0);
975
0
      if (priv->message_data) {
976
0
        g_debug ("received unfragmented message when fragment was expected");
977
0
        protocol_error_and_close (self);
978
0
        return;
979
0
      }
980
0
      g_debug ("received frame %d with %d payload", (int)opcode, (int)payload_len);
981
0
    }
982
983
0
    if (opcode) {
984
0
      priv->message_opcode = opcode;
985
0
      priv->message_data = g_byte_array_sized_new (payload_len + 1);
986
0
    }
987
988
0
    switch (priv->message_opcode) {
989
0
    case 0x01:
990
0
    case 0x02:
991
      /* Safety valve */
992
0
      if (priv->max_total_message_size > 0 &&
993
0
          (priv->message_data->len + payload_len) > priv->max_total_message_size) {
994
0
        too_big_message_error_and_close (self, (priv->message_data->len + payload_len));
995
0
        return;
996
0
      }
997
0
      g_byte_array_append (priv->message_data, payload, payload_len);
998
0
      break;
999
0
    default:
1000
0
      g_debug ("received unknown data frame: %d", (int)opcode);
1001
0
      protocol_error_and_close (self);
1002
0
      return;
1003
0
    }
1004
1005
    /* Actually deliver the message? */
1006
0
    if (fin) {
1007
0
      if (priv->message_opcode == 0x01 &&
1008
0
          !utf8_validate((const char *)priv->message_data->data,
1009
0
             priv->message_data->len)) {
1010
1011
0
        g_debug ("received invalid non-UTF8 text data");
1012
1013
        /* Discard the entire message */
1014
0
        g_clear_pointer (&priv->message_data, g_byte_array_unref);
1015
0
        priv->message_opcode = 0;
1016
1017
0
        bad_data_error_and_close (self);
1018
0
        return;
1019
0
      }
1020
1021
      /* Always null terminate, as a convenience */
1022
0
      g_byte_array_append (priv->message_data, (guchar *)"\0", 1);
1023
1024
      /* But don't include the null terminator in the byte count */
1025
0
      priv->message_data->len--;
1026
1027
0
      opcode = priv->message_opcode;
1028
0
      message = g_byte_array_free_to_bytes (priv->message_data);
1029
0
      priv->message_data = NULL;
1030
0
      priv->message_opcode = 0;
1031
0
      g_debug ("message: delivering %d with %d length",
1032
0
         (int)opcode, (int)g_bytes_get_size (message));
1033
0
      g_signal_emit (self, signals[MESSAGE], 0, (int)opcode, message);
1034
0
      g_bytes_unref (message);
1035
0
    }
1036
0
  }
1037
0
}
1038
1039
static gboolean
1040
process_frame (SoupWebsocketConnection *self)
1041
0
{
1042
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1043
0
  guint8 *header;
1044
0
  guint8 *payload;
1045
0
  guint64 payload_len;
1046
0
  guint8 *mask;
1047
0
  gboolean fin;
1048
0
  gboolean control;
1049
0
  gboolean masked;
1050
0
  guint8 opcode;
1051
0
  gsize len;
1052
0
  gsize at;
1053
0
  GBytes *filtered_bytes;
1054
0
  GList *l;
1055
0
  GError *error = NULL;
1056
1057
0
  len = priv->incoming->len;
1058
0
  if (len < 2)
1059
0
    return FALSE; /* need more data */
1060
1061
0
  header = priv->incoming->data;
1062
0
  fin = ((header[0] & 0x80) != 0);
1063
0
  control = header[0] & 0x08;
1064
0
  opcode = header[0] & 0x0f;
1065
0
  masked = ((header[1] & 0x80) != 0);
1066
1067
0
  if (priv->connection_type == SOUP_WEBSOCKET_CONNECTION_CLIENT && masked) {
1068
    /* A server MUST NOT mask any frames that it sends to the client.
1069
     * A client MUST close a connection if it detects a masked frame.
1070
     */
1071
0
    g_debug ("A server must not mask any frames that it sends to the client.");
1072
0
    protocol_error_and_close (self);
1073
0
    return FALSE;
1074
0
  }
1075
1076
0
  if (priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER && !masked) {
1077
    /* The server MUST close the connection upon receiving a frame
1078
     * that is not masked.
1079
     */
1080
0
    g_debug ("The client should always mask frames");
1081
0
    protocol_error_and_close (self);
1082
0
                return FALSE;
1083
0
        }
1084
1085
  /* RFC 6455 section 5.5 limits control frame payloads to 125 bytes. */
1086
0
  if (control && (header[1] & 0x7f) > 125) {
1087
0
    g_debug ("received oversized control frame");
1088
0
    protocol_error_and_close (self);
1089
0
    return FALSE;
1090
0
  }
1091
1092
0
  switch (header[1] & 0x7f) {
1093
0
  case 126:
1094
    /* If 126, the following 2 bytes interpreted as a 16-bit
1095
     * unsigned integer are the payload length.
1096
     */
1097
0
    at = 4;
1098
0
    if (len < at)
1099
0
      return FALSE; /* need more data */
1100
0
    payload_len = (((guint16)header[2] << 8) |
1101
0
             ((guint16)header[3] << 0));
1102
1103
    /* The minimal number of bytes MUST be used to encode the length. */
1104
0
    if (payload_len <= 125) {
1105
0
      protocol_error_and_close (self);
1106
0
      return FALSE;
1107
0
    }
1108
0
    break;
1109
0
  case 127:
1110
    /* If 127, the following 8 bytes interpreted as a 64-bit
1111
     * unsigned integer (the most significant bit MUST be 0)
1112
     * are the payload length.
1113
     */
1114
0
    at = 10;
1115
0
    if (len < at)
1116
0
      return FALSE; /* need more data */
1117
0
    payload_len = (((guint64)header[2] << 56) |
1118
0
             ((guint64)header[3] << 48) |
1119
0
             ((guint64)header[4] << 40) |
1120
0
             ((guint64)header[5] << 32) |
1121
0
             ((guint64)header[6] << 24) |
1122
0
             ((guint64)header[7] << 16) |
1123
0
             ((guint64)header[8] << 8) |
1124
0
             ((guint64)header[9] << 0));
1125
1126
    /* The minimal number of bytes MUST be used to encode the length. */
1127
0
    if (payload_len <= G_MAXUINT16) {
1128
0
      protocol_error_and_close (self);
1129
0
      return FALSE;
1130
0
    }
1131
0
    break;
1132
0
  default:
1133
0
    payload_len = header[1] & 0x7f;
1134
0
    at = 2;
1135
0
    break;
1136
0
  }
1137
1138
  /* Safety valve */
1139
0
  if (priv->max_incoming_payload_size > 0 &&
1140
0
      payload_len > priv->max_incoming_payload_size) {
1141
0
    too_big_incoming_payload_error_and_close (self, payload_len);
1142
0
    return FALSE;
1143
0
  }
1144
1145
0
  if (len < at + payload_len)
1146
0
    return FALSE; /* need more data */
1147
1148
0
  payload = header + at;
1149
1150
  /* at has a maximum value of 10 + 4 = 14 */
1151
0
  if (payload_len > G_MAXSIZE - 14) {
1152
0
    bad_data_error_and_close (self);
1153
0
    return FALSE;
1154
0
  }
1155
1156
0
  if (masked) {
1157
0
    mask = header + at;
1158
0
    payload += 4;
1159
0
    at += 4;
1160
1161
0
    if (len < at + payload_len)
1162
0
      return FALSE; /* need more data */
1163
1164
0
    xor_with_mask (mask, payload, payload_len);
1165
0
  }
1166
1167
0
  filtered_bytes = g_bytes_new_static (payload, payload_len);
1168
0
  for (l = priv->extensions; l != NULL; l = g_list_next (l)) {
1169
0
    SoupWebsocketExtension *extension;
1170
1171
0
    extension = (SoupWebsocketExtension *)l->data;
1172
0
    filtered_bytes = soup_websocket_extension_process_incoming_message (extension, priv->incoming->data, filtered_bytes, &error);
1173
0
    if (error) {
1174
0
      emit_error_and_close (self, error, FALSE);
1175
0
      return FALSE;
1176
0
    }
1177
0
  }
1178
1179
  /* After being processed by extensions reserved bits must be 0 */
1180
0
  if (header[0] & 0x70) {
1181
0
    protocol_error_and_close (self);
1182
0
    g_bytes_unref (filtered_bytes);
1183
1184
0
    return FALSE;
1185
0
  }
1186
1187
  /* Note that now that we've unmasked, we've modified the buffer, we can
1188
   * only return below via discarding or processing the message
1189
   */
1190
0
  process_contents (self, control, fin, opcode, filtered_bytes);
1191
0
  g_bytes_unref (filtered_bytes);
1192
1193
  /* Move past the parsed frame */
1194
0
  g_byte_array_remove_range (priv->incoming, 0, at + payload_len);
1195
1196
0
  return TRUE;
1197
0
}
1198
1199
static void
1200
process_incoming (SoupWebsocketConnection *self)
1201
0
{
1202
0
  while (process_frame (self))
1203
0
    ;
1204
0
}
1205
1206
static void
1207
soup_websocket_connection_read (SoupWebsocketConnection *self)
1208
0
{
1209
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1210
0
  GError *error = NULL;
1211
0
  gboolean end = FALSE;
1212
0
  gssize count;
1213
0
  gsize len;
1214
1215
0
  soup_websocket_connection_stop_input_source (self);
1216
1217
0
  do {
1218
0
    len = priv->incoming->len;
1219
0
    g_byte_array_set_size (priv->incoming, len + READ_BUFFER_SIZE);
1220
1221
0
    count = g_pollable_input_stream_read_nonblocking (priv->input,
1222
0
                  priv->incoming->data + len,
1223
0
                  READ_BUFFER_SIZE, NULL, &error);
1224
0
    if (count < 0) {
1225
0
      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
1226
0
        g_error_free (error);
1227
0
        count = 0;
1228
0
      } else {
1229
0
        emit_error_and_close (self, error, TRUE);
1230
0
        return;
1231
0
      }
1232
0
    } else if (count == 0) {
1233
0
      end = TRUE;
1234
0
    }
1235
1236
0
    priv->incoming->len = len + count;
1237
1238
0
    process_incoming (self);
1239
0
  } while (count > 0 && !priv->close_sent && !priv->io_closing);
1240
1241
0
  if (end) {
1242
0
    if (!priv->close_sent || !priv->close_received) {
1243
0
      priv->dirty_close = TRUE;
1244
0
      g_debug ("connection unexpectedly closed by peer");
1245
0
    } else {
1246
0
      g_debug ("peer has closed socket");
1247
0
    }
1248
1249
0
    close_io_stream (self);
1250
0
    return;
1251
0
  }
1252
1253
0
  if (!priv->io_closing)
1254
0
    soup_websocket_connection_start_input_source (self);
1255
0
}
1256
1257
static gboolean
1258
on_web_socket_input (GObject *pollable_stream,
1259
         gpointer user_data)
1260
0
{
1261
0
  soup_websocket_connection_read (SOUP_WEBSOCKET_CONNECTION (user_data));
1262
1263
0
  return G_SOURCE_REMOVE;
1264
0
}
1265
1266
static void
1267
soup_websocket_connection_write (SoupWebsocketConnection *self)
1268
0
{
1269
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1270
0
  const guint8 *data;
1271
0
  GError *error = NULL;
1272
0
  Frame *frame;
1273
0
  gssize count;
1274
0
  gsize len;
1275
1276
0
  soup_websocket_connection_stop_output_source (self);
1277
1278
0
  if (soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_CLOSED) {
1279
0
    g_debug ("Ignoring message since the connection is closed");
1280
0
    return;
1281
0
  }
1282
1283
0
  frame = g_queue_peek_head (&priv->outgoing);
1284
1285
  /* No more frames to send */
1286
0
  if (frame == NULL)
1287
0
    return;
1288
1289
0
  data = g_bytes_get_data (frame->data, &len);
1290
0
  g_assert (len > 0);
1291
0
  g_assert (len > frame->sent);
1292
1293
0
  count = g_pollable_output_stream_write_nonblocking (priv->output,
1294
0
                  data + frame->sent,
1295
0
                  len - frame->sent,
1296
0
                  NULL, &error);
1297
1298
0
  if (count < 0) {
1299
0
    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
1300
0
      g_clear_error (&error);
1301
0
      count = 0;
1302
1303
0
      g_debug ("failed to send frame because it would block, marking as pending");
1304
0
      frame->pending = TRUE;
1305
0
    } else {
1306
0
      emit_error_and_close (self, error, TRUE);
1307
0
      return;
1308
0
    }
1309
0
  }
1310
1311
0
  frame->sent += count;
1312
0
  if (frame->sent >= len) {
1313
0
    g_debug ("sent frame");
1314
0
    g_queue_pop_head (&priv->outgoing);
1315
1316
0
    if (frame->flags & SOUP_WEBSOCKET_QUEUE_LAST) {
1317
0
      if (priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER) {
1318
0
        close_io_stream (self);
1319
0
      } else {
1320
0
        shutdown_wr_io_stream (self);
1321
0
        close_io_after_timeout (self);
1322
0
      }
1323
0
    }
1324
0
    frame_free (frame);
1325
1326
0
    if (g_queue_is_empty (&priv->outgoing))
1327
0
      return;
1328
0
  }
1329
1330
0
  soup_websocket_connection_start_output_source (self);
1331
0
}
1332
1333
static gboolean
1334
on_web_socket_output (GObject *pollable_stream,
1335
          gpointer user_data)
1336
0
{
1337
0
  soup_websocket_connection_write (SOUP_WEBSOCKET_CONNECTION (user_data));
1338
1339
0
  return G_SOURCE_REMOVE;
1340
0
}
1341
1342
static void
1343
queue_frame (SoupWebsocketConnection *self,
1344
       SoupWebsocketQueueFlags flags,
1345
       gpointer data,
1346
       gsize len,
1347
       gsize amount)
1348
0
{
1349
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1350
0
  Frame *frame;
1351
1352
0
  g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
1353
0
  g_return_if_fail (priv->close_sent == FALSE);
1354
0
  g_return_if_fail (data != NULL);
1355
0
  g_return_if_fail (len > 0);
1356
1357
0
  frame = g_slice_new0 (Frame);
1358
0
  frame->data = g_bytes_new_take (data, len);
1359
0
  frame->amount = amount;
1360
0
  frame->flags = flags;
1361
1362
  /* If urgent put at front of queue */
1363
0
  if (flags & SOUP_WEBSOCKET_QUEUE_URGENT) {
1364
0
    GList *l;
1365
1366
    /* Find out the first frame that is not urgent or partially sent or pending */
1367
0
    for (l = g_queue_peek_head_link (&priv->outgoing); l != NULL; l = l->next) {
1368
0
      Frame *prev = l->data;
1369
1370
0
      if (!(prev->flags & SOUP_WEBSOCKET_QUEUE_URGENT) &&
1371
0
          prev->sent == 0 && !prev->pending)
1372
0
        break;
1373
0
    }
1374
1375
0
    g_queue_insert_before (&priv->outgoing, l, frame);
1376
0
  } else {
1377
0
    g_queue_push_tail (&priv->outgoing, frame);
1378
0
  }
1379
1380
0
  soup_websocket_connection_write (self);
1381
0
}
1382
1383
static void
1384
soup_websocket_connection_constructed (GObject *object)
1385
0
{
1386
0
  SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (object);
1387
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1388
0
  GInputStream *is;
1389
0
  GOutputStream *os;
1390
1391
0
  G_OBJECT_CLASS (soup_websocket_connection_parent_class)->constructed (object);
1392
1393
0
  g_return_if_fail (priv->io_stream != NULL);
1394
1395
0
  is = g_io_stream_get_input_stream (priv->io_stream);
1396
0
  g_return_if_fail (G_IS_POLLABLE_INPUT_STREAM (is));
1397
0
  priv->input = G_POLLABLE_INPUT_STREAM (is);
1398
0
  g_return_if_fail (g_pollable_input_stream_can_poll (priv->input));
1399
1400
0
  os = g_io_stream_get_output_stream (priv->io_stream);
1401
0
  g_return_if_fail (G_IS_POLLABLE_OUTPUT_STREAM (os));
1402
0
  priv->output = G_POLLABLE_OUTPUT_STREAM (os);
1403
0
  g_return_if_fail (g_pollable_output_stream_can_poll (priv->output));
1404
1405
0
  soup_websocket_connection_start_input_source (self);
1406
0
}
1407
1408
static void
1409
soup_websocket_connection_get_property (GObject *object,
1410
          guint prop_id,
1411
          GValue *value,
1412
          GParamSpec *pspec)
1413
0
{
1414
0
  SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (object);
1415
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1416
1417
0
  switch (prop_id) {
1418
0
  case PROP_IO_STREAM:
1419
0
    g_value_set_object (value, soup_websocket_connection_get_io_stream (self));
1420
0
    break;
1421
1422
0
  case PROP_CONNECTION_TYPE:
1423
0
    g_value_set_enum (value, soup_websocket_connection_get_connection_type (self));
1424
0
    break;
1425
1426
0
  case PROP_URI:
1427
0
    g_value_set_boxed (value, soup_websocket_connection_get_uri (self));
1428
0
    break;
1429
1430
0
  case PROP_ORIGIN:
1431
0
    g_value_set_string (value, soup_websocket_connection_get_origin (self));
1432
0
    break;
1433
1434
0
  case PROP_PROTOCOL:
1435
0
    g_value_set_string (value, soup_websocket_connection_get_protocol (self));
1436
0
    break;
1437
1438
0
  case PROP_STATE:
1439
0
    g_value_set_enum (value, soup_websocket_connection_get_state (self));
1440
0
    break;
1441
1442
0
  case PROP_MAX_INCOMING_PAYLOAD_SIZE:
1443
0
    g_value_set_uint64 (value, priv->max_incoming_payload_size);
1444
0
    break;
1445
1446
0
  case PROP_KEEPALIVE_INTERVAL:
1447
0
    g_value_set_uint (value, priv->keepalive_interval);
1448
0
    break;
1449
1450
0
  case PROP_KEEPALIVE_PONG_TIMEOUT:
1451
0
    g_value_set_uint (value, priv->keepalive_pong_timeout);
1452
0
    break;
1453
1454
0
  case PROP_EXTENSIONS:
1455
0
    g_value_set_pointer (value, priv->extensions);
1456
0
    break;
1457
1458
0
        case PROP_MAX_TOTAL_MESSAGE_SIZE:
1459
0
    g_value_set_uint64 (value, priv->max_total_message_size);
1460
0
    break;
1461
1462
0
  default:
1463
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1464
0
    break;
1465
0
  }
1466
0
}
1467
1468
static void
1469
soup_websocket_connection_set_property (GObject *object,
1470
          guint prop_id,
1471
          const GValue *value,
1472
          GParamSpec *pspec)
1473
0
{
1474
0
  SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (object);
1475
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1476
1477
0
  switch (prop_id) {
1478
0
  case PROP_IO_STREAM:
1479
0
    g_return_if_fail (priv->io_stream == NULL);
1480
0
    priv->io_stream = g_value_dup_object (value);
1481
0
    break;
1482
1483
0
  case PROP_CONNECTION_TYPE:
1484
0
    priv->connection_type = g_value_get_enum (value);
1485
0
    break;
1486
1487
0
  case PROP_URI:
1488
0
    g_return_if_fail (priv->uri == NULL);
1489
0
    priv->uri = soup_uri_copy_with_normalized_flags (g_value_get_boxed (value));
1490
0
    break;
1491
1492
0
  case PROP_ORIGIN:
1493
0
    g_return_if_fail (priv->origin == NULL);
1494
0
    priv->origin = g_value_dup_string (value);
1495
0
    break;
1496
1497
0
  case PROP_PROTOCOL:
1498
0
    g_return_if_fail (priv->protocol == NULL);
1499
0
    priv->protocol = g_value_dup_string (value);
1500
0
    break;
1501
1502
0
  case PROP_MAX_INCOMING_PAYLOAD_SIZE:
1503
0
    priv->max_incoming_payload_size = g_value_get_uint64 (value);
1504
0
    break;
1505
1506
0
  case PROP_KEEPALIVE_INTERVAL:
1507
0
    soup_websocket_connection_set_keepalive_interval (self,
1508
0
                                                      g_value_get_uint (value));
1509
0
    break;
1510
1511
0
  case PROP_KEEPALIVE_PONG_TIMEOUT:
1512
0
    soup_websocket_connection_set_keepalive_pong_timeout (self,
1513
0
                      g_value_get_uint (value));
1514
0
    break;
1515
1516
0
  case PROP_EXTENSIONS:
1517
0
    priv->extensions = g_value_get_pointer (value);
1518
0
    break;
1519
1520
0
  case PROP_MAX_TOTAL_MESSAGE_SIZE:
1521
0
    priv->max_total_message_size = g_value_get_uint64 (value);
1522
0
    break;
1523
0
  case PROP_STATE:
1524
0
    g_assert_not_reached ();
1525
0
    break;
1526
1527
0
  default:
1528
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1529
0
    break;
1530
0
  }
1531
0
}
1532
1533
static void
1534
soup_websocket_connection_dispose (GObject *object)
1535
0
{
1536
0
  SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (object);
1537
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1538
1539
0
  keepalive_stop_outstanding_pongs (self);
1540
1541
0
  priv->dirty_close = TRUE;
1542
0
  close_io_stream (self);
1543
1544
0
  G_OBJECT_CLASS (soup_websocket_connection_parent_class)->dispose (object);
1545
0
}
1546
1547
static void
1548
soup_websocket_connection_finalize (GObject *object)
1549
0
{
1550
0
  SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (object);
1551
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1552
1553
0
  g_free (priv->peer_close_data);
1554
1555
0
  if (priv->incoming)
1556
0
    g_byte_array_free (priv->incoming, TRUE);
1557
0
  while (!g_queue_is_empty (&priv->outgoing))
1558
0
    frame_free (g_queue_pop_head (&priv->outgoing));
1559
1560
0
  g_clear_object (&priv->io_stream);
1561
0
  g_assert (!priv->input_source);
1562
0
  g_assert (!priv->output_source);
1563
0
  g_assert (priv->io_closing);
1564
0
  g_assert (priv->io_closed);
1565
0
  g_assert (!priv->close_timeout);
1566
0
  g_assert (!priv->keepalive_timeout);
1567
1568
0
  if (priv->message_data)
1569
0
    g_byte_array_free (priv->message_data, TRUE);
1570
1571
0
  if (priv->uri)
1572
0
    g_uri_unref (priv->uri);
1573
0
  g_free (priv->origin);
1574
0
  g_free (priv->protocol);
1575
1576
0
  g_list_free_full (priv->extensions, g_object_unref);
1577
1578
0
  G_OBJECT_CLASS (soup_websocket_connection_parent_class)->finalize (object);
1579
0
}
1580
1581
static void
1582
soup_websocket_connection_class_init (SoupWebsocketConnectionClass *klass)
1583
0
{
1584
0
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1585
1586
0
  gobject_class->constructed = soup_websocket_connection_constructed;
1587
0
  gobject_class->get_property = soup_websocket_connection_get_property;
1588
0
  gobject_class->set_property = soup_websocket_connection_set_property;
1589
0
  gobject_class->dispose = soup_websocket_connection_dispose;
1590
0
  gobject_class->finalize = soup_websocket_connection_finalize;
1591
1592
  /**
1593
   * SoupWebsocketConnection:io-stream:
1594
   *
1595
   * The underlying IO stream the WebSocket is communicating
1596
   * over.
1597
   *
1598
   * The input and output streams must be pollable streams.
1599
   */
1600
0
        properties[PROP_IO_STREAM] =
1601
0
                g_param_spec_object ("io-stream",
1602
0
                                     "I/O Stream",
1603
0
                                     "Underlying I/O stream",
1604
0
                                     G_TYPE_IO_STREAM,
1605
0
                                     G_PARAM_READWRITE |
1606
0
                                     G_PARAM_CONSTRUCT_ONLY |
1607
0
                                     G_PARAM_STATIC_STRINGS);
1608
1609
  /**
1610
   * SoupWebsocketConnection:connection-type:
1611
   *
1612
   * The type of connection (client/server).
1613
   */
1614
0
        properties[PROP_CONNECTION_TYPE] =
1615
0
                g_param_spec_enum ("connection-type",
1616
0
                                   "Connection type",
1617
0
                                   "Connection type (client/server)",
1618
0
                                   SOUP_TYPE_WEBSOCKET_CONNECTION_TYPE,
1619
0
                                   SOUP_WEBSOCKET_CONNECTION_UNKNOWN,
1620
0
                                   G_PARAM_READWRITE |
1621
0
                                   G_PARAM_CONSTRUCT_ONLY |
1622
0
                                   G_PARAM_STATIC_STRINGS);
1623
1624
  /**
1625
   * SoupWebsocketConnection:uri:
1626
   *
1627
   * The URI of the WebSocket.
1628
   *
1629
   * For servers this represents the address of the WebSocket,
1630
   * and for clients it is the address connected to.
1631
   */
1632
0
        properties[PROP_URI] =
1633
0
                g_param_spec_boxed ("uri",
1634
0
                                    "URI",
1635
0
                                    "The WebSocket URI",
1636
0
                                    G_TYPE_URI,
1637
0
                                    G_PARAM_READWRITE |
1638
0
                                    G_PARAM_CONSTRUCT_ONLY |
1639
0
                                    G_PARAM_STATIC_STRINGS);
1640
1641
  /**
1642
   * SoupWebsocketConnection:origin:
1643
   *
1644
   * The client's Origin.
1645
   */
1646
0
        properties[PROP_ORIGIN] =
1647
0
                g_param_spec_string ("origin",
1648
0
                                     "Origin",
1649
0
                                     "The WebSocket origin",
1650
0
                                     NULL,
1651
0
                                     G_PARAM_READWRITE |
1652
0
                                     G_PARAM_CONSTRUCT_ONLY |
1653
0
                                     G_PARAM_STATIC_STRINGS);
1654
1655
  /**
1656
   * SoupWebsocketConnection:protocol:
1657
   *
1658
   * The chosen protocol, or %NULL if a protocol was not agreed
1659
   * upon.
1660
   */
1661
0
        properties[PROP_PROTOCOL] =
1662
0
                g_param_spec_string ("protocol",
1663
0
                                     "Protocol",
1664
0
                                     "The chosen WebSocket protocol",
1665
0
                                     NULL,
1666
0
                                     G_PARAM_READWRITE |
1667
0
                                     G_PARAM_CONSTRUCT_ONLY |
1668
0
                                     G_PARAM_STATIC_STRINGS);
1669
1670
  /**
1671
   * SoupWebsocketConnection:state:
1672
   *
1673
   * The current state of the WebSocket.
1674
   */
1675
0
        properties[PROP_STATE] =
1676
0
                g_param_spec_enum ("state",
1677
0
                                   "State",
1678
0
                                   "State ",
1679
0
                                   SOUP_TYPE_WEBSOCKET_STATE,
1680
0
                                   SOUP_WEBSOCKET_STATE_OPEN,
1681
0
                                   G_PARAM_READABLE |
1682
0
                                   G_PARAM_STATIC_STRINGS);
1683
1684
  /**
1685
   * SoupWebsocketConnection:max-incoming-payload-size:
1686
   *
1687
   * The maximum payload size for incoming packets, or 0 to not limit it.
1688
   *
1689
   * Each message may consist of multiple packets, so also refer to
1690
   * [property@WebsocketConnection:max-total-message-size].
1691
   */
1692
0
        properties[PROP_MAX_INCOMING_PAYLOAD_SIZE] =
1693
0
                g_param_spec_uint64 ("max-incoming-payload-size",
1694
0
                                     "Max incoming payload size",
1695
0
                                     "Max incoming payload size ",
1696
0
                                     0,
1697
0
                                     G_MAXUINT64,
1698
0
                                     MAX_INCOMING_PAYLOAD_SIZE_DEFAULT,
1699
0
                                     G_PARAM_READWRITE |
1700
0
                                     G_PARAM_CONSTRUCT |
1701
0
                                     G_PARAM_STATIC_STRINGS);
1702
1703
  /**
1704
   * SoupWebsocketConnection:keepalive-interval:
1705
   *
1706
   * Interval in seconds on when to send a ping message which will
1707
   * serve as a keepalive message.
1708
   *
1709
   * If set to 0 the keepalive message is disabled.
1710
   */
1711
0
        properties[PROP_KEEPALIVE_INTERVAL] =
1712
0
                g_param_spec_uint ("keepalive-interval",
1713
0
                                   "Keepalive interval",
1714
0
                                   "Keepalive interval",
1715
0
                                   0,
1716
0
                                   G_MAXUINT,
1717
0
                                   0,
1718
0
                                   G_PARAM_READWRITE |
1719
0
                                   G_PARAM_CONSTRUCT |
1720
0
                                   G_PARAM_STATIC_STRINGS);
1721
1722
        /**
1723
         * SoupWebsocketConnection:keepalive-pong-timeout:
1724
         *
1725
         * Timeout in seconds for when the absence of a pong from a keepalive
1726
         * ping is assumed to be caused by a faulty connection. The WebSocket
1727
         * will be transitioned to a closed state when this happens.
1728
         *
1729
         * If set to 0 then the absence of pongs from keepalive pings is
1730
         * ignored.
1731
         *
1732
         * Since: 3.6
1733
         */
1734
0
        properties[PROP_KEEPALIVE_PONG_TIMEOUT] =
1735
0
                g_param_spec_uint ("keepalive-pong-timeout",
1736
0
                                   "Keepalive pong timeout",
1737
0
                                   "Keepalive pong timeout",
1738
0
                                   0,
1739
0
                                   G_MAXUINT,
1740
0
                                   0,
1741
0
                                   G_PARAM_READWRITE |
1742
0
                                   G_PARAM_CONSTRUCT |
1743
0
                                   G_PARAM_STATIC_STRINGS);
1744
1745
        /**
1746
         * SoupWebsocketConnection:extensions:
1747
         *
1748
         * List of [class@WebsocketExtension] objects that are active in the connection.
1749
         */
1750
0
        properties[PROP_EXTENSIONS] =
1751
0
                g_param_spec_pointer ("extensions",
1752
0
                                      "Active extensions",
1753
0
                                      "The list of active extensions",
1754
0
                                      G_PARAM_READWRITE |
1755
0
                                      G_PARAM_CONSTRUCT_ONLY |
1756
0
                                      G_PARAM_STATIC_STRINGS);
1757
1758
  /**
1759
   * SoupWebsocketConnection:max-total-message-size:
1760
   *
1761
   * The maximum size for incoming messages.
1762
   *
1763
   * Set to a value to limit the total message size, or 0 to not
1764
   * limit it.
1765
   *
1766
   * [method@Server.add_websocket_handler] will set this to a nonzero
1767
   * default value to mitigate denial of service attacks. Clients must
1768
   * choose their own default if they need to mitigate denial of service
1769
   * attacks. You also need to set your own default if creating your own
1770
   * server SoupWebsocketConnection without using SoupServer.
1771
   *
1772
   * Each message may consist of multiple packets, so also refer to
1773
   * [property@WebsocketConnection:max-incoming-payload-size].
1774
   *
1775
   * Since: 3.8
1776
   */
1777
0
        properties[PROP_MAX_TOTAL_MESSAGE_SIZE] =
1778
0
                g_param_spec_uint64 ("max-total-message-size",
1779
0
                                     "Max total message size",
1780
0
                                     "Max total message size ",
1781
0
                                     0,
1782
0
                                     G_MAXUINT64,
1783
0
                                     0,
1784
0
                                     G_PARAM_READWRITE |
1785
0
                                     G_PARAM_CONSTRUCT |
1786
0
                                     G_PARAM_STATIC_STRINGS);
1787
1788
0
        g_object_class_install_properties (gobject_class, LAST_PROPERTY, properties);
1789
1790
  /**
1791
   * SoupWebsocketConnection::message:
1792
   * @self: the WebSocket
1793
   * @type: the type of message contents
1794
   * @message: the message data
1795
   *
1796
   * Emitted when we receive a message from the peer.
1797
   *
1798
   * As a convenience, the @message data will always be
1799
   * %NULL-terminated, but the NUL byte will not be included in
1800
   * the length count.
1801
   */
1802
0
  signals[MESSAGE] = g_signal_new ("message",
1803
0
           SOUP_TYPE_WEBSOCKET_CONNECTION,
1804
0
           G_SIGNAL_RUN_FIRST,
1805
0
           0,
1806
0
           NULL, NULL, g_cclosure_marshal_generic,
1807
0
           G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_BYTES);
1808
1809
  /**
1810
   * SoupWebsocketConnection::error:
1811
   * @self: the WebSocket
1812
   * @error: the error that occured
1813
   *
1814
   * Emitted when an error occurred on the WebSocket.
1815
   *
1816
   * This may be fired multiple times. Fatal errors will be followed by
1817
   * the [signal@WebsocketConnection::closed] signal being emitted.
1818
   */
1819
0
  signals[ERROR] = g_signal_new ("error",
1820
0
               SOUP_TYPE_WEBSOCKET_CONNECTION,
1821
0
               G_SIGNAL_RUN_FIRST,
1822
0
               0,
1823
0
               NULL, NULL, g_cclosure_marshal_generic,
1824
0
               G_TYPE_NONE, 1, G_TYPE_ERROR);
1825
1826
  /**
1827
   * SoupWebsocketConnection::closing:
1828
   * @self: the WebSocket
1829
   *
1830
   * This signal will be emitted during an orderly close.
1831
   */
1832
0
  signals[CLOSING] = g_signal_new ("closing",
1833
0
           SOUP_TYPE_WEBSOCKET_CONNECTION,
1834
0
           G_SIGNAL_RUN_LAST,
1835
0
           0,
1836
0
           NULL, NULL, g_cclosure_marshal_generic,
1837
0
           G_TYPE_NONE, 0);
1838
1839
  /**
1840
   * SoupWebsocketConnection::closed:
1841
   * @self: the WebSocket
1842
   *
1843
   * Emitted when the connection has completely closed.
1844
   *
1845
   * This happens either due to an orderly close from the peer, one
1846
   * initiated via [method@WebsocketConnection.close] or a fatal error
1847
   * condition that caused a close.
1848
   *
1849
   * This signal will be emitted once.
1850
   */
1851
0
  signals[CLOSED] = g_signal_new ("closed",
1852
0
          SOUP_TYPE_WEBSOCKET_CONNECTION,
1853
0
          G_SIGNAL_RUN_FIRST,
1854
0
          0,
1855
0
          NULL, NULL, g_cclosure_marshal_generic,
1856
0
          G_TYPE_NONE, 0);
1857
1858
  /**
1859
   * SoupWebsocketConnection::pong:
1860
   * @self: the WebSocket
1861
   * @message: the application data (if any)
1862
   *
1863
   * Emitted when we receive a Pong frame (solicited or
1864
   * unsolicited) from the peer.
1865
   *
1866
   * As a convenience, the @message data will always be
1867
   * %NULL-terminated, but the NUL byte will not be included in
1868
   * the length count.
1869
   */
1870
0
  signals[PONG] = g_signal_new ("pong",
1871
0
              SOUP_TYPE_WEBSOCKET_CONNECTION,
1872
0
              G_SIGNAL_RUN_FIRST,
1873
0
              0,
1874
0
              NULL, NULL, g_cclosure_marshal_generic,
1875
0
              G_TYPE_NONE, 1, G_TYPE_BYTES);
1876
0
}
1877
1878
/**
1879
 * soup_websocket_connection_new:
1880
 * @stream: a #GIOStream connected to the WebSocket server
1881
 * @uri: the URI of the connection
1882
 * @type: the type of connection (client/side)
1883
 * @origin: (nullable): the Origin of the client
1884
 * @protocol: (nullable): the subprotocol in use
1885
 * @extensions: (element-type SoupWebsocketExtension) (transfer full): a #GList of #SoupWebsocketExtension objects
1886
 *
1887
 * Creates a [class@WebsocketConnection] on @stream with the given active @extensions.
1888
 *
1889
 * This should be called after completing the handshake to begin using the WebSocket
1890
 * protocol.
1891
 *
1892
 * Returns: a new #SoupWebsocketConnection
1893
 */
1894
SoupWebsocketConnection *
1895
soup_websocket_connection_new (GIOStream                    *stream,
1896
             GUri                         *uri,
1897
             SoupWebsocketConnectionType   type,
1898
             const char                   *origin,
1899
             const char                   *protocol,
1900
             GList                        *extensions)
1901
0
{
1902
0
        g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL);
1903
0
        g_return_val_if_fail (uri != NULL, NULL);
1904
0
        g_return_val_if_fail (type != SOUP_WEBSOCKET_CONNECTION_UNKNOWN, NULL);
1905
1906
0
        return g_object_new (SOUP_TYPE_WEBSOCKET_CONNECTION,
1907
0
                             "io-stream", stream,
1908
0
                             "uri", uri,
1909
0
                             "connection-type", type,
1910
0
                             "origin", origin,
1911
0
                             "protocol", protocol,
1912
0
                             "extensions", extensions,
1913
0
                             NULL);
1914
0
}
1915
1916
/**
1917
 * soup_websocket_connection_get_io_stream:
1918
 * @self: the WebSocket
1919
 *
1920
 * Get the I/O stream the WebSocket is communicating over.
1921
 *
1922
 * Returns: (transfer none): the WebSocket's I/O stream.
1923
 */
1924
GIOStream *
1925
soup_websocket_connection_get_io_stream (SoupWebsocketConnection *self)
1926
0
{
1927
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1928
1929
0
  g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
1930
1931
0
  return priv->io_stream;
1932
0
}
1933
1934
/**
1935
 * soup_websocket_connection_get_connection_type:
1936
 * @self: the WebSocket
1937
 *
1938
 * Get the connection type (client/server) of the connection.
1939
 *
1940
 * Returns: the connection type
1941
 */
1942
SoupWebsocketConnectionType
1943
soup_websocket_connection_get_connection_type (SoupWebsocketConnection *self)
1944
0
{
1945
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1946
1947
0
  g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), SOUP_WEBSOCKET_CONNECTION_UNKNOWN);
1948
1949
0
  return priv->connection_type;
1950
0
}
1951
1952
/**
1953
 * soup_websocket_connection_get_uri:
1954
 * @self: the WebSocket
1955
 *
1956
 * Get the URI of the WebSocket.
1957
 *
1958
 * For servers this represents the address of the WebSocket, and
1959
 * for clients it is the address connected to.
1960
 *
1961
 * Returns: (transfer none): the URI
1962
 */
1963
GUri *
1964
soup_websocket_connection_get_uri (SoupWebsocketConnection *self)
1965
0
{
1966
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1967
1968
0
  g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
1969
1970
0
  return priv->uri;
1971
0
}
1972
1973
/**
1974
 * soup_websocket_connection_get_origin:
1975
 * @self: the WebSocket
1976
 *
1977
 * Get the origin of the WebSocket.
1978
 *
1979
 * Returns: (nullable): the origin
1980
 */
1981
const char *
1982
soup_websocket_connection_get_origin (SoupWebsocketConnection *self)
1983
0
{
1984
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
1985
1986
0
  g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
1987
1988
0
  return priv->origin;
1989
0
}
1990
1991
/**
1992
 * soup_websocket_connection_get_protocol:
1993
 * @self: the WebSocket
1994
 *
1995
 * Get the protocol chosen via negotiation with the peer.
1996
 *
1997
 * Returns: (nullable): the chosen protocol
1998
 */
1999
const char *
2000
soup_websocket_connection_get_protocol (SoupWebsocketConnection *self)
2001
0
{
2002
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2003
2004
0
  g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
2005
2006
0
  return priv->protocol;
2007
0
}
2008
2009
/**
2010
 * soup_websocket_connection_get_extensions:
2011
 * @self: the WebSocket
2012
 *
2013
 * Get the extensions chosen via negotiation with the peer.
2014
 *
2015
 * Returns: (element-type SoupWebsocketExtension) (transfer none): a #GList of #SoupWebsocketExtension objects
2016
 */
2017
GList *
2018
soup_websocket_connection_get_extensions (SoupWebsocketConnection *self)
2019
0
{
2020
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2021
2022
0
        g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
2023
2024
0
        return priv->extensions;
2025
0
}
2026
2027
/**
2028
 * soup_websocket_connection_get_state:
2029
 * @self: the WebSocket
2030
 *
2031
 * Get the current state of the WebSocket.
2032
 *
2033
 * Returns: the state
2034
 */
2035
SoupWebsocketState
2036
soup_websocket_connection_get_state (SoupWebsocketConnection *self)
2037
0
{
2038
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2039
2040
0
  g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), 0);
2041
2042
0
  if (priv->io_closed)
2043
0
    return SOUP_WEBSOCKET_STATE_CLOSED;
2044
0
  else if (priv->io_closing || priv->close_sent)
2045
0
    return SOUP_WEBSOCKET_STATE_CLOSING;
2046
0
  else
2047
0
    return SOUP_WEBSOCKET_STATE_OPEN;
2048
0
}
2049
2050
/**
2051
 * soup_websocket_connection_get_close_code:
2052
 * @self: the WebSocket
2053
 *
2054
 * Get the close code received from the WebSocket peer.
2055
 *
2056
 * This only becomes valid once the WebSocket is in the
2057
 * %SOUP_WEBSOCKET_STATE_CLOSED state. The value will often be in the
2058
 * [enum@WebsocketCloseCode] enumeration, but may also be an application
2059
 * defined close code.
2060
 *
2061
 * Returns: the close code or zero.
2062
 */
2063
gushort
2064
soup_websocket_connection_get_close_code (SoupWebsocketConnection *self)
2065
0
{
2066
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2067
2068
0
  g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), 0);
2069
2070
0
  return priv->peer_close_code;
2071
0
}
2072
2073
/**
2074
 * soup_websocket_connection_get_close_data:
2075
 * @self: the WebSocket
2076
 *
2077
 * Get the close data received from the WebSocket peer.
2078
 *
2079
 * This only becomes valid once the WebSocket is in the
2080
 * %SOUP_WEBSOCKET_STATE_CLOSED state. The data may be freed once
2081
 * the main loop is run, so copy it if you need to keep it around.
2082
 *
2083
 * Returns: the close data or %NULL
2084
 */
2085
const char *
2086
soup_websocket_connection_get_close_data (SoupWebsocketConnection *self)
2087
0
{
2088
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2089
2090
0
  g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), NULL);
2091
2092
0
  return priv->peer_close_data;
2093
0
}
2094
2095
/**
2096
 * soup_websocket_connection_send_text:
2097
 * @self: the WebSocket
2098
 * @text: the message contents
2099
 *
2100
 * Send a %NULL-terminated text (UTF-8) message to the peer.
2101
 *
2102
 * If you need to send text messages containing %NULL characters use
2103
 * [method@WebsocketConnection.send_message] instead.
2104
 *
2105
 * The message is queued to be sent and will be sent when the main loop
2106
 * is run.
2107
 */
2108
void
2109
soup_websocket_connection_send_text (SoupWebsocketConnection *self,
2110
             const char *text)
2111
0
{
2112
0
  gsize length;
2113
2114
0
  g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
2115
0
  g_return_if_fail (soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_OPEN);
2116
0
  g_return_if_fail (text != NULL);
2117
2118
0
  length = strlen (text);
2119
0
        g_return_if_fail (utf8_validate (text, length));
2120
2121
0
  send_message (self, SOUP_WEBSOCKET_QUEUE_NORMAL, 0x01, (const guint8 *) text, length);
2122
0
}
2123
2124
/**
2125
 * soup_websocket_connection_send_binary:
2126
 * @self: the WebSocket
2127
 * @data: (array length=length) (element-type guint8) (nullable): the message contents
2128
 * @length: the length of @data
2129
 *
2130
 * Send a binary message to the peer.
2131
 *
2132
 * If @length is 0, @data may be %NULL.
2133
 *
2134
 * The message is queued to be sent and will be sent when the main loop
2135
 * is run.
2136
 */
2137
void
2138
soup_websocket_connection_send_binary (SoupWebsocketConnection *self,
2139
               gconstpointer data,
2140
               gsize length)
2141
0
{
2142
0
  g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
2143
0
  g_return_if_fail (soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_OPEN);
2144
0
  g_return_if_fail (data != NULL || length == 0);
2145
2146
0
  send_message (self, SOUP_WEBSOCKET_QUEUE_NORMAL, 0x02, data, length);
2147
0
}
2148
2149
/**
2150
 * soup_websocket_connection_send_message:
2151
 * @self: the WebSocket
2152
 * @type: the type of message contents
2153
 * @message: the message data as #GBytes
2154
 *
2155
 * Send a message of the given @type to the peer. Note that this method,
2156
 * allows to send text messages containing %NULL characters.
2157
 *
2158
 * The message is queued to be sent and will be sent when the main loop
2159
 * is run.
2160
 */
2161
void
2162
soup_websocket_connection_send_message (SoupWebsocketConnection *self,
2163
                                        SoupWebsocketDataType type,
2164
                                        GBytes *message)
2165
0
{
2166
0
        gconstpointer data;
2167
0
        gsize length;
2168
2169
0
        g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
2170
0
        g_return_if_fail (soup_websocket_connection_get_state (self) == SOUP_WEBSOCKET_STATE_OPEN);
2171
0
        g_return_if_fail (message != NULL);
2172
2173
0
        data = g_bytes_get_data (message, &length);
2174
0
        g_return_if_fail (type != SOUP_WEBSOCKET_DATA_TEXT || utf8_validate ((const char *)data, length));
2175
2176
0
        send_message (self, SOUP_WEBSOCKET_QUEUE_NORMAL, (int)type, data, length);
2177
0
}
2178
2179
/**
2180
 * soup_websocket_connection_close:
2181
 * @self: the WebSocket
2182
 * @code: close code
2183
 * @data: (nullable): close data
2184
 *
2185
 * Close the connection in an orderly fashion.
2186
 *
2187
 * Note that until the [signal@WebsocketConnection::closed] signal fires, the connection
2188
 * is not yet completely closed. The close message is not even sent until the
2189
 * main loop runs.
2190
 *
2191
 * The @code and @data are sent to the peer along with the close request.
2192
 * If @code is %SOUP_WEBSOCKET_CLOSE_NO_STATUS a close message with no body
2193
 * (without code and data) is sent.
2194
 * Note that the @data must be UTF-8 valid.
2195
 */
2196
void
2197
soup_websocket_connection_close (SoupWebsocketConnection *self,
2198
         gushort code,
2199
         const char *data)
2200
0
{
2201
2202
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2203
2204
0
  g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
2205
0
  g_return_if_fail (!priv->close_sent);
2206
2207
0
  g_return_if_fail (code != SOUP_WEBSOCKET_CLOSE_ABNORMAL &&
2208
0
        code != SOUP_WEBSOCKET_CLOSE_TLS_HANDSHAKE);
2209
0
  if (priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER)
2210
0
    g_return_if_fail (code != SOUP_WEBSOCKET_CLOSE_NO_EXTENSION);
2211
0
  else
2212
0
    g_return_if_fail (code != SOUP_WEBSOCKET_CLOSE_SERVER_ERROR);
2213
2214
0
  close_connection (self, code, data);
2215
0
}
2216
2217
/**
2218
 * soup_websocket_connection_get_max_incoming_payload_size:
2219
 * @self: the WebSocket
2220
 *
2221
 * Gets the maximum payload size allowed for incoming packets.
2222
 *
2223
 * Returns: the maximum payload size.
2224
 */
2225
guint64
2226
soup_websocket_connection_get_max_incoming_payload_size (SoupWebsocketConnection *self)
2227
0
{
2228
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2229
2230
0
  g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), MAX_INCOMING_PAYLOAD_SIZE_DEFAULT);
2231
2232
0
  return priv->max_incoming_payload_size;
2233
0
}
2234
2235
/**
2236
 * soup_websocket_connection_set_max_incoming_payload_size:
2237
 * @self: the WebSocket
2238
 * @max_incoming_payload_size: the maximum payload size
2239
 *
2240
 * Sets the maximum payload size allowed for incoming packets.
2241
 *
2242
 * It does not limit the outgoing packet size.
2243
 */
2244
void
2245
soup_websocket_connection_set_max_incoming_payload_size (SoupWebsocketConnection *self,
2246
                                                         guint64                  max_incoming_payload_size)
2247
0
{
2248
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2249
2250
0
  g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
2251
2252
0
  if (priv->max_incoming_payload_size != max_incoming_payload_size) {
2253
0
    priv->max_incoming_payload_size = max_incoming_payload_size;
2254
0
    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_INCOMING_PAYLOAD_SIZE]);
2255
0
  }
2256
0
}
2257
2258
/**
2259
 * soup_websocket_connection_get_max_total_message_size:
2260
 * @self: the WebSocket
2261
 *
2262
 * Gets the maximum total message size allowed for packets.
2263
 *
2264
 * Returns: the maximum total message size.
2265
 *
2266
 * Since: 3.8
2267
 */
2268
guint64
2269
soup_websocket_connection_get_max_total_message_size (SoupWebsocketConnection *self)
2270
0
{
2271
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2272
2273
0
  g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), 0);
2274
2275
0
  return priv->max_total_message_size;
2276
0
}
2277
2278
/**
2279
 * soup_websocket_connection_set_max_total_message_size:
2280
 * @self: the WebSocket
2281
 * @max_total_message_size: the maximum total message size
2282
 *
2283
 * Sets the maximum total message size allowed for packets.
2284
 *
2285
 * It does not limit the outgoing packet size.
2286
 *
2287
 * Since: 3.8
2288
 */
2289
void
2290
soup_websocket_connection_set_max_total_message_size (SoupWebsocketConnection *self,
2291
                                                      guint64                  max_total_message_size)
2292
0
{
2293
0
  SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2294
2295
0
  g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
2296
2297
0
  if (priv->max_total_message_size != max_total_message_size) {
2298
0
    priv->max_total_message_size = max_total_message_size;
2299
0
    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_TOTAL_MESSAGE_SIZE]);
2300
0
  }
2301
0
}
2302
2303
/**
2304
 * soup_websocket_connection_get_keepalive_interval:
2305
 * @self: the WebSocket
2306
 *
2307
 * Gets the keepalive interval in seconds or 0 if disabled.
2308
 *
2309
 * Returns: the keepalive interval.
2310
 */
2311
guint
2312
soup_websocket_connection_get_keepalive_interval (SoupWebsocketConnection *self)
2313
0
{
2314
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2315
2316
0
  g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), 0);
2317
2318
0
  return priv->keepalive_interval;
2319
0
}
2320
2321
static gboolean
2322
on_pong_timeout (gpointer user_data)
2323
0
{
2324
0
        SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (user_data);
2325
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2326
2327
0
        g_debug ("expected pong never arrived; connection probably lost");
2328
2329
0
        GError *error = g_error_new (SOUP_WEBSOCKET_ERROR,
2330
0
                                     SOUP_WEBSOCKET_CLOSE_POLICY_VIOLATION,
2331
0
                                     "Did not receive keepalive pong within %d seconds",
2332
0
                                     priv->keepalive_pong_timeout);
2333
0
        emit_error_and_close (self, g_steal_pointer (&error), FALSE /* to ignore error if already closing */);
2334
2335
0
        return G_SOURCE_REMOVE;
2336
0
}
2337
2338
static GSource *
2339
new_pong_timeout_source (SoupWebsocketConnection *self, int pong_timeout)
2340
0
{
2341
0
        GSource *source = g_timeout_source_new_seconds (pong_timeout);
2342
0
        g_source_set_static_name (source, "SoupWebsocketConnection pong timeout");
2343
0
        g_source_set_callback (source, on_pong_timeout, self, NULL);
2344
0
        g_source_attach (source, g_main_context_get_thread_default ());
2345
0
        return source;
2346
0
}
2347
2348
static void
2349
destroy_and_unref (gpointer data)
2350
0
{
2351
0
        GSource *source = data;
2352
2353
0
        g_source_destroy (source);
2354
0
        g_source_unref (source);
2355
0
}
2356
2357
/**
2358
 * register_outstanding_pong:
2359
 * @ping_payload: (transfer full): The payload. Note the transfer of ownership.
2360
 */
2361
static void
2362
register_outstanding_pong (SoupWebsocketConnection *self, char *ping_payload, guint pong_timeout)
2363
0
{
2364
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2365
2366
0
        if (!priv->outstanding_pongs) {
2367
0
                priv->outstanding_pongs = g_hash_table_new_full (g_str_hash, g_str_equal,
2368
0
                                                                 g_free, destroy_and_unref);
2369
0
        }
2370
2371
0
        g_hash_table_insert (priv->outstanding_pongs,
2372
0
                             ping_payload,
2373
0
                             new_pong_timeout_source (self, pong_timeout));
2374
0
}
2375
2376
static void
2377
send_ping (SoupWebsocketConnection *self, const guint8 *ping_payload, gsize length)
2378
0
{
2379
0
  g_debug ("sending ping message");
2380
2381
0
  send_message (self, SOUP_WEBSOCKET_QUEUE_NORMAL, 0x09,
2382
0
          ping_payload, length);
2383
0
}
2384
2385
static gboolean
2386
on_keepalive_timeout (gpointer user_data)
2387
0
{
2388
0
  SoupWebsocketConnection *self = SOUP_WEBSOCKET_CONNECTION (user_data);
2389
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2390
2391
        /* We need to be able to uniquely identify each ping so that we can
2392
         * bookkeep what pongs we are still missing. Since we use TCP as
2393
         * transport, we don't need to worry about some pings and pongs getting
2394
         * lost. An ascending sequence number will work fine to uniquely
2395
         * identify pings.
2396
         *
2397
         * Even with G_MAXUINT64 this string is well within the 125 byte ping
2398
         * payload limit.
2399
         */
2400
0
        priv->last_keepalive_seq_num++;
2401
0
        char *ping_payload = g_strdup_printf (KEEPALIVE_PAYLOAD_PREFIX "%" G_GUINT64_FORMAT,
2402
0
                                              priv->last_keepalive_seq_num);
2403
2404
        /* We fully control the payload, so we know it is safe to print. */
2405
0
        g_debug ("ping %s", ping_payload);
2406
2407
0
  send_ping (self, (guint8 *) ping_payload, strlen(ping_payload));
2408
0
        if (priv->keepalive_pong_timeout > 0) {
2409
0
                register_outstanding_pong (self, g_steal_pointer (&ping_payload), priv->keepalive_pong_timeout);
2410
0
        } else {
2411
0
                g_clear_pointer (&ping_payload, g_free);
2412
0
        }
2413
2414
0
  return G_SOURCE_CONTINUE;
2415
0
}
2416
2417
/**
2418
 * soup_websocket_connection_set_keepalive_interval:
2419
 * @self: the WebSocket
2420
 * @interval: the interval to send a ping message or 0 to disable it
2421
 *
2422
 * Sets the interval in seconds on when to send a ping message which will serve
2423
 * as a keepalive message.
2424
 *
2425
 * If set to 0 the keepalive message is disabled.
2426
 */
2427
void
2428
soup_websocket_connection_set_keepalive_interval (SoupWebsocketConnection *self,
2429
                                                  guint                    interval)
2430
0
{
2431
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2432
2433
0
  g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
2434
2435
0
  if (priv->keepalive_interval != interval) {
2436
0
    priv->keepalive_interval = interval;
2437
0
    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_KEEPALIVE_INTERVAL]);
2438
2439
0
    keepalive_stop_timeout (self);
2440
2441
0
    if (interval > 0) {
2442
0
      priv->keepalive_timeout = g_timeout_source_new_seconds (interval);
2443
0
      g_source_set_static_name (priv->keepalive_timeout, "SoupWebsocketConnection keepalive timeout");
2444
0
      g_source_set_callback (priv->keepalive_timeout, on_keepalive_timeout, self, NULL);
2445
0
      g_source_attach (priv->keepalive_timeout, g_main_context_get_thread_default ());
2446
0
    }
2447
0
  }
2448
0
}
2449
2450
/**
2451
 * soup_websocket_connection_get_keepalive_pong_timeout:
2452
 * @self: the WebSocket
2453
 *
2454
 * Gets the keepalive pong timeout in seconds or 0 if disabled.
2455
 *
2456
 * Returns: the keepalive pong timeout.
2457
 *
2458
 * Since: 3.6
2459
 */
2460
guint
2461
soup_websocket_connection_get_keepalive_pong_timeout (SoupWebsocketConnection *self)
2462
0
{
2463
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2464
2465
0
        g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), 0);
2466
2467
0
        return priv->keepalive_pong_timeout;
2468
0
}
2469
2470
/**
2471
 * soup_websocket_connection_set_keepalive_pong_timeout:
2472
 * @self: the WebSocket
2473
 * @pong_timeout: the timeout in seconds
2474
 *
2475
 * Set the timeout in seconds for when the absence of a pong from a keepalive
2476
 * ping is assumed to be caused by a faulty connection.
2477
 *
2478
 * If set to 0 then the absence of pongs from keepalive pings is ignored.
2479
 *
2480
 * Since: 3.6
2481
 */
2482
void
2483
soup_websocket_connection_set_keepalive_pong_timeout (SoupWebsocketConnection *self,
2484
                                                      guint pong_timeout)
2485
0
{
2486
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2487
2488
0
        g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self));
2489
2490
0
        if (priv->keepalive_pong_timeout != pong_timeout) {
2491
0
                priv->keepalive_pong_timeout = pong_timeout;
2492
0
                g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_KEEPALIVE_PONG_TIMEOUT]);
2493
0
        }
2494
2495
0
        if (priv->keepalive_pong_timeout == 0) {
2496
0
                keepalive_stop_outstanding_pongs (self);
2497
0
        }
2498
0
}
2499
2500
void
2501
soup_websocket_connection_set_suppress_pongs_for_tests (SoupWebsocketConnection *self,
2502
                                                        gboolean suppress)
2503
0
{
2504
0
        SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self);
2505
2506
0
        priv->suppress_pongs_for_tests = suppress;
2507
0
}