Ejemplo n.º 1
0
/**
 * soup_message_headers_get_content_length:
 * @hdrs: a #SoupMessageHeaders
 *
 * Gets the message body length that @hdrs declare. This will only
 * be non-0 if soup_message_headers_get_encoding() returns
 * %SOUP_ENCODING_CONTENT_LENGTH.
 *
 * Return value: the message body length declared by @hdrs.
 **/
goffset
soup_message_headers_get_content_length (SoupMessageHeaders *hdrs)
{
	SoupEncoding encoding;

	encoding = soup_message_headers_get_encoding (hdrs);
	if (encoding == SOUP_ENCODING_CONTENT_LENGTH)
		return hdrs->content_length;
	else
		return 0;
}
Ejemplo n.º 2
0
void gotheaders(SoupMessage *msg, GtkTreeRowReference *ref) {
	GtkTreeIter iter;
	GtkListStore *store = GTK_LIST_STORE(gtk_builder_get_object(builder, "downloads"));
	gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, gtk_tree_row_reference_get_path(ref));
	if(SOUP_ENCODING_CONTENT_LENGTH == soup_message_headers_get_encoding(msg->response_headers)) {
		gint64 content_length = soup_message_headers_get_content_length(msg->response_headers);
		printf("got content_length %lld\n", content_length);
		gtk_list_store_set(store, &iter, 2, content_length, 3, (gint64)0, -1);
	} else {
		printf("got content_length -1\n");
		gtk_list_store_set(store, &iter, 2, -1, -1);
	}
}
Ejemplo n.º 3
0
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 void
get_response_headers (SoupMessage *msg, GString *headers,
		      SoupEncoding *encoding, gpointer user_data)
{
	SoupEncoding claimed_encoding;
	SoupMessageHeadersIter iter;
	const char *name, *value;

	handle_partial_get (msg);

	g_string_append_printf (headers, "HTTP/1.%c %d %s\r\n",
				soup_message_get_http_version (msg) == SOUP_HTTP_1_0 ? '0' : '1',
				msg->status_code, msg->reason_phrase);

	claimed_encoding = soup_message_headers_get_encoding (msg->response_headers);
	if ((msg->method == SOUP_METHOD_HEAD ||
	     msg->status_code  == SOUP_STATUS_NO_CONTENT ||
	     msg->status_code  == SOUP_STATUS_NOT_MODIFIED ||
	     SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) ||
	    (msg->method == SOUP_METHOD_CONNECT &&
	     SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)))
		*encoding = SOUP_ENCODING_NONE;
	else
		*encoding = claimed_encoding;

	if (claimed_encoding == SOUP_ENCODING_CONTENT_LENGTH &&
	    !soup_message_headers_get_content_length (msg->response_headers)) {
		soup_message_headers_set_content_length (msg->response_headers,
							 msg->response_body->length);
	}

	soup_message_headers_iter_init (&iter, msg->response_headers);
	while (soup_message_headers_iter_next (&iter, &name, &value))
		g_string_append_printf (headers, "%s: %s\r\n", name, value);
	g_string_append (headers, "\r\n");
}
Ejemplo n.º 5
0
static void
gst_soup_http_src_got_headers_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
{
  const char *value;
  GstTagList *tag_list;
  GstBaseSrc *basesrc;
  guint64 newsize;
  GHashTable *params = NULL;

  GST_DEBUG_OBJECT (src, "got headers:");
  soup_message_headers_foreach (msg->response_headers,
      gst_soup_http_src_headers_foreach, src);

  if (msg->status_code == 407 && src->proxy_id && src->proxy_pw)
    return;

  if (src->automatic_redirect && SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
    GST_DEBUG_OBJECT (src, "%u redirect to \"%s\"", msg->status_code,
        soup_message_headers_get_one (msg->response_headers, "Location"));
    return;
  }

  if (msg->status_code == SOUP_STATUS_UNAUTHORIZED)
    return;

  src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING;

  /* Parse Content-Length. */
  if (soup_message_headers_get_encoding (msg->response_headers) ==
      SOUP_ENCODING_CONTENT_LENGTH) {
    newsize = src->request_position +
        soup_message_headers_get_content_length (msg->response_headers);
    if (!src->have_size || (src->content_size != newsize)) {
      src->content_size = newsize;
      src->have_size = TRUE;
      src->seekable = TRUE;
      GST_DEBUG_OBJECT (src, "size = %" G_GUINT64_FORMAT, src->content_size);

      basesrc = GST_BASE_SRC_CAST (src);
      gst_segment_set_duration (&basesrc->segment, GST_FORMAT_BYTES,
          src->content_size);
      gst_element_post_message (GST_ELEMENT (src),
          gst_message_new_duration (GST_OBJECT (src), GST_FORMAT_BYTES,
              src->content_size));
    }
  }

  /* Icecast stuff */
  tag_list = gst_tag_list_new ();

  if ((value =
          soup_message_headers_get_one (msg->response_headers,
              "icy-metaint")) != NULL) {
    gint icy_metaint = atoi (value);

    GST_DEBUG_OBJECT (src, "icy-metaint: %s (parsed: %d)", value, icy_metaint);
    if (icy_metaint > 0) {
      if (src->src_caps)
        gst_caps_unref (src->src_caps);

      src->src_caps = gst_caps_new_simple ("application/x-icy",
          "metadata-interval", G_TYPE_INT, icy_metaint, NULL);
    }
  }
  if ((value =
          soup_message_headers_get_content_type (msg->response_headers,
              &params)) != NULL) {
    GST_DEBUG_OBJECT (src, "Content-Type: %s", value);
    if (g_ascii_strcasecmp (value, "audio/L16") == 0) {
      gint channels = 2;
      gint rate = 44100;
      char *param;

      if (src->src_caps)
        gst_caps_unref (src->src_caps);

      param = g_hash_table_lookup (params, "channels");
      if (param != NULL)
        channels = atol (param);

      param = g_hash_table_lookup (params, "rate");
      if (param != NULL)
        rate = atol (param);

      src->src_caps = gst_caps_new_simple ("audio/x-raw-int",
          "channels", G_TYPE_INT, channels,
          "rate", G_TYPE_INT, rate,
          "width", G_TYPE_INT, 16,
          "depth", G_TYPE_INT, 16,
          "signed", G_TYPE_BOOLEAN, TRUE,
          "endianness", G_TYPE_INT, G_BIG_ENDIAN, NULL);
    } else {
      /* Set the Content-Type field on the caps */
      if (src->src_caps)
        gst_caps_set_simple (src->src_caps, "content-type", G_TYPE_STRING,
            value, NULL);
    }
  }

  if (params != NULL)
    g_hash_table_destroy (params);

  if ((value =
          soup_message_headers_get_one (msg->response_headers,
              "icy-name")) != NULL) {
    g_free (src->iradio_name);
    src->iradio_name = gst_soup_http_src_unicodify (value);
    if (src->iradio_name) {
      g_object_notify (G_OBJECT (src), "iradio-name");
      gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION,
          src->iradio_name, NULL);
    }
  }
  if ((value =
          soup_message_headers_get_one (msg->response_headers,
              "icy-genre")) != NULL) {
    g_free (src->iradio_genre);
    src->iradio_genre = gst_soup_http_src_unicodify (value);
    if (src->iradio_genre) {
      g_object_notify (G_OBJECT (src), "iradio-genre");
      gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
          src->iradio_genre, NULL);
    }
  }
  if ((value = soup_message_headers_get_one (msg->response_headers, "icy-url"))
      != NULL) {
    g_free (src->iradio_url);
    src->iradio_url = gst_soup_http_src_unicodify (value);
    if (src->iradio_url) {
      g_object_notify (G_OBJECT (src), "iradio-url");
      gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION,
          src->iradio_url, NULL);
    }
  }
  if (!gst_tag_list_is_empty (tag_list)) {
    GST_DEBUG_OBJECT (src,
        "calling gst_element_found_tags with %" GST_PTR_FORMAT, tag_list);
    gst_element_found_tags (GST_ELEMENT_CAST (src), tag_list);
  } else {
    gst_tag_list_free (tag_list);
  }

  /* Handle HTTP errors. */
  gst_soup_http_src_parse_status (msg, src);

  /* Check if Range header was respected. */
  if (src->ret == GST_FLOW_CUSTOM_ERROR &&
      src->read_position && msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) {
    src->seekable = FALSE;
    GST_ELEMENT_ERROR (src, RESOURCE, SEEK,
        (_("Server does not support seeking.")),
        ("Server does not accept Range HTTP header, URL: %s", src->location));
    src->ret = GST_FLOW_ERROR;
  }
}
static guint
parse_request_headers (SoupMessage *msg, char *headers, guint headers_len,
		       SoupEncoding *encoding, gpointer sock)
{
	SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
	char *req_method, *req_path, *url;
	SoupHTTPVersion version;
	const char *req_host;
	guint status;
	SoupURI *uri;

	status = soup_headers_parse_request (headers, headers_len,
					     msg->request_headers,
					     &req_method,
					     &req_path,
					     &version);
	if (!SOUP_STATUS_IS_SUCCESSFUL (status))
		return status;

	g_object_set (G_OBJECT (msg),
		      SOUP_MESSAGE_METHOD, req_method,
		      SOUP_MESSAGE_HTTP_VERSION, version,
		      NULL);
	g_free (req_method);

	/* Handle request body encoding */
	*encoding = soup_message_headers_get_encoding (msg->request_headers);
	if (*encoding == SOUP_ENCODING_UNRECOGNIZED) {
		if (soup_message_headers_get_list (msg->request_headers, "Transfer-Encoding"))
			return SOUP_STATUS_NOT_IMPLEMENTED;
		else
			return SOUP_STATUS_BAD_REQUEST;
	}

	/* Generate correct context for request */
	req_host = soup_message_headers_get_one (msg->request_headers, "Host");
	if (req_host && strchr (req_host, '/')) {
		g_free (req_path);
		return SOUP_STATUS_BAD_REQUEST;
	}

	if (!strcmp (req_path, "*") && req_host) {
		/* Eg, "OPTIONS * HTTP/1.1" */
		url = g_strdup_printf ("%s://%s",
				       soup_socket_is_ssl (sock) ? "https" : "http",
				       req_host);
		uri = soup_uri_new (url);
		if (uri)
			soup_uri_set_path (uri, "*");
		g_free (url);
	} else if (*req_path != '/') {
		/* Must be an absolute URI */
		uri = soup_uri_new (req_path);
	} else if (req_host) {
		url = g_strdup_printf ("%s://%s%s",
				       soup_socket_is_ssl (sock) ? "https" : "http",
				       req_host, req_path);
		uri = soup_uri_new (url);
		g_free (url);
	} else if (priv->http_version == SOUP_HTTP_1_0) {
		/* No Host header, no AbsoluteUri */
		SoupAddress *addr = soup_socket_get_local_address (sock);

		uri = soup_uri_new (NULL);
		soup_uri_set_scheme (uri, soup_socket_is_ssl (sock) ?
				     SOUP_URI_SCHEME_HTTPS :
				     SOUP_URI_SCHEME_HTTP);
		soup_uri_set_host (uri, soup_address_get_physical (addr));
		soup_uri_set_port (uri, soup_address_get_port (addr));
		soup_uri_set_path (uri, req_path);
	} else
		uri = NULL;

	g_free (req_path);

	if (!SOUP_URI_VALID_FOR_HTTP (uri)) {
		/* certainly not "a valid host on the server" (RFC2616 5.2.3)
		 * SOUP_URI_VALID_FOR_HTTP also guards against uri == NULL
		 */
		if (uri)
			soup_uri_free (uri);
		return SOUP_STATUS_BAD_REQUEST;
	}

	soup_message_set_uri (msg, uri);
	soup_uri_free (uri);

	return SOUP_STATUS_OK;
}
static void
handle_partial_get (SoupMessage *msg)
{
	SoupRange *ranges;
	int nranges;
	SoupBuffer *full_response;

	/* Make sure the message is set up right for us to return a
	 * partial response; it has to be a GET, the status must be
	 * 200 OK (and in particular, NOT already 206 Partial
	 * Content), and the SoupServer must have already filled in
	 * the response body
	 */
	if (msg->method != SOUP_METHOD_GET ||
	    msg->status_code != SOUP_STATUS_OK ||
	    soup_message_headers_get_encoding (msg->response_headers) !=
	    SOUP_ENCODING_CONTENT_LENGTH ||
	    msg->response_body->length == 0 ||
	    !soup_message_body_get_accumulate (msg->response_body))
		return;

	/* Oh, and there has to have been a valid Range header on the
	 * request, of course.
	 */
	if (!soup_message_headers_get_ranges (msg->request_headers,
					      msg->response_body->length,
					      &ranges, &nranges))
		return;

	full_response = soup_message_body_flatten (msg->response_body);
	if (!full_response) {
		soup_message_headers_free_ranges (msg->request_headers, ranges);
		return;
	}

	soup_message_set_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
	soup_message_body_truncate (msg->response_body);

	if (nranges == 1) {
		SoupBuffer *range_buf;

		/* Single range, so just set Content-Range and fix the body. */

		soup_message_headers_set_content_range (msg->response_headers,
							ranges[0].start,
							ranges[0].end,
							full_response->length);
		range_buf = soup_buffer_new_subbuffer (full_response,
						       ranges[0].start,
						       ranges[0].end - ranges[0].start + 1);
		soup_message_body_append_buffer (msg->response_body, range_buf);
		soup_buffer_free (range_buf);
	} else {
		SoupMultipart *multipart;
		SoupMessageHeaders *part_headers;
		SoupBuffer *part_body;
		const char *content_type;
		int i;

		/* Multiple ranges, so build a multipart/byteranges response
		 * to replace msg->response_body with.
		 */

		multipart = soup_multipart_new ("multipart/byteranges");
		content_type = soup_message_headers_get_one (msg->response_headers,
							     "Content-Type");
		for (i = 0; i < nranges; i++) {
			part_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
			if (content_type) {
				soup_message_headers_append (part_headers,
							     "Content-Type",
							     content_type);
			}
			soup_message_headers_set_content_range (part_headers,
								ranges[i].start,
								ranges[i].end,
								full_response->length);
			part_body = soup_buffer_new_subbuffer (full_response,
							       ranges[i].start,
							       ranges[i].end - ranges[i].start + 1);
			soup_multipart_append_part (multipart, part_headers,
						    part_body);
			soup_message_headers_free (part_headers);
			soup_buffer_free (part_body);
		}

		soup_multipart_to_message (multipart, msg->response_headers,
					   msg->response_body);
		soup_multipart_free (multipart);
	}

	soup_buffer_free (full_response);
	soup_message_headers_free_ranges (msg->request_headers, ranges);
}
Ejemplo n.º 8
0
static void
file_info_from_message (SoupMessage *msg,
                        GFileInfo *info,
                        GFileAttributeMatcher *matcher)
{
  const char *text;
  GHashTable *params;
  char       *basename;
  char       *ed_name;

  basename = ed_name = NULL;

  /* prefer the filename from the Content-Disposition (rfc2183) header
     if one if present. See bug 551298. */
  if (soup_message_headers_get_content_disposition (msg->response_headers,
                                                    NULL, &params))
    {
      const char *name = g_hash_table_lookup (params, "filename");

      if (name)
        basename = g_strdup (name);

      g_hash_table_destroy (params);
    }

  if (basename == NULL)
    {
      const SoupURI *uri;

      uri = soup_message_get_uri (msg);
      basename = http_uri_get_basename (uri->path);
    }

  g_debug ("basename:%s\n", basename);

  /* read http/1.1 rfc, until then we copy the local files
   * behaviour */
  if (basename != NULL &&
      (g_file_attribute_matcher_matches (matcher,
                                         G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME) ||
       g_file_attribute_matcher_matches (matcher,
                                         G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME)))
    ed_name = gvfs_file_info_populate_names_as_local (info, basename);

  g_free (basename);
  g_free (ed_name);

  if (soup_message_headers_get_encoding (msg->response_headers) == SOUP_ENCODING_CONTENT_LENGTH)
    {
      goffset start, end, length;
      gboolean ret;

      ret = soup_message_headers_get_content_range (msg->response_headers,
                                                    &start, &end, &length);
      if (ret && length != -1)
        {
          g_file_info_set_size (info, length);
        }
      else if (!ret)
        {
          length = soup_message_headers_get_content_length (msg->response_headers);
          g_file_info_set_size (info, length);
        }
    }

  g_file_info_set_file_type (info, G_FILE_TYPE_REGULAR);

  text = soup_message_headers_get_content_type (msg->response_headers, NULL);
  if (text)
    {
      GIcon *icon;

      g_file_info_set_content_type (info, text);
      g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, text);

      icon = g_content_type_get_icon (text);
      g_file_info_set_icon (info, icon);
      g_object_unref (icon);

      icon = g_content_type_get_symbolic_icon (text);
      g_file_info_set_symbolic_icon (info, icon);
      g_object_unref (icon);
    }


  text = soup_message_headers_get_one (msg->response_headers,
                                       "Last-Modified");
  if (text)
    {
      SoupDate *sd;
      GTimeVal tv;

      sd = soup_date_new_from_string(text);
      if (sd)
        {
          soup_date_to_timeval (sd, &tv);
	  g_file_info_set_modification_time (info, &tv);
          soup_date_free (sd);
        }
    }


  text = soup_message_headers_get_one (msg->response_headers,
                                       "ETag");
  if (text)
    {
      g_file_info_set_attribute_string (info,
                                        G_FILE_ATTRIBUTE_ETAG_VALUE,
                                        text);
    }
}
Ejemplo n.º 9
0
static void
get_request_headers (SoupMessage *req, GString *header,
		     SoupEncoding *encoding, gpointer user_data)
{
	SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req);
	gboolean proxy = GPOINTER_TO_UINT (user_data);
	SoupURI *uri = soup_message_get_uri (req);
	char *uri_host;
	char *uri_string;
	SoupMessageHeadersIter iter;
	const char *name, *value;

	if (strchr (uri->host, ':'))
		uri_host = g_strdup_printf ("[%s]", uri->host);
	else
		uri_host = uri->host;

	if (req->method == SOUP_METHOD_CONNECT) {
		/* CONNECT URI is hostname:port for tunnel destination */
		uri_string = g_strdup_printf ("%s:%d", uri_host, uri->port);
	} else {
		/* Proxy expects full URI to destination. Otherwise
		 * just the path.
		 */
		uri_string = soup_uri_to_string (uri, !proxy);
	}

	if (priv->http_version == SOUP_HTTP_1_0) {
		g_string_append_printf (header, "%s %s HTTP/1.0\r\n",
					req->method, uri_string);
	} else {
		g_string_append_printf (header, "%s %s HTTP/1.1\r\n",
					req->method, uri_string);
		if (!soup_message_headers_get_one (req->request_headers, "Host")) {
			if (soup_uri_uses_default_port (uri)) {
				g_string_append_printf (header, "Host: %s\r\n",
							uri_host);
			} else {
				g_string_append_printf (header, "Host: %s:%d\r\n",
							uri_host, uri->port);
			}
		}
	}
	g_free (uri_string);
	if (uri_host != uri->host)
		g_free (uri_host);

	*encoding = soup_message_headers_get_encoding (req->request_headers);
	if ((*encoding == SOUP_ENCODING_CONTENT_LENGTH ||
	     *encoding == SOUP_ENCODING_NONE) &&
	    (req->request_body->length > 0 ||
	     soup_message_headers_get_one (req->request_headers, "Content-Type")) &&
	    !soup_message_headers_get_content_length (req->request_headers)) {
		*encoding = SOUP_ENCODING_CONTENT_LENGTH;
		soup_message_headers_set_content_length (req->request_headers,
							 req->request_body->length);
	}

	soup_message_headers_iter_init (&iter, req->request_headers);
	while (soup_message_headers_iter_next (&iter, &name, &value))
		g_string_append_printf (header, "%s: %s\r\n", name, value);
	g_string_append (header, "\r\n");
}
Ejemplo n.º 10
0
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;
}