static guint append_header (GString *string, const gchar *name, const gchar *value) { if (value) { g_return_val_if_fail (cockpit_web_response_is_simple_token (name), 0); g_return_val_if_fail (cockpit_web_response_is_header_value (value), 0); g_string_append_printf (string, "%s: %s\r\n", name, value); } if (g_ascii_strcasecmp ("Content-Type", name) == 0) return HEADER_CONTENT_TYPE; if (g_ascii_strcasecmp ("Cache-Control", name) == 0) return HEADER_CACHE_CONTROL; if (g_ascii_strcasecmp ("Vary", name) == 0) return HEADER_VARY; if (g_ascii_strcasecmp ("Content-Encoding", name) == 0) return HEADER_CONTENT_ENCODING; else if (g_ascii_strcasecmp ("Content-Length", name) == 0) g_critical ("Don't set Content-Length manually. This is a programmer error."); else if (g_ascii_strcasecmp ("Connection", name) == 0) g_critical ("Don't set Connection header manually. This is a programmer error."); return 0; }
gboolean cockpit_web_service_parse_external (JsonObject *options, const gchar **content_type, const gchar **content_encoding, const gchar **content_disposition, gchar ***protocols) { JsonObject *external; const gchar *value; JsonNode *node; g_return_val_if_fail (options != NULL, FALSE); if (!cockpit_json_get_string (options, "channel", NULL, &value) || value != NULL) { g_message ("don't specify \"channel\" on external channel"); return FALSE; } if (!cockpit_json_get_string (options, "command", NULL, &value) || value != NULL) { g_message ("don't specify \"command\" on external channel"); return FALSE; } node = json_object_get_member (options, "external"); if (node == NULL) { if (content_disposition) *content_disposition = NULL; if (content_type) *content_type = NULL; if (content_encoding) *content_encoding = NULL; if (protocols) *protocols = NULL; return TRUE; } if (!JSON_NODE_HOLDS_OBJECT (node)) { g_message ("invalid \"external\" option"); return FALSE; } external = json_node_get_object (node); if (!cockpit_json_get_string (external, "content-disposition", NULL, &value) || (value && !cockpit_web_response_is_header_value (value))) { g_message ("invalid \"content-disposition\" external option"); return FALSE; } if (content_disposition) *content_disposition = value; if (!cockpit_json_get_string (external, "content-type", NULL, &value) || (value && !cockpit_web_response_is_header_value (value))) { g_message ("invalid \"content-type\" external option"); return FALSE; } if (content_type) *content_type = value; if (!cockpit_json_get_string (external, "content-encoding", NULL, &value) || (value && !cockpit_web_response_is_header_value (value))) { g_message ("invalid \"content-encoding\" external option"); return FALSE; } if (content_encoding) *content_encoding = value; if (!cockpit_json_get_strv (external, "protocols", NULL, protocols)) { g_message ("invalid \"protocols\" external option"); return FALSE; } return TRUE; }
static void send_http_request (CockpitHttpStream *self) { CockpitChannel *channel = COCKPIT_CHANNEL (self); JsonObject *options; gboolean had_host; gboolean had_encoding; const gchar *method; const gchar *path; GString *string = NULL; JsonNode *node; JsonObject *headers; const gchar *header; const gchar *value; GList *request = NULL; GList *names = NULL; GBytes *bytes; GList *l; gsize total; options = cockpit_channel_get_options (channel); /* * The checks we do here for token validity are just enough to be able * to format an HTTP response, without leaking across lines etc. */ if (!cockpit_json_get_string (options, "path", NULL, &path)) { cockpit_channel_fail (channel, "protocol-error", "%s: bad \"path\" field in HTTP stream request", self->name); goto out; } else if (path == NULL) { cockpit_channel_fail (channel, "protocol-error", "%s: missing \"path\" field in HTTP stream request", self->name); goto out; } else if (!cockpit_web_response_is_simple_token (path)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid \"path\" field in HTTP stream request", self->name); goto out; } if (!cockpit_json_get_string (options, "method", NULL, &method)) { cockpit_channel_fail (channel, "protocol-error", "%s: bad \"method\" field in HTTP stream request", self->name); goto out; } else if (method == NULL) { cockpit_channel_fail (channel, "protocol-error", "%s: missing \"method\" field in HTTP stream request", self->name); goto out; } else if (!cockpit_web_response_is_simple_token (method)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid \"method\" field in HTTP stream request", self->name); goto out; } g_debug ("%s: sending %s request", self->name, method); string = g_string_sized_new (128); g_string_printf (string, "%s %s HTTP/1.1\r\n", method, path); had_encoding = had_host = FALSE; 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 HTTP stream request", self->name); goto out; } headers = json_node_get_object (node); names = json_object_get_members (headers); for (l = names; l != NULL; l = g_list_next (l)) { header = l->data; if (!cockpit_web_response_is_simple_token (header)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid header in HTTP stream request: %s", self->name, header); goto out; } node = json_object_get_member (headers, header); 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 HTTP stream request: %s", self->name, header); goto out; } value = json_node_get_string (node); if (disallowed_header (header, value, self->binary)) { cockpit_channel_fail (channel, "protocol-error", "%s: disallowed header in HTTP stream request: %s", self->name, header); goto out; } if (!cockpit_web_response_is_header_value (value)) { cockpit_channel_fail (channel, "protocol-error", "%s: invalid header value in HTTP stream request: %s", self->name, header); goto out; } g_string_append_printf (string, "%s: %s\r\n", (gchar *)l->data, value); g_debug ("%s: sending header: %s %s", self->name, (gchar *)l->data, value); if (g_ascii_strcasecmp (l->data, "Host") == 0) had_host = TRUE; if (g_ascii_strcasecmp (l->data, "Accept-Encoding") == 0) had_encoding = TRUE; } } if (!had_host) { g_string_append (string, "Host: "); g_string_append_uri_escaped (string, self->client->connectable->name, "[]!%$&()*+,-.:;=\\_~", FALSE); g_string_append (string, "\r\n"); } if (!had_encoding) g_string_append (string, "Accept-Encoding: identity\r\n"); if (!self->binary) g_string_append (string, "Accept-Charset: UTF-8\r\n"); request = g_list_reverse (self->request); self->request = NULL; /* Calculate how much data we have to send */ total = 0; for (l = request; l != NULL; l = g_list_next (l)) total += g_bytes_get_size (l->data); if (request || g_ascii_strcasecmp (method, "POST") == 0) g_string_append_printf (string, "Content-Length: %" G_GSIZE_FORMAT "\r\n", total); g_string_append (string, "\r\n"); bytes = g_string_free_to_bytes (string); string = NULL; cockpit_stream_write (self->stream, bytes); g_bytes_unref (bytes); /* Now send all the data */ for (l = request; l != NULL; l = g_list_next (l)) cockpit_stream_write (self->stream, l->data); out: g_list_free (names); g_list_free_full (request, (GDestroyNotify)g_bytes_unref); if (string) g_string_free (string, TRUE); }