static void on_web_socket_open (WebSocketConnection *connection, gpointer user_data) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (user_data); CockpitChannel *channel = COCKPIT_CHANNEL (user_data); JsonObject *object; JsonObject *headers; GHashTableIter iter; gpointer key, value; headers = json_object_new (); g_hash_table_iter_init (&iter, web_socket_client_get_headers (WEB_SOCKET_CLIENT (self->client))); while (g_hash_table_iter_next (&iter, &key, &value)) json_object_set_string_member (headers, key, value); object = json_object_new (); json_object_set_object_member (object, "headers", headers); cockpit_channel_control (channel, "response", object); json_object_unref (object); cockpit_channel_ready (channel, NULL); }
static void cockpit_web_socket_stream_dispose (GObject *object) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (object); GIOStream *io = NULL; // Owned by self->client; if (self->client) { if (web_socket_connection_get_ready_state (self->client) < WEB_SOCKET_STATE_CLOSING) web_socket_connection_close (self->client, WEB_SOCKET_CLOSE_GOING_AWAY, "disconnected"); g_signal_handler_disconnect (self->client, self->sig_open); g_signal_handler_disconnect (self->client, self->sig_message); g_signal_handler_disconnect (self->client, self->sig_closing); g_signal_handler_disconnect (self->client, self->sig_close); g_signal_handler_disconnect (self->client, self->sig_error); io = web_socket_connection_get_io_stream (self->client); if (io != NULL && self->sig_accept_cert) g_signal_handler_disconnect (io, self->sig_accept_cert); g_object_unref (self->client); self->client = NULL; } G_OBJECT_CLASS (cockpit_web_socket_stream_parent_class)->dispose (object); }
static void cockpit_web_socket_stream_finalize (GObject *object) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (object); g_free (self->url); g_free (self->origin); g_assert (self->client == NULL); G_OBJECT_CLASS (cockpit_web_socket_stream_parent_class)->finalize (object); }
static gboolean on_web_socket_error (WebSocketConnection *ws, GError *error, gpointer user_data) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (user_data); self->last_error_code = 0; if (error && error->domain == WEB_SOCKET_ERROR) self->last_error_code = error->code; return TRUE; }
static gboolean cockpit_web_socket_stream_control (CockpitChannel *channel, const gchar *command, JsonObject *options) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (channel); if (!g_str_equal (command, "done")) return FALSE; if (self->client && web_socket_connection_get_ready_state (self->client) == WEB_SOCKET_STATE_OPEN) web_socket_connection_close (self->client, WEB_SOCKET_CLOSE_NORMAL, "disconnected"); return TRUE; }
static void on_web_socket_close (WebSocketConnection *connection, gpointer user_data) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (user_data); CockpitChannel *channel = COCKPIT_CHANNEL (user_data); const gchar *problem; gushort code; code = web_socket_connection_get_close_code (connection); problem = web_socket_connection_get_close_data (connection); if (code == WEB_SOCKET_CLOSE_NORMAL || code == WEB_SOCKET_CLOSE_GOING_AWAY) { problem = NULL; } else if (problem == NULL || !problem[0]) { /* If we don't have a code but have a last error * use it's code */ if (code == 0) code = self->last_error_code; switch (code) { case WEB_SOCKET_CLOSE_NO_STATUS: case WEB_SOCKET_CLOSE_ABNORMAL: problem = "disconnected"; break; case WEB_SOCKET_CLOSE_PROTOCOL: case WEB_SOCKET_CLOSE_UNSUPPORTED_DATA: case WEB_SOCKET_CLOSE_BAD_DATA: case WEB_SOCKET_CLOSE_POLICY_VIOLATION: case WEB_SOCKET_CLOSE_TOO_BIG: case WEB_SOCKET_CLOSE_TLS_HANDSHAKE: problem = "protocol-error"; break; case WEB_SOCKET_CLOSE_NO_EXTENSION: problem = "unsupported"; break; default: problem = "internal-error"; break; } } cockpit_channel_close (channel, problem); }
static void cockpit_web_socket_stream_close (CockpitChannel *channel, const gchar *problem) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (channel); self->closed = TRUE; if (self->client && web_socket_connection_get_ready_state (self->client) < WEB_SOCKET_STATE_CLOSING) { if (problem) web_socket_connection_close (self->client, WEB_SOCKET_CLOSE_ABNORMAL, problem); else web_socket_connection_close (self->client, WEB_SOCKET_CLOSE_NORMAL, "disconnected"); } COCKPIT_CHANNEL_CLASS (cockpit_web_socket_stream_parent_class)->close (channel, problem); }
static void cockpit_web_socket_stream_prepare (CockpitChannel *channel) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (channel); CockpitConnectable *connectable = NULL; JsonObject *options; const gchar *path; gboolean started = FALSE; COCKPIT_CHANNEL_CLASS (cockpit_web_socket_stream_parent_class)->prepare (channel); if (self->closed) goto out; connectable = cockpit_channel_parse_stream (channel); if (!connectable) goto out; options = cockpit_channel_get_options (channel); if (!cockpit_json_get_string (options, "path", NULL, &path)) { g_warning ("%s: bad \"path\" field in WebSocket stream request", self->origin); goto out; } else if (path == NULL || path[0] != '/') { g_warning ("%s: invalid or missing \"path\" field in WebSocket stream request", self->origin); goto out; } self->url = g_strdup_printf ("%s://%s%s", connectable->tls ? "wss" : "ws", connectable->name, path); self->origin = g_strdup_printf ("%s://%s", connectable->tls ? "https" : "http", connectable->name); /* Parsed elsewhere */ self->binary = json_object_has_member (options, "binary"); cockpit_connect_stream_full (connectable, NULL, on_socket_connect, g_object_ref (self)); started = TRUE; out: if (connectable) { cockpit_connectable_unref (connectable); if (!started) cockpit_channel_close (channel, "protocol-error"); } }
static void cockpit_web_socket_stream_recv (CockpitChannel *channel, GBytes *message) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (channel); WebSocketDataType type; WebSocketState state; /* Should never be called before cockpit_channel_ready() */ g_return_if_fail (self->client != NULL); state = web_socket_connection_get_ready_state (self->client); g_return_if_fail (state >= WEB_SOCKET_STATE_OPEN); if (state == WEB_SOCKET_STATE_OPEN) { type = self->binary ? WEB_SOCKET_DATA_BINARY : WEB_SOCKET_DATA_TEXT; web_socket_connection_send (self->client, type, NULL, message); } }
static void on_socket_connect (GObject *object, GAsyncResult *result, gpointer user_data) { CockpitWebSocketStream *self = COCKPIT_WEB_SOCKET_STREAM (user_data); CockpitChannel *channel = COCKPIT_CHANNEL (self); const gchar *problem = "protocol-error"; gchar **protocols = NULL; GList *l, *names = NULL; GError *error = NULL; JsonObject *options; JsonObject *headers; const gchar *value; JsonNode *node; GIOStream *io; io = cockpit_connect_stream_finish (result, &error); if (error) { problem = cockpit_stream_problem (error, self->origin, "couldn't connect", cockpit_channel_close_options (channel)); cockpit_channel_close (channel, problem); goto out; } options = cockpit_channel_get_options (channel); if (!cockpit_json_get_strv (options, "protocols", NULL, &protocols)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid \"protocol\" value in WebSocket stream request", self->origin); goto out; } if (G_IS_TLS_CONNECTION (io)) { self->sig_accept_cert = g_signal_connect (G_TLS_CONNECTION (io), "accept-certificate", G_CALLBACK (on_rejected_certificate), self); } else { self->sig_accept_cert = 0; } self->client = web_socket_client_new_for_stream (self->url, self->origin, (const gchar **)protocols, io); node = json_object_get_member (options, "headers"); if (node) { if (!JSON_NODE_HOLDS_OBJECT (node)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid \"headers\" field in WebSocket stream request", self->origin); goto out; } headers = json_node_get_object (node); names = json_object_get_members (headers); for (l = names; l != NULL; l = g_list_next (l)) { node = json_object_get_member (headers, l->data); if (!node || !JSON_NODE_HOLDS_VALUE (node) || json_node_get_value_type (node) != G_TYPE_STRING) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid header value in WebSocket stream request: %s", self->origin, (gchar *)l->data); goto out; } value = json_node_get_string (node); g_debug ("%s: sending header: %s %s", self->origin, (gchar *)l->data, value); web_socket_client_include_header (WEB_SOCKET_CLIENT (self->client), l->data, value); } } self->sig_open = g_signal_connect (self->client, "open", G_CALLBACK (on_web_socket_open), self); self->sig_message = g_signal_connect (self->client, "message", G_CALLBACK (on_web_socket_message), self); self->sig_closing = g_signal_connect (self->client, "closing", G_CALLBACK (on_web_socket_closing), self); self->sig_close = g_signal_connect (self->client, "close", G_CALLBACK (on_web_socket_close), self); self->sig_error = g_signal_connect (self->client, "error", G_CALLBACK (on_web_socket_error), self); problem = NULL; out: g_clear_error (&error); g_strfreev (protocols); if (io) g_object_unref (io); g_list_free (names); }