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 on_socket_close (WebSocketConnection *socket, gpointer user_data) { CockpitChannelSocket *self = COCKPIT_CHANNEL_SOCKET (user_data); CockpitChannel *channel = COCKPIT_CHANNEL (user_data); const gchar *problem = NULL; gushort code; if (self->closed) return; code = web_socket_connection_get_close_code (socket); if (code == WEB_SOCKET_CLOSE_NORMAL) { cockpit_channel_control (channel, "done", NULL); } else { problem = web_socket_connection_get_close_data (socket); if (problem == NULL) problem = "disconnected"; } cockpit_channel_close (channel, problem); }
static gboolean on_web_socket_closing (WebSocketConnection *connection, gpointer user_data) { CockpitChannel *channel = COCKPIT_CHANNEL (user_data); cockpit_channel_control (channel, "done", NULL); return TRUE; }
/** * cockpit_channel_ready: * @self: a pipe * @message: an optional control message, or NULL * * Called by channel implementations to signal when they're * ready. Any messages received before the channel was ready * will be delivered to the channel's recv() vfunc in the order * that they were received. * * If this is called immediately after or during construction then * the closing will happen after the main loop so that handlers * can connect appropriately. */ void cockpit_channel_ready (CockpitChannel *self, JsonObject *message) { g_object_ref (self); cockpit_transport_thaw (self->priv->transport, self->priv->id); cockpit_channel_control (self, "ready", message); g_object_unref (self); }
static void cockpit_channel_actual_send (CockpitChannel *self, GBytes *payload, gboolean trust_is_utf8) { GBytes *validated = NULL; guint64 out_sequence; JsonObject *ping; gsize size; g_return_if_fail (self->priv->out_buffer == NULL); g_return_if_fail (self->priv->buffer_timeout == 0); if (!trust_is_utf8) { if (!self->priv->binary_ok) payload = validated = cockpit_unicode_force_utf8 (payload); } cockpit_transport_send (self->priv->transport, self->priv->id, payload); /* A wraparound of our gint64 size? */ if (self->priv->flow_control) { size = g_bytes_get_size (payload); g_return_if_fail (G_MAXINT64 - size > self->priv->out_sequence); /* How many bytes have been sent (queued) */ out_sequence = self->priv->out_sequence + size; /* Every CHANNEL_FLOW_PING bytes we send a ping */ if (out_sequence / CHANNEL_FLOW_PING != self->priv->out_sequence / CHANNEL_FLOW_PING) { ping = json_object_new (); json_object_set_int_member (ping, "sequence", out_sequence); cockpit_channel_control (self, "ping", ping); g_debug ("%s: sending ping with sequence: %" G_GINT64_FORMAT, self->priv->id, out_sequence); json_object_unref (ping); } /* If we've sent more than the window, apply back pressure */ self->priv->out_sequence = out_sequence; if (self->priv->out_sequence > self->priv->out_window) { g_debug ("%s: sent too much data without acknowledgement, emitting back pressure until %" G_GINT64_FORMAT, self->priv->id, self->priv->out_window); cockpit_flow_emit_pressure (COCKPIT_FLOW (self), TRUE); } } if (validated) g_bytes_unref (validated); }
/** * cockpit_channel_ready: * @self: a pipe * * Called by channel implementations to signal when they're * ready. Any messages received before the channel was ready * will be delivered to the channel's recv() vfunc in the order * that they were received. * * If this is called immediately after or during construction then * the closing will happen after the main loop so that handlers * can connect appropriately. */ void cockpit_channel_ready (CockpitChannel *self) { CockpitChannelClass *klass; GBytes *decoded; GBytes *payload; GQueue *queue; klass = COCKPIT_CHANNEL_GET_CLASS (self); g_assert (klass->recv != NULL); g_assert (klass->close != NULL); g_object_ref (self); while (self->priv->received) { queue = self->priv->received; self->priv->received = NULL; for (;;) { payload = g_queue_pop_head (queue); if (payload == NULL) break; if (self->priv->base64_encoding) { decoded = base64_decode (payload); g_bytes_unref (payload); payload = decoded; } (klass->recv) (self, payload); g_bytes_unref (payload); } g_queue_free (queue); } cockpit_channel_control (self, "ready", NULL); self->priv->ready = TRUE; /* No more data coming? */ if (self->priv->received_done) { if (klass->control) (klass->control) (self, "done", NULL); } g_object_unref (self); }
static void on_socket_open (WebSocketConnection *connection, gpointer user_data) { CockpitChannel *channel = COCKPIT_CHANNEL (user_data); JsonObject *open; /* * Actually open the channel. We wait until the WebSocket is open * before doing this, so we don't receive messages from the bridge * before the websocket is open. */ open = cockpit_channel_get_options (channel); cockpit_channel_control (channel, "open", open); /* Tell the channel we're ready */ cockpit_channel_ready (channel, NULL); }
static void cockpit_http_stream_close (CockpitChannel *channel, const gchar *problem) { CockpitHttpStream *self = COCKPIT_HTTP_STREAM (channel); if (problem) { self->failed = TRUE; self->state = FINISHED; COCKPIT_CHANNEL_CLASS (cockpit_http_stream_parent_class)->close (channel, problem); } else if (self->state == RELAY_DATA) { g_debug ("%s: relayed response", self->name); self->state = FINISHED; cockpit_channel_control (channel, "done", NULL); /* Save this for another round? */ if (self->keep_alive) { if (self->sig_open) g_signal_handler_disconnect (self->stream, self->sig_open); g_signal_handler_disconnect (self->stream, self->sig_read); g_signal_handler_disconnect (self->stream, self->sig_close); g_signal_handler_disconnect (self->stream, self->sig_rejected_cert); cockpit_http_client_checkin (self->client, self->stream); cockpit_flow_throttle (COCKPIT_FLOW (self->stream), NULL); cockpit_flow_throttle (COCKPIT_FLOW (channel), NULL); g_object_unref (self->stream); self->stream = NULL; } COCKPIT_CHANNEL_CLASS (cockpit_http_stream_parent_class)->close (channel, NULL); } else if (self->state != FINISHED) { g_warn_if_reached (); self->failed = TRUE; self->state = FINISHED; COCKPIT_CHANNEL_CLASS (cockpit_http_stream_parent_class)->close (channel, "internal-error"); } }
/** * cockpit_channel_ready: * @self: a pipe * @message: an optional control message, or NULL * * Called by channel implementations to signal when they're * ready. Any messages received before the channel was ready * will be delivered to the channel's recv() vfunc in the order * that they were received. * * If this is called immediately after or during construction then * the closing will happen after the main loop so that handlers * can connect appropriately. */ void cockpit_channel_ready (CockpitChannel *self, JsonObject *message) { FrozenMessage *frozen; g_return_if_fail (self->priv->frozen != NULL); g_object_ref (self); while (self->priv->frozen) { frozen = g_queue_pop_head (self->priv->frozen); if (frozen == NULL) break; /* Is it a control message */ if (frozen->control) { process_control (self, json_object_get_string_member (frozen->control, "command"), frozen->control); } else { process_recv (self, frozen->payload); } frozen_message_free (frozen); } if (self->priv->frozen) g_queue_free (self->priv->frozen); self->priv->frozen = NULL; cockpit_channel_control (self, "ready", message); g_object_unref (self); }
static void on_files_listed (GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *error = NULL; JsonObject *options; GList *files; files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source_object), res, &error); if (error) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { CockpitFslist *self = COCKPIT_FSLIST (user_data); g_message ("%s: couldn't process files %s", COCKPIT_FSLIST(user_data)->path, error->message); options = cockpit_channel_close_options (COCKPIT_CHANNEL (self)); json_object_set_string_member (options, "message", error->message); cockpit_channel_close (COCKPIT_CHANNEL (self), "internal-error"); } g_clear_error (&error); return; } CockpitFslist *self = COCKPIT_FSLIST (user_data); if (files == NULL) { g_clear_object (&self->cancellable); g_object_unref (source_object); cockpit_channel_ready (COCKPIT_CHANNEL (self)); if (self->monitor == NULL) { cockpit_channel_control (COCKPIT_CHANNEL (self), "done", NULL); cockpit_channel_close (COCKPIT_CHANNEL (self), NULL); } return; } for (GList *l = files; l; l = l->next) { GFileInfo *info = G_FILE_INFO (l->data); JsonObject *msg; GBytes *msg_bytes; msg = json_object_new (); json_object_set_string_member (msg, "event", "present"); json_object_set_string_member (msg, "path", g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_STANDARD_NAME)); json_object_set_string_member (msg, "type", cockpit_file_type_to_string (g_file_info_get_file_type (info))); msg_bytes = cockpit_json_write_bytes (msg); json_object_unref (msg); cockpit_channel_send (COCKPIT_CHANNEL(self), msg_bytes, FALSE); g_bytes_unref (msg_bytes); } g_list_free_full (files, g_object_unref); g_file_enumerator_next_files_async (G_FILE_ENUMERATOR (source_object), 10, G_PRIORITY_DEFAULT, self->cancellable, on_files_listed, self); }
static gboolean relay_headers (CockpitHttpStream *self, CockpitChannel *channel, GByteArray *buffer) { GHashTable *headers = NULL; gchar *version = NULL; gchar *reason = NULL; JsonObject *object; const gchar *data; JsonObject *heads; GHashTableIter iter; GBytes *message; gpointer key; gpointer value; guint status; gsize length; gssize offset; gssize offset2; data = (const gchar *)buffer->data; length = buffer->len; offset = web_socket_util_parse_status_line (data, length, &version, &status, &reason); if (offset == 0) return FALSE; /* want more data */ if (offset < 0) { cockpit_channel_fail (channel, "protocol-error", "%s: received response with bad HTTP status line", self->name); goto out; } offset2 = web_socket_util_parse_headers (data + offset, length - offset, &headers); if (offset2 == 0) return FALSE; /* want more data */ if (offset2 < 0) { cockpit_channel_fail (channel, "protocol-error", "%s: received response with bad HTTP headers", self->name); goto out; } g_debug ("%s: response: %u %s", self->name, status, reason); g_hash_table_iter_init (&iter, headers); while (g_hash_table_iter_next (&iter, &key, &value)) g_debug ("%s: header: %s %s", self->name, (gchar *)key, (gchar *)value); if (!parse_transfer_encoding (self, channel, headers) || !parse_content_length (self, channel, status, headers) || !parse_keep_alive (self, channel, version, headers)) goto out; cockpit_pipe_skip (buffer, offset + offset2); if (!self->binary) { g_hash_table_remove (headers, "Content-Length"); g_hash_table_remove (headers, "Range"); } g_hash_table_remove (headers, "Connection"); g_hash_table_remove (headers, "Transfer-Encoding"); /* Now serialize all the rest of this into JSON */ object = json_object_new (); json_object_set_int_member (object, "status", status); json_object_set_string_member (object, "reason", reason); heads = json_object_new(); g_hash_table_iter_init (&iter, headers); while (g_hash_table_iter_next (&iter, &key, &value)) json_object_set_string_member (heads, key, value); json_object_set_object_member (object, "headers", heads); if (self->headers_inline) { message = cockpit_json_write_bytes (object); cockpit_channel_send (channel, message, TRUE); g_bytes_unref (message); } else { cockpit_channel_control (channel, "response", object); } json_object_unref (object); out: if (headers) g_hash_table_unref (headers); g_free (version); g_free (reason); return TRUE; }