static guint parse_response_headers (SoupMessage *req, char *headers, guint headers_len, SoupEncoding *encoding, gpointer user_data) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req); SoupHTTPVersion version; g_free(req->reason_phrase); req->reason_phrase = NULL; if (!soup_headers_parse_response (headers, headers_len, req->response_headers, &version, &req->status_code, &req->reason_phrase)) return SOUP_STATUS_MALFORMED; g_object_notify (G_OBJECT (req), SOUP_MESSAGE_STATUS_CODE); g_object_notify (G_OBJECT (req), SOUP_MESSAGE_REASON_PHRASE); if (version < priv->http_version) { priv->http_version = version; g_object_notify (G_OBJECT (req), SOUP_MESSAGE_HTTP_VERSION); } if ((req->method == SOUP_METHOD_HEAD || req->status_code == SOUP_STATUS_NO_CONTENT || req->status_code == SOUP_STATUS_NOT_MODIFIED || SOUP_STATUS_IS_INFORMATIONAL (req->status_code)) || (req->method == SOUP_METHOD_CONNECT && SOUP_STATUS_IS_SUCCESSFUL (req->status_code))) *encoding = SOUP_ENCODING_NONE; else *encoding = soup_message_headers_get_encoding (req->response_headers); if (*encoding == SOUP_ENCODING_UNRECOGNIZED) return SOUP_STATUS_MALFORMED; return SOUP_STATUS_OK; }
static gboolean send_request (const gchar *method, const gchar *path, const gchar *content, const gchar *macaroon, gchar **discharges, guint *status_code, gchar **reason_phrase, gchar **response_type, gchar **response, gsize *response_length, GCancellable *cancellable, GError **error) { g_autoptr (GSocket) socket = NULL; g_autoptr (GString) request = NULL; gssize n_written; g_autoptr (GByteArray) buffer = NULL; gsize data_length = 0, header_length; gchar *body = NULL; g_autoptr (SoupMessageHeaders) headers = NULL; gsize chunk_length = 0, n_required; guint8 *chunk_start = NULL; guint code; // NOTE: Would love to use libsoup but it doesn't support unix sockets // https://bugzilla.gnome.org/show_bug.cgi?id=727563 socket = open_snapd_socket (cancellable, error); if (socket == NULL) return FALSE; request = g_string_new (""); g_string_append_printf (request, "%s %s HTTP/1.1\r\n", method, path); g_string_append (request, "Host:\r\n"); if (macaroon != NULL) { gint i; g_string_append_printf (request, "Authorization: Macaroon root=\"%s\"", macaroon); for (i = 0; discharges[i] != NULL; i++) g_string_append_printf (request, ",discharge=\"%s\"", discharges[i]); g_string_append (request, "\r\n"); } if (content) g_string_append_printf (request, "Content-Length: %zu\r\n", strlen (content)); g_string_append (request, "\r\n"); if (content) g_string_append (request, content); g_debug ("begin snapd request: %s", request->str); /* send HTTP request */ n_written = g_socket_send (socket, request->str, request->len, cancellable, error); if (n_written < 0) return FALSE; /* read HTTP headers */ buffer = g_byte_array_new (); while (body == NULL) { if (!read_from_snapd (socket, buffer, &data_length, 1024, cancellable, error)) return FALSE; body = strstr (buffer->data, "\r\n\r\n"); } if (body == NULL) { g_set_error_literal (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_INVALID_FORMAT, "Unable to find header separator in snapd response"); return FALSE; } /* body starts after header divider */ body += 4; header_length = (gsize) ((guint8 *) body - buffer->data); /* parse headers */ headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE); if (!soup_headers_parse_response (buffer->data, (gint) header_length, headers, NULL, &code, reason_phrase)) { g_set_error_literal (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_INVALID_FORMAT, "snapd response HTTP headers not parseable"); return FALSE; } if (status_code != NULL) *status_code = code; /* read content */ switch (soup_message_headers_get_encoding (headers)) { case SOUP_ENCODING_EOF: while (TRUE) { gsize n_read = data_length; if (!read_from_snapd (socket, buffer, &data_length, 1024, cancellable, error)) return FALSE; if (n_read == data_length) break; chunk_length += data_length - n_read; } break; case SOUP_ENCODING_CHUNKED: // FIXME: support multiple chunks while (TRUE) { chunk_start = strstr (body, "\r\n"); if (chunk_start) break; if (!read_from_snapd (socket, buffer, &data_length, 1024, cancellable, error)) return FALSE; } if (!chunk_start) { g_set_error_literal (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_INVALID_FORMAT, "Unable to find chunk header in " "snapd response"); return FALSE; } chunk_length = strtoul (body, NULL, 16); chunk_start += 2; /* check if enough space to read chunk */ n_required = (chunk_start - buffer->data) + chunk_length; while (data_length < n_required) if (!read_from_snapd (socket, buffer, &data_length, n_required - data_length, cancellable, error)) return FALSE; break; case SOUP_ENCODING_CONTENT_LENGTH: chunk_length = soup_message_headers_get_content_length (headers); chunk_start = body; n_required = header_length + chunk_length; while (data_length < n_required) { if (!read_from_snapd (socket, buffer, &data_length, n_required - data_length, cancellable, error)) return FALSE; } break; default: g_set_error_literal (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_INVALID_FORMAT, "Unable to determine content " "length of snapd response"); return FALSE; } if (response_type) *response_type = g_strdup (soup_message_headers_get_content_type (headers, NULL)); if (response != NULL && chunk_start != NULL) { *response = g_malloc (chunk_length + 2); memcpy (*response, chunk_start, chunk_length + 1); (*response)[chunk_length + 1] = '\0'; g_debug ("snapd status %u: %s", code, *response); } if (response_length) *response_length = chunk_length; return TRUE; }