[gnome-software/wip/william/cherry-pick-4: 1/23] Add utility module for snapd



commit c4417a3906b5ab38fb02372975b6f167cd244d76
Author: Robert Ancell <robert ancell canonical com>
Date:   Thu Jun 2 12:58:29 2016 -0400

    Add utility module for snapd
    
    Authored by Robert Ancell <robert ancell canonical com> and William Hua
    <william hua canonical com>.

 src/plugins/gs-ubuntu-snapd.c |  277 +++++++++++++++++++++++++++++++++++++++++
 src/plugins/gs-ubuntu-snapd.h |   41 ++++++
 2 files changed, 318 insertions(+), 0 deletions(-)
---
diff --git a/src/plugins/gs-ubuntu-snapd.c b/src/plugins/gs-ubuntu-snapd.c
new file mode 100644
index 0000000..32fb2bc
--- /dev/null
+++ b/src/plugins/gs-ubuntu-snapd.c
@@ -0,0 +1,277 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Canonical Ltd
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <gs-plugin.h>
+#include <libsoup/soup.h>
+#include <gio/gunixsocketaddress.h>
+#include "gs-ubuntu-snapd.h"
+#include "gs-ubuntuone.h"
+
+#define SNAPD_SOCKET_PATH "/run/snapd.socket"
+
+// snapd API documentation is at https://github.com/ubuntu-core/snappy/blob/master/docs/rest.md
+
+static GSocket *
+open_snapd_socket (GError **error)
+{
+       GSocket *socket;
+       g_autoptr(GSocketAddress) address = NULL;
+       g_autoptr(GError) sub_error = NULL;
+
+       socket = g_socket_new (G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, 
&sub_error);
+       if (!socket) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "Unable to open snapd socket: %s", sub_error->message);
+               return NULL;
+       }
+       address = g_unix_socket_address_new (SNAPD_SOCKET_PATH);
+       if (!g_socket_connect (socket, address, NULL, &sub_error)) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "Unable to connect snapd socket: %s", sub_error->message);
+               g_object_unref (socket);
+               return NULL;
+       }
+
+       return socket;
+}
+
+static gboolean
+read_from_snapd (GSocket *socket, gchar *buffer, gsize buffer_length, gsize *read_offset, GError **error)
+{
+       gssize n_read;
+       n_read = g_socket_receive (socket, buffer + *read_offset, buffer_length - *read_offset, NULL, error);
+       if (n_read < 0)
+               return FALSE;
+       *read_offset += n_read;
+       buffer[*read_offset] = '\0';
+
+       return TRUE;
+}
+
+gboolean
+send_snapd_request (const gchar  *method,
+                   const gchar  *path,
+                   const gchar  *content,
+                   gboolean      authenticate,
+                   GVariant     *macaroon,
+                   gboolean      retry_after_login,
+                   GVariant    **out_macaroon,
+                   guint        *status_code,
+                   gchar       **reason_phrase,
+                   gchar       **response_type,
+                   gchar       **response,
+                   gsize        *response_length,
+                   GError      **error)
+{
+       g_autoptr (GSocket) socket = NULL;
+       g_autoptr (GString) request = NULL;
+       gssize n_written;
+       gsize max_data_length = 65535, data_length = 0, header_length;
+       gchar data[max_data_length + 1], *body = NULL;
+       g_autoptr (SoupMessageHeaders) headers = NULL;
+       g_autoptr(GVariant) auto_macaroon = NULL;
+       gsize chunk_length, n_required;
+       gchar *chunk_start = NULL;
+       const gchar *root;
+       const gchar *discharge;
+       GVariantIter *iter;
+       guint code;
+       gboolean ret;
+
+       if (macaroon == NULL && authenticate) {
+               auto_macaroon = gs_ubuntuone_get_macaroon (TRUE, FALSE, NULL);
+               macaroon = auto_macaroon;
+       }
+
+       // 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 (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) {
+               g_variant_get (macaroon, "(&sas)", &root, &iter);
+               g_string_append_printf (request, "Authorization: Macaroon root=\"%s\"", root);
+
+               while (g_variant_iter_next (iter, "&s", &discharge))
+                       g_string_append_printf (request, ",discharge=\"%s\"", discharge);
+
+               g_variant_iter_free (iter);
+               g_string_append (request, "\r\n");
+       }
+       if (content)
+               g_string_append_printf (request, "Content-Length: %zi\r\n", strlen (content));
+       g_string_append (request, "\r\n");
+       if (content)
+               g_string_append (request, content);
+
+       if (g_strcmp0 (g_getenv ("GNOME_SOFTWARE_SNAPPY"), "debug") == 0)
+               g_print ("===== begin snapd request =====\n%s\n===== end snapd request =====\n", 
request->str);
+
+       /* Send HTTP request */
+       n_written = g_socket_send (socket, request->str, request->len, NULL, error);
+       if (n_written < 0)
+               return FALSE;
+
+       /* Read HTTP headers */
+       while (data_length < max_data_length && !body) {
+               if (!read_from_snapd (socket, data, max_data_length, &data_length, error))
+                       return FALSE;
+               body = strstr (data, "\r\n\r\n");
+       }
+       if (!body) {
+               g_set_error_literal (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_FAILED,
+                                    "Unable to find header separator in snapd response");
+               return FALSE;
+       }
+
+       /* Body starts after header divider */
+       body += 4;
+       header_length = body - data;
+
+       /* Parse headers */
+       headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
+       if (!soup_headers_parse_response (data, header_length, headers, NULL, &code, reason_phrase)) {
+               g_set_error_literal (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_FAILED,
+                                    "snapd response HTTP headers not parseable");
+               return FALSE;
+       }
+
+       if (status_code != NULL)
+               *status_code = code;
+
+       if ((code == 401 || code == 403) && retry_after_login) {
+               g_socket_close (socket, NULL);
+
+               gs_ubuntuone_clear_macaroon ();
+
+               macaroon = gs_ubuntuone_get_macaroon (FALSE, TRUE, NULL);
+
+               if (macaroon == NULL) {
+                       g_set_error_literal (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+                                            "failed to authenticate");
+                       return FALSE;
+               }
+
+               ret = send_snapd_request (method,
+                                         path,
+                                         content,
+                                         TRUE,
+                                         macaroon,
+                                         FALSE,
+                                         NULL,
+                                         status_code,
+                                         reason_phrase,
+                                         response_type,
+                                         response,
+                                         response_length,
+                                         error);
+
+               if (ret && out_macaroon != NULL) {
+                       *out_macaroon = macaroon;
+               } else {
+                       g_variant_unref (macaroon);
+               }
+
+               return ret;
+       }
+
+       /* Work out how much data to follow */
+       if (g_strcmp0 (soup_message_headers_get_one (headers, "Transfer-Encoding"), "chunked") == 0) {
+               while (data_length < max_data_length) {
+                       chunk_start = strstr (body, "\r\n");
+                       if (chunk_start)
+                               break;
+                       if (!read_from_snapd (socket, data, max_data_length, &data_length, error))
+                               return FALSE;
+               }
+               if (!chunk_start) {
+                       g_set_error_literal (error,
+                                            GS_PLUGIN_ERROR,
+                                            GS_PLUGIN_ERROR_FAILED,
+                                            "Unable to find chunk header in snapd response");
+                       return FALSE;
+               }
+               chunk_length = strtoul (body, NULL, 16);
+               chunk_start += 2;
+               // FIXME: Support multiple chunks
+       }
+       else {
+               const gchar *value;
+               value = soup_message_headers_get_one (headers, "Content-Length");
+               if (!value) {
+                       g_set_error_literal (error,
+                                            GS_PLUGIN_ERROR,
+                                            GS_PLUGIN_ERROR_FAILED,
+                                            "Unable to determine content length of snapd response");
+                       return FALSE;
+               }
+               chunk_length = strtoul (value, NULL, 10);
+               chunk_start = body;
+       }
+
+       /* Check if enough space to read chunk */
+       n_required = (chunk_start - data) + chunk_length;
+       if (n_required > max_data_length) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "Not enough space for snapd response, require %zi octets, have %zi", n_required, 
max_data_length);
+               return FALSE;
+       }
+
+       /* Read chunk content */
+       while (data_length < n_required)
+               if (!read_from_snapd (socket, data, n_required - data_length, &data_length, error))
+                       return FALSE;
+
+       if (out_macaroon != NULL)
+               *out_macaroon = g_variant_ref (macaroon);
+       if (response_type)
+               *response_type = g_strdup (soup_message_headers_get_one (headers, "Content-Type"));
+       if (response) {
+               *response = g_malloc (chunk_length + 2);
+               memcpy (*response, chunk_start, chunk_length + 1);
+               (*response)[chunk_length + 1] = '\0';
+
+               if (g_strcmp0 (g_getenv ("GNOME_SOFTWARE_SNAPPY"), "debug") == 0)
+                       g_print ("===== begin snapd response =====\nStatus %u\n%s\n===== end snapd response 
=====\n", code, *response);
+       }
+       if (response_length)
+               *response_length = chunk_length;
+
+       return TRUE;
+}
diff --git a/src/plugins/gs-ubuntu-snapd.h b/src/plugins/gs-ubuntu-snapd.h
new file mode 100644
index 0000000..cbc1e1a
--- /dev/null
+++ b/src/plugins/gs-ubuntu-snapd.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Canonical Ltd
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GS_UBUNTU_SNAPD_H__
+#define __GS_UBUNTU_SNAPD_H__
+
+#include <gio/gio.h>
+
+gboolean send_snapd_request (const gchar  *method,
+                            const gchar  *path,
+                            const gchar  *content,
+                            gboolean      authenticate,
+                            GVariant     *macaroon,
+                            gboolean      retry_after_login,
+                            GVariant    **out_macaroon,
+                            guint        *status_code,
+                            gchar       **reason_phrase,
+                            gchar       **response_type,
+                            gchar       **response,
+                            gsize        *response_length,
+                            GError      **error);
+
+#endif /* __GS_UBUNTU_SNAPD_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]