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