Coverage Report

Created: 2026-02-26 06:07

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