[gnome-keyring] gcr: A rudimentary Gnupg Collection implementation.



commit 82700571fd647598dc2258b04ec0e32650f55b2e
Author: Stef Walter <stefw collabora co uk>
Date:   Fri Apr 15 19:22:12 2011 +0200

    gcr: A rudimentary Gnupg Collection implementation.
    
     * Loads basic GPG keys.
     * No splitting of UIDs yet.
     * No monitoring yet.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=647885

 .gitignore                                |    5 +
 configure.in                              |    6 +
 gcr/Makefile.am                           |    4 +
 gcr/gcr-colons.c                          |  154 +++++++++
 gcr/gcr-colons.h                          |   74 +++++
 gcr/gcr-gnupg-collection.c                |  480 +++++++++++++++++++++++++++++
 gcr/gcr-gnupg-collection.h                |   69 ++++
 gcr/gcr-gnupg-key.c                       |  249 +++++++++++++++
 gcr/gcr-gnupg-key.h                       |   75 +++++
 gcr/gcr-util.c                            |   55 ++++
 gcr/gcr-util.h                            |   41 +++
 gcr/tests/Makefile.am                     |   10 +-
 gcr/tests/files/gnupg-homedir/pubring.gpg |  Bin 0 -> 19689 bytes
 gcr/tests/files/gnupg-homedir/trustdb.gpg |  Bin 0 -> 1200 bytes
 gcr/tests/frob-gnupg-selector.c           |   79 +++++
 gcr/tests/test-colons.c                   |  193 ++++++++++++
 gcr/tests/test-gnupg-collection.c         |   82 +++++
 gcr/tests/test-gnupg-key.c                |  132 ++++++++
 gcr/tests/test-util.c                     |  112 +++++++
 19 files changed, 1818 insertions(+), 2 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index cae7040..af16c8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -106,14 +106,19 @@ run-tests
 
 /gcr/tests/frob-certificate
 /gcr/tests/frob-key
+/gcr/tests/frob-gnupg-selector
 /gcr/tests/frob-selector
 /gcr/tests/frob-unlock-options
 /gcr/tests/test-certificate
 /gcr/tests/test-certificate-chain
+/gcr/tests/test-colons
+/gcr/tests/test-gnupg-collection
+/gcr/tests/test-gnupg-key
 /gcr/tests/test-parser
 /gcr/tests/test-pkcs11-certificate
 /gcr/tests/test-simple-certificate
 /gcr/tests/test-trust
+/gcr/tests/test-util
 
 /pkcs11/gkm/tests/test-attributes
 /pkcs11/gkm/tests/test-credential
diff --git a/configure.in b/configure.in
index dc6fe19..7e145e2 100644
--- a/configure.in
+++ b/configure.in
@@ -359,6 +359,12 @@ fi
 AM_CONDITIONAL(WITH_GPG, test "$enable_gpg_agent" != "no")
 
 # --------------------------------------------------------------------
+# GPG support
+
+AC_PATH_PROGS([GNUPG], [gpg gpg2], ["gpg"])
+AC_DEFINE_UNQUOTED([GPG_EXECUTABLE], ["$GNUPG"], [Path to gpg executable.])
+
+# --------------------------------------------------------------------
 # Trusted Root Certificates Directory
 #
 
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index 532462c..b2fe94a 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -65,10 +65,13 @@ LIB_SOURCES = \
 	gcr-certificate-widget.c gcr-certificate-widget.h \
 	gcr-collection.c gcr-collection.h \
 	gcr-collection-model.c gcr-collection-model.h \
+	gcr-colons.c gcr-colons.h \
 	gcr-debug.c gcr-debug.h \
 	gcr-display-scrolled.c gcr-display-scrolled.h \
 	gcr-comparable.c gcr-comparable.h \
 	gcr-display-view.c gcr-display-view.h \
+	gcr-gnupg-collection.c gcr-gnupg-collection.h \
+	gcr-gnupg-key.c gcr-gnupg-key.h \
 	gcr-icons.c gcr-icons.h \
 	gcr-import-dialog.c gcr-import-dialog.h \
 	gcr-importer.c gcr-importer.h  \
@@ -86,6 +89,7 @@ LIB_SOURCES = \
 	gcr-types.h \
 	gcr-unlock-options.h \
 	gcr-unlock-options-widget.c gcr-unlock-options-widget.h \
+	gcr-util.c gcr-util.h \
 	gcr-viewer.c gcr-viewer.h \
 	$(BUILT_SOURCES)
 
diff --git a/gcr/gcr-colons.c b/gcr/gcr-colons.c
new file mode 100644
index 0000000..4a54934
--- /dev/null
+++ b/gcr/gcr-colons.c
@@ -0,0 +1,154 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#include "config.h"
+
+#include "gcr-colons.h"
+
+#include <string.h>
+
+#define MAX_COLUMNS 32
+
+struct _GcrColons {
+	gchar *data;
+	gpointer columns[MAX_COLUMNS];
+	guint n_columns;
+};
+
+GcrColons*
+_gcr_colons_parse (const gchar *line, gssize n_line)
+{
+	GcrColons *result;
+	gchar *p;
+
+	g_return_val_if_fail (line, NULL);
+	if (n_line < 0)
+		n_line = strlen (line);
+
+	result = g_slice_new0 (GcrColons);
+	result->data = g_strndup (line, n_line);
+
+	p = result->data;
+	for (;;) {
+		if (result->n_columns >= MAX_COLUMNS) {
+			_gcr_colons_free (result);
+			return NULL;
+		}
+
+		result->columns[result->n_columns] = p;
+		result->n_columns++;
+
+		p = strchr (p, ':');
+		if (p == NULL)
+			break;
+		p[0] = '\0';
+		p++;
+	}
+
+	return result;
+}
+
+GcrColons*
+_gcr_colons_find (GPtrArray *dataset, GQuark schema)
+{
+	guint i;
+
+	g_return_val_if_fail (dataset, NULL);
+	g_return_val_if_fail (schema, NULL);
+
+	for (i = 0; i < dataset->len; i++) {
+		if (schema == _gcr_colons_get_schema (dataset->pdata[i]))
+			return dataset->pdata[i];
+	}
+
+	return NULL;
+}
+
+gchar*
+_gcr_colons_get_string (GcrColons *colons, guint column)
+{
+	const gchar *value;
+	gchar *text;
+	gchar *converted;
+
+	g_return_val_if_fail (colons, NULL);
+
+	value = _gcr_colons_get_raw (colons, column);
+	if (!value)
+		return NULL;
+	text = g_strcompress (value);
+	if (g_utf8_validate (text, -1, NULL))
+		return text;
+
+	converted = g_convert (text, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
+	g_free (text);
+
+	if (!converted)
+		g_return_val_if_reached (NULL);
+
+	return converted;
+}
+
+const gchar*
+_gcr_colons_get_raw (GcrColons *colons, guint column)
+{
+	g_return_val_if_fail (colons, NULL);
+
+	if (column >= colons->n_columns)
+		return NULL;
+
+	return colons->columns[column];
+}
+
+void
+_gcr_colons_free (gpointer colons)
+{
+	if (!colons)
+		return;
+
+	g_free (((GcrColons*)colons)->data);
+	g_slice_free (GcrColons, colons);
+}
+
+GQuark
+_gcr_colons_get_schema (GcrColons *colons)
+{
+	const gchar *value;
+
+	value = _gcr_colons_get_raw (colons, GCR_COLONS_SCHEMA);
+	if (value != NULL)
+		return g_quark_try_string (value);
+	return 0;
+}
+
+GQuark
+_gcr_colons_get_schema_uid_quark (void)
+{
+	return g_quark_from_static_string ("uid");
+}
+
+GQuark
+_gcr_colons_get_schema_pub_quark (void)
+{
+	return g_quark_from_static_string ("pub");
+}
diff --git a/gcr/gcr-colons.h b/gcr/gcr-colons.h
new file mode 100644
index 0000000..b8a055e
--- /dev/null
+++ b/gcr/gcr-colons.h
@@ -0,0 +1,74 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#if !defined (__GCR_H_INSIDE__) && !defined (GCR_COMPILATION)
+#error "Only <gcr/gcr.h> can be included directly."
+#endif
+
+#ifndef GCR_GNUPG_COLONS_H
+#define GCR_GNUPG_COLONS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define GCR_COLONS_SCHEMA_UID  _gcr_colons_get_schema_uid_quark ()
+#define GCR_COLONS_SCHEMA_PUB  _gcr_colons_get_schema_pub_quark ()
+
+typedef enum {
+	GCR_COLONS_SCHEMA = 0
+} GcrColonColumns;
+
+typedef enum {
+	GCR_COLONS_PUB_KEYID = 4
+} GcrColonPubColumns;
+
+typedef enum {
+	GCR_COLONS_UID_NAME = 9
+} GcrColonUidColumns;
+
+typedef struct _GcrColons GcrColons;
+
+GcrColons*     _gcr_colons_parse                (const gchar *line,
+                                                 gssize n_line);
+
+void           _gcr_colons_free                 (gpointer colons);
+
+GcrColons*     _gcr_colons_find                 (GPtrArray *dataset,
+                                                 GQuark schema);
+
+gchar*         _gcr_colons_get_string           (GcrColons *colons,
+                                                 guint column);
+
+const gchar*   _gcr_colons_get_raw              (GcrColons *colons,
+                                                 guint column);
+
+GQuark         _gcr_colons_get_schema           (GcrColons *colons);
+
+GQuark         _gcr_colons_get_schema_uid_quark (void) G_GNUC_CONST;
+
+GQuark         _gcr_colons_get_schema_pub_quark (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GCR_GNUPG_COLONS_H */
diff --git a/gcr/gcr-gnupg-collection.c b/gcr/gcr-gnupg-collection.c
new file mode 100644
index 0000000..812a35f
--- /dev/null
+++ b/gcr/gcr-gnupg-collection.c
@@ -0,0 +1,480 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#include "config.h"
+
+#include "gcr-colons.h"
+#include "gcr-collection.h"
+#include "gcr-gnupg-collection.h"
+#include "gcr-gnupg-key.h"
+#include "gcr-internal.h"
+#include "gcr-util.h"
+
+#include "egg/egg-spawn.h"
+
+#include <sys/wait.h>
+#include <string.h>
+
+enum {
+	PROP_0,
+	PROP_DIRECTORY,
+	PROP_MAIN_CONTEXT
+};
+
+struct _GcrGnupgCollectionPrivate {
+	GHashTable *items;
+	gchar *directory;
+};
+
+static void _gcr_collection_iface (GcrCollectionIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GcrGnupgCollection, _gcr_gnupg_collection, G_TYPE_OBJECT,
+	G_IMPLEMENT_INTERFACE (GCR_TYPE_COLLECTION, _gcr_collection_iface)
+);
+
+/* -----------------------------------------------------------------------------
+ * OBJECT
+ */
+
+static void
+_gcr_gnupg_collection_init (GcrGnupgCollection *self)
+{
+	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_GNUPG_COLLECTION,
+	                                        GcrGnupgCollectionPrivate);
+
+	self->pv->items = g_hash_table_new_full (g_str_hash, g_str_equal,
+	                                         g_free, g_object_unref);
+}
+
+static void
+_gcr_gnupg_collection_set_property (GObject *obj, guint prop_id, const GValue *value,
+                                    GParamSpec *pspec)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
+
+	switch (prop_id) {
+	case PROP_DIRECTORY:
+		g_return_if_fail (!self->pv->directory);
+		self->pv->directory = g_value_dup_string (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+_gcr_gnupg_collection_get_property (GObject *obj, guint prop_id, GValue *value,
+                                    GParamSpec *pspec)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
+
+	switch (prop_id) {
+	case PROP_DIRECTORY:
+		g_value_set_string (value, self->pv->directory);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+static void
+_gcr_gnupg_collection_dispose (GObject *obj)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
+
+	g_hash_table_remove_all (self->pv->items);
+
+	G_OBJECT_CLASS (_gcr_gnupg_collection_parent_class)->dispose (obj);
+}
+
+static void
+_gcr_gnupg_collection_finalize (GObject *obj)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (obj);
+
+	g_assert (self->pv->items);
+	g_assert (g_hash_table_size (self->pv->items) == 0);
+	g_hash_table_destroy (self->pv->items);
+	self->pv->items = NULL;
+
+	g_free (self->pv->directory);
+	self->pv->directory = NULL;
+
+	G_OBJECT_CLASS (_gcr_gnupg_collection_parent_class)->finalize (obj);
+}
+
+static void
+_gcr_gnupg_collection_class_init (GcrGnupgCollectionClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->get_property = _gcr_gnupg_collection_get_property;
+	gobject_class->set_property = _gcr_gnupg_collection_set_property;
+	gobject_class->dispose = _gcr_gnupg_collection_dispose;
+	gobject_class->finalize = _gcr_gnupg_collection_finalize;
+
+	g_object_class_install_property (gobject_class, PROP_DIRECTORY,
+	           g_param_spec_string ("directory", "Directory", "Gnupg Directory",
+	                                NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_type_class_add_private (gobject_class, sizeof (GcrGnupgCollectionPrivate));
+	_gcr_initialize ();
+}
+
+static guint
+gcr_gnupg_collection_real_get_length (GcrCollection *coll)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (coll);
+	return g_hash_table_size (self->pv->items);
+}
+
+static GList*
+gcr_gnupg_collection_real_get_objects (GcrCollection *coll)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (coll);
+	return g_hash_table_get_keys (self->pv->items);
+}
+
+static void
+_gcr_collection_iface (GcrCollectionIface *iface)
+{
+	iface->get_length = gcr_gnupg_collection_real_get_length;
+	iface->get_objects = gcr_gnupg_collection_real_get_objects;
+}
+
+GcrCollection*
+_gcr_gnupg_collection_new (const gchar *directory)
+{
+	return g_object_new (GCR_TYPE_GNUPG_COLLECTION,
+	                     "directory", directory,
+	                     NULL);
+}
+
+typedef struct {
+	GcrGnupgCollection *collection;
+	GPtrArray *dataset;
+	guint spawn_sig;
+	guint child_sig;
+	GPid gnupg_pid;
+	GString *out_data;
+	GString *err_data;
+	GHashTable *difference;
+} GcrGnupgCollectionLoad;
+
+static void
+_gcr_gnupg_collection_load_free (gpointer data)
+{
+	GcrGnupgCollectionLoad *load = data;
+	g_assert (load);
+
+	g_ptr_array_unref (load->dataset);
+	g_string_free (load->err_data, TRUE);
+	g_string_free (load->out_data, TRUE);
+	g_hash_table_destroy (load->difference);
+	g_object_unref (load->collection);
+
+	if (load->spawn_sig)
+		g_source_remove (load->spawn_sig);
+	if (load->child_sig)
+		g_source_remove (load->child_sig);
+	if (load->gnupg_pid) {
+		kill (load->gnupg_pid, SIGTERM);
+		g_spawn_close_pid (load->gnupg_pid);
+	}
+	g_slice_free (GcrGnupgCollectionLoad, load);
+}
+
+static void
+process_dataset_as_key (GcrGnupgCollectionLoad *load)
+{
+	const gchar *keyid;
+	GPtrArray *dataset;
+	GcrGnupgKey *key;
+
+	g_assert (load->dataset->len);
+
+	dataset = load->dataset;
+	load->dataset = g_ptr_array_new_with_free_func (_gcr_colons_free);
+
+	keyid = _gcr_gnupg_key_get_keyid_for_colons (dataset);
+	if (keyid) {
+		/* Note that we've seen this keyid */
+		g_hash_table_remove (load->difference, keyid);
+
+		key = g_hash_table_lookup (load->collection->pv->items, keyid);
+
+		/* Already have this key, just update */
+		if (key) {
+			_gcr_gnupg_key_set_dataset (key, dataset);
+
+		/* Add a new key */
+		} else {
+			key = _gcr_gnupg_key_new (dataset);
+			g_hash_table_insert (load->collection->pv->items, g_strdup (keyid), key);
+			gcr_collection_emit_added (GCR_COLLECTION (load->collection), G_OBJECT (key));
+		}
+
+	} else {
+		g_warning ("parsed gnupg data had no keyid");
+	}
+
+	g_ptr_array_unref (dataset);
+}
+
+static void
+on_line_parse_output (const gchar *line, gpointer user_data)
+{
+	GcrGnupgCollectionLoad *load = user_data;
+	GcrColons *colons;
+	GQuark schema;
+
+	colons = _gcr_colons_parse (line, -1);
+	if (!colons) {
+		g_warning ("invalid gnupg output line: %s", line);
+		return;
+	}
+
+	schema = _gcr_colons_get_schema (colons);
+	if (schema == GCR_COLONS_SCHEMA_PUB) {
+		if (load->dataset->len)
+			process_dataset_as_key (load);
+		g_assert (!load->dataset->len);
+		g_ptr_array_add (load->dataset, colons);
+		colons = NULL;
+	} else if (schema == GCR_COLONS_SCHEMA_UID) {
+		if (load->dataset->len) {
+			g_ptr_array_add (load->dataset, colons);
+			colons = NULL;
+		}
+	}
+
+	if (colons != NULL)
+		_gcr_colons_free (colons);
+}
+
+
+static gboolean
+on_spawn_standard_output (int fd, gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
+	gchar buffer[1024];
+	gssize ret;
+
+	ret = egg_spawn_read_output (fd, buffer, sizeof (buffer));
+	if (ret < 0) {
+		g_warning ("couldn't read output data from prompt process");
+		return FALSE;
+	}
+
+	g_string_append_len (load->out_data, buffer, ret);
+	_gcr_util_parse_lines (load->out_data, FALSE, on_line_parse_output, load);
+
+	return (ret > 0);
+}
+
+static void
+on_line_print_error (const gchar *line, gpointer unused)
+{
+	g_printerr ("%s\n", line);
+}
+
+static gboolean
+on_spawn_standard_error (int fd, gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
+	gchar buffer[1024];
+	gssize ret;
+
+	ret = egg_spawn_read_output (fd, buffer, sizeof (buffer));
+	if (ret < 0) {
+		g_warning ("couldn't read error data from prompt process");
+		return FALSE;
+	}
+
+	g_string_append_len (load->err_data, buffer, ret);
+	_gcr_util_parse_lines (load->err_data, FALSE, on_line_print_error, NULL);
+
+	return ret > 0;
+}
+
+static void
+on_each_difference_remove (gpointer key, gpointer value, gpointer user_data)
+{
+	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (user_data);
+	GObject *object;
+
+	object = g_hash_table_lookup (self->pv->items, key);
+	if (object != NULL) {
+		g_object_ref (object);
+		g_hash_table_remove (self->pv->items, key);
+		gcr_collection_emit_removed (GCR_COLLECTION (self), object);
+		g_object_unref (object);
+	}
+}
+
+static void
+on_spawn_completed (gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
+
+	/* Should be the last call we receive */
+	g_assert (load->spawn_sig != 0);
+	load->spawn_sig = 0;
+
+	/* Print out any remaining errors */
+	_gcr_util_parse_lines (load->err_data, TRUE, on_line_print_error, NULL);
+
+	/* Process any remaining output */
+	_gcr_util_parse_lines (load->out_data, TRUE, on_line_parse_output, load);
+
+	/* Process last bit as a key, if any */
+	if (load->dataset->len)
+		process_dataset_as_key (load);
+
+	/* Remove any keys that we still have in the difference */
+	g_hash_table_foreach (load->difference, on_each_difference_remove, load->collection);
+
+	g_simple_async_result_complete (res);
+}
+
+static void
+on_child_exited (GPid pid, gint status, gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
+	gint code;
+
+	g_return_if_fail (pid == load->gnupg_pid);
+
+	g_spawn_close_pid (load->gnupg_pid);
+	load->gnupg_pid = 0;
+	load->child_sig = 0;
+
+	if (WIFEXITED (status)) {
+		code = WEXITSTATUS (status);
+		if (code != 0) {
+			g_message ("gnupg process exited with failure code: %d", code);
+		} else if (WIFSIGNALED (status)) {
+			code = WTERMSIG (status);
+			g_message ("gnupg process was killed with signal: %d", code);
+		}
+	}
+}
+
+static EggSpawnCallbacks spawn_callbacks = {
+	NULL,
+	on_spawn_standard_output,
+	on_spawn_standard_error,
+	on_spawn_completed,
+	g_object_unref,
+	NULL
+};
+
+static void
+on_each_item_add_keyid_to_difference (gpointer key, gpointer value, gpointer user_data)
+{
+	GHashTable *difference = user_data;
+	g_hash_table_insert (difference, key, key);
+}
+
+void
+_gcr_gnupg_collection_load_async (GcrGnupgCollection *self, GCancellable *cancellable,
+                                  GAsyncReadyCallback callback, gpointer user_data)
+{
+	GSimpleAsyncResult *res;
+	GcrGnupgCollectionLoad *load;
+	GError *error = NULL;
+	GPtrArray *argv;
+
+	g_return_if_fail (GCR_IS_GNUPG_COLLECTION (self));
+
+	/* Not yet implemented */
+	g_return_if_fail (cancellable == NULL);
+
+	argv = g_ptr_array_new ();
+	g_ptr_array_add (argv, GPG_EXECUTABLE);
+	g_ptr_array_add (argv, "--list-keys");
+	g_ptr_array_add (argv, "--fixed-list-mode");
+	g_ptr_array_add (argv, "--with-colons");
+	if (self->pv->directory) {
+		g_ptr_array_add (argv, "--homedir");
+		g_ptr_array_add (argv, (gpointer)self->pv->directory);
+	}
+	g_ptr_array_add (argv, NULL);
+
+	res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+	                                 _gcr_gnupg_collection_load_async);
+
+	load = g_slice_new0 (GcrGnupgCollectionLoad);
+	load->dataset = g_ptr_array_new_with_free_func (_gcr_colons_free);
+	load->err_data = g_string_sized_new (128);
+	load->out_data = g_string_sized_new (1024);
+	load->difference = g_hash_table_new (g_str_hash, g_str_equal);
+	load->collection = g_object_ref (self);
+	g_hash_table_foreach (self->pv->items, on_each_item_add_keyid_to_difference,
+	                      load->difference);
+
+	g_simple_async_result_set_op_res_gpointer (res, load,
+	                                           _gcr_gnupg_collection_load_free);
+
+	load->spawn_sig = egg_spawn_async_with_callbacks (self->pv->directory,
+	                                                  (gchar**)argv->pdata, NULL,
+	                                                  G_SPAWN_DO_NOT_REAP_CHILD,
+	                                                  &load->gnupg_pid,
+	                                                  &spawn_callbacks,
+	                                                  g_object_ref (res),
+	                                                  NULL, &error);
+
+	if (error) {
+		g_simple_async_result_set_from_error (res, error);
+		g_simple_async_result_complete_in_idle (res);
+	} else {
+		load->child_sig = g_child_watch_add_full (G_PRIORITY_DEFAULT,
+		                                          load->gnupg_pid,
+		                                          on_child_exited,
+		                                          g_object_ref (res),
+		                                          g_object_unref);
+	}
+
+	g_object_unref (res);
+}
+
+gboolean
+_gcr_gnupg_collection_load_finish (GcrGnupgCollection *self, GAsyncResult *result,
+                                   GError **error)
+{
+	g_return_val_if_fail (GCR_IS_GNUPG_COLLECTION (self), FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
+	                      _gcr_gnupg_collection_load_async), FALSE);
+
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return FALSE;
+
+	return TRUE;
+}
diff --git a/gcr/gcr-gnupg-collection.h b/gcr/gcr-gnupg-collection.h
new file mode 100644
index 0000000..b2a5d25
--- /dev/null
+++ b/gcr/gcr-gnupg-collection.h
@@ -0,0 +1,69 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#ifndef GCR_GNUPG_COLLECTION_H
+#define GCR_GNUPG_COLLECTION_H
+
+#include "gcr.h"
+#include "gcr-collection.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GCR_TYPE_GNUPG_COLLECTION               (_gcr_gnupg_collection_get_type ())
+#define GCR_GNUPG_COLLECTION(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_COLLECTION, GcrGnupgCollection))
+#define GCR_GNUPG_COLLECTION_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_COLLECTION, GcrGnupgCollectionClass))
+#define GCR_IS_GNUPG_COLLECTION(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_COLLECTION))
+#define GCR_IS_GNUPG_COLLECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_COLLECTION))
+#define GCR_GNUPG_COLLECTION_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_COLLECTION, GcrGnupgCollectionClass))
+
+typedef struct _GcrGnupgCollection GcrGnupgCollection;
+typedef struct _GcrGnupgCollectionClass GcrGnupgCollectionClass;
+typedef struct _GcrGnupgCollectionPrivate GcrGnupgCollectionPrivate;
+
+struct _GcrGnupgCollection {
+	GObject parent;
+	GcrGnupgCollectionPrivate *pv;
+};
+
+struct _GcrGnupgCollectionClass {
+	GObjectClass parent_class;
+};
+
+GType               _gcr_gnupg_collection_get_type                (void);
+
+GcrCollection*      _gcr_gnupg_collection_new                     (const gchar *directory);
+
+void                _gcr_gnupg_collection_load_async              (GcrGnupgCollection *self,
+                                                                   GCancellable *cancellable,
+                                                                   GAsyncReadyCallback callback,
+                                                                   gpointer user_data);
+
+gboolean            _gcr_gnupg_collection_load_finish             (GcrGnupgCollection *self,
+                                                                   GAsyncResult *result,
+                                                                   GError **error);
+
+G_END_DECLS
+
+#endif /* GCR_GNUPG_COLLECTION_H */
diff --git a/gcr/gcr-gnupg-key.c b/gcr/gcr-gnupg-key.c
new file mode 100644
index 0000000..225d628
--- /dev/null
+++ b/gcr/gcr-gnupg-key.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#include "config.h"
+
+#include "gcr-colons.h"
+#include "gcr-gnupg-key.h"
+
+#include "gck/gck.h"
+
+#include <gdk/gdk.h>
+#include <glib/gi18n-lib.h>
+
+enum {
+	PROP_0,
+	PROP_DATASET,
+	PROP_LABEL,
+	PROP_MARKUP,
+	PROP_DESCRIPTION,
+	PROP_KEYID
+};
+
+struct _GcrGnupgKeyPrivate {
+	GPtrArray *dataset;
+};
+
+G_DEFINE_TYPE (GcrGnupgKey, _gcr_gnupg_key, G_TYPE_OBJECT);
+
+/* -----------------------------------------------------------------------------
+ * INTERNAL
+ */
+
+static gchar *
+calculate_name (GcrGnupgKey *self)
+{
+	GcrColons* colons;
+
+	colons = _gcr_colons_find (self->pv->dataset, GCR_COLONS_SCHEMA_UID);
+	g_return_val_if_fail (colons, NULL);
+
+	return _gcr_colons_get_string (colons, GCR_COLONS_UID_NAME);
+}
+
+static gchar *
+calculate_markup (GcrGnupgKey *self)
+{
+	gchar *result = NULL;
+	gchar *name;
+
+	name = calculate_name (self);
+	if (name)
+		result = g_markup_escape_text (name, -1);
+	g_free (name);
+
+	return result;
+}
+
+static const gchar *
+calculate_keyid (GcrGnupgKey *self)
+{
+	const gchar *keyid;
+	gsize length;
+
+	keyid = _gcr_gnupg_key_get_keyid_for_colons (self->pv->dataset);
+	if (keyid == NULL)
+		return NULL;
+
+	length = strlen (keyid);
+	if (length > 8)
+		keyid += (length - 8);
+
+	return keyid;
+}
+
+static void
+_gcr_gnupg_key_init (GcrGnupgKey *self)
+{
+	self->pv = (G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_GNUPG_KEY, GcrGnupgKeyPrivate));
+}
+
+static void
+_gcr_gnupg_key_finalize (GObject *obj)
+{
+	GcrGnupgKey *self = GCR_GNUPG_KEY (obj);
+
+	if (self->pv->dataset)
+		g_ptr_array_free (self->pv->dataset, TRUE);
+	self->pv->dataset = NULL;
+
+	G_OBJECT_CLASS (_gcr_gnupg_key_parent_class)->finalize (obj);
+}
+
+static void
+_gcr_gnupg_key_set_property (GObject *obj, guint prop_id, const GValue *value,
+                             GParamSpec *pspec)
+{
+	GcrGnupgKey *self = GCR_GNUPG_KEY (obj);
+
+	switch (prop_id) {
+	case PROP_DATASET:
+		g_return_if_fail (!self->pv->dataset);
+		self->pv->dataset = g_value_dup_boxed (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+_gcr_gnupg_key_get_property (GObject *obj, guint prop_id, GValue *value,
+                             GParamSpec *pspec)
+{
+	GcrGnupgKey *self = GCR_GNUPG_KEY (obj);
+
+	switch (prop_id) {
+	case PROP_DATASET:
+		g_value_set_boxed (value, self->pv->dataset);
+		break;
+	case PROP_LABEL:
+		g_value_take_string (value, calculate_name (self));
+		break;
+	case PROP_DESCRIPTION:
+		g_value_set_string (value, _("PGP Key"));
+		break;
+	case PROP_MARKUP:
+		g_value_take_string (value, calculate_markup (self));
+		break;
+	case PROP_KEYID:
+		g_value_set_string (value, calculate_keyid (self));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+_gcr_gnupg_key_class_init (GcrGnupgKeyClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	_gcr_gnupg_key_parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (GcrGnupgKeyPrivate));
+
+	gobject_class->finalize = _gcr_gnupg_key_finalize;
+	gobject_class->set_property = _gcr_gnupg_key_set_property;
+	gobject_class->get_property = _gcr_gnupg_key_get_property;
+
+	g_object_class_install_property (gobject_class, PROP_DATASET,
+	         g_param_spec_boxed ("dataset", "Dataset", "Colon Dataset",
+	                             G_TYPE_PTR_ARRAY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_object_class_install_property (gobject_class, PROP_LABEL,
+	         g_param_spec_string ("label", "Label", "Key label",
+	                              "", G_PARAM_READABLE));
+
+	g_object_class_install_property (gobject_class, PROP_DESCRIPTION,
+	         g_param_spec_string ("description", "Description", "Description of object type",
+	                              "", G_PARAM_READABLE));
+
+	g_object_class_install_property (gobject_class, PROP_MARKUP,
+	         g_param_spec_string ("markup", "Markup", "Markup which describes key",
+	                              "", G_PARAM_READABLE));
+
+	g_object_class_install_property (gobject_class, PROP_KEYID,
+	         g_param_spec_string ("keyid", "Key ID", "Display key identifier",
+	                              "", G_PARAM_READABLE));
+}
+
+GcrGnupgKey*
+_gcr_gnupg_key_new (GPtrArray *dataset)
+{
+	return g_object_new (GCR_TYPE_GNUPG_KEY, "dataset", dataset, NULL);
+}
+
+
+GPtrArray*
+_gcr_gnupg_key_get_dataset (GcrGnupgKey *self)
+{
+	g_return_val_if_fail (GCR_IS_GNUPG_KEY (self), NULL);
+	return self->pv->dataset;
+}
+
+void
+_gcr_gnupg_key_set_dataset (GcrGnupgKey *self, GPtrArray *dataset)
+{
+	GObject *obj;
+
+	g_return_if_fail (GCR_IS_GNUPG_KEY (self));
+	g_return_if_fail (dataset);
+
+	g_ptr_array_ref (dataset);
+	if (self->pv->dataset)
+		g_ptr_array_unref (self->pv->dataset);
+	self->pv->dataset = dataset;
+
+	obj = G_OBJECT (self);
+	g_object_freeze_notify (obj);
+	g_object_notify (obj, "dataset");
+	g_object_notify (obj, "label");
+	g_object_notify (obj, "markup");
+	g_object_notify (obj, "keyid");
+	g_object_thaw_notify (obj);
+}
+
+const gchar*
+_gcr_gnupg_key_get_keyid_for_colons (GPtrArray *dataset)
+{
+	GcrColons *colons;
+
+	colons = _gcr_colons_find (dataset, GCR_COLONS_SCHEMA_PUB);
+	if (colons == NULL)
+		return NULL;
+
+	return _gcr_colons_get_raw (colons, GCR_COLONS_PUB_KEYID);
+}
+
+const GcrColumn*
+_gcr_gnupg_key_get_columns (void)
+{
+	static GcrColumn columns[] = {
+		{ "label", G_TYPE_STRING, G_TYPE_STRING, N_("Name"),
+		  GCR_COLUMN_SORTABLE },
+		{ "keyid", G_TYPE_STRING, G_TYPE_STRING, N_("Key ID"),
+		  GCR_COLUMN_SORTABLE },
+		{ NULL }
+	};
+
+	return columns;
+}
diff --git a/gcr/gcr-gnupg-key.h b/gcr/gcr-gnupg-key.h
new file mode 100644
index 0000000..b1b8765
--- /dev/null
+++ b/gcr/gcr-gnupg-key.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#if !defined (__GCR_H_INSIDE__) && !defined (GCR_COMPILATION)
+#error "Only <gcr/gcr.h> can be included directly."
+#endif
+
+#ifndef GCR_GNUPG_KEY_H
+#define GCR_GNUPG_KEY_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gcr-column.h"
+#include "gcr-types.h"
+
+G_BEGIN_DECLS
+
+#define GCR_GNUPG_KEY_COLUMNS            (_gcr_gnupg_key_get_columns ())
+#define GCR_TYPE_GNUPG_KEY               (_gcr_gnupg_key_get_type ())
+#define GCR_GNUPG_KEY(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_GNUPG_KEY, GcrGnupgKey))
+#define GCR_GNUPG_KEY_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_GNUPG_KEY, GcrGnupgKeyClass))
+#define GCR_IS_GNUPG_KEY(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_GNUPG_KEY))
+#define GCR_IS_GNUPG_KEY_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_GNUPG_KEY))
+#define GCR_GNUPG_KEY_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_GNUPG_KEY, GcrGnupgKeyClass))
+
+typedef struct _GcrGnupgKey GcrGnupgKey;
+typedef struct _GcrGnupgKeyClass GcrGnupgKeyClass;
+typedef struct _GcrGnupgKeyPrivate GcrGnupgKeyPrivate;
+
+struct _GcrGnupgKey {
+	GObject parent;
+
+	/*< private >*/
+	GcrGnupgKeyPrivate *pv;
+};
+
+struct _GcrGnupgKeyClass {
+	GObjectClass parent_class;
+};
+
+GType               _gcr_gnupg_key_get_type                      (void);
+
+const GcrColumn*    _gcr_gnupg_key_get_columns                   (void);
+
+GcrGnupgKey*        _gcr_gnupg_key_new                           (GPtrArray *dataset);
+
+GPtrArray*          _gcr_gnupg_key_get_dataset                   (GcrGnupgKey *self);
+
+void                _gcr_gnupg_key_set_dataset                   (GcrGnupgKey *self,
+                                                                  GPtrArray *dataset);
+
+const gchar*        _gcr_gnupg_key_get_keyid_for_colons          (GPtrArray *dataset);
+
+G_END_DECLS
+
+#endif /* __GCR_GNUPG_KEY_H__ */
diff --git a/gcr/gcr-util.c b/gcr/gcr-util.c
new file mode 100644
index 0000000..3929d5b
--- /dev/null
+++ b/gcr/gcr-util.c
@@ -0,0 +1,55 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#include "config.h"
+
+#include "gcr-util.h"
+
+#include <string.h>
+
+void
+_gcr_util_parse_lines (GString *string, gboolean last_line,
+                       GcrLineCallback callback, gpointer user_data)
+{
+	gchar *ptr;
+	gchar *prev;
+
+	g_return_if_fail (string);
+	g_return_if_fail (callback);
+
+	/* Print all stderr lines as messages */
+	while ((ptr = strchr (string->str, '\n')) != NULL) {
+		*ptr = '\0';
+		prev = ptr - 1;
+		if (*prev == '\r')
+			*prev = '\0';
+
+		(callback) (string->str, user_data);
+		g_string_erase (string, 0, ptr - string->str + 1);
+	}
+
+	if (last_line && string->len) {
+		(callback) (string->str, user_data);
+		g_string_erase (string, 0, string->len);
+	}
+}
diff --git a/gcr/gcr-util.h b/gcr/gcr-util.h
new file mode 100644
index 0000000..a104a8e
--- /dev/null
+++ b/gcr/gcr-util.h
@@ -0,0 +1,41 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#ifndef GCR_UTIL_H
+#define GCR_UTIL_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef void  (*GcrLineCallback)              (const gchar *line,
+                                               gpointer user_data);
+
+void           _gcr_util_parse_lines          (GString *string,
+                                               gboolean last_line,
+                                               GcrLineCallback callback,
+                                               gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __GCR_TOKEN_MANAGER_H__ */
diff --git a/gcr/tests/Makefile.am b/gcr/tests/Makefile.am
index b5ff961..9105937 100644
--- a/gcr/tests/Makefile.am
+++ b/gcr/tests/Makefile.am
@@ -5,6 +5,7 @@ INCLUDES = \
 	-DSRCDIR=$(srcdir) \
 	-DGCR_API_SUBJECT_TO_CHANGE \
 	-DGCK_API_SUBJECT_TO_CHANGE \
+	-DGCR_COMPILATION \
 	$(GLIB_CFLAGS) \
 	$(GTK_CFLAGS) \
 	$(LIBGCRYPT_CFLAGS)
@@ -19,12 +20,16 @@ LDADD = \
 	$(LIBGCRYPT_LIBS)
 
 TEST_PROGS = \
+	test-util \
+	test-simple-certificate \
 	test-certificate \
 	test-certificate-chain \
 	test-pkcs11-certificate \
-	test-simple-certificate \
 	test-trust \
-	test-parser
+	test-parser \
+	test-colons \
+	test-gnupg-key \
+	test-gnupg-collection
 
 check_PROGRAMS = $(TEST_PROGS)
 
@@ -44,6 +49,7 @@ EXTRA_DIST = \
 
 noinst_PROGRAMS = \
 	frob-certificate \
+	frob-gnupg-selector \
 	frob-key \
 	frob-selector \
 	frob-unlock-options
diff --git a/gcr/tests/files/gnupg-homedir/pubring.gpg b/gcr/tests/files/gnupg-homedir/pubring.gpg
new file mode 100644
index 0000000..a642536
Binary files /dev/null and b/gcr/tests/files/gnupg-homedir/pubring.gpg differ
diff --git a/gcr/tests/files/gnupg-homedir/secring.gpg b/gcr/tests/files/gnupg-homedir/secring.gpg
new file mode 100644
index 0000000..e69de29
diff --git a/gcr/tests/files/gnupg-homedir/trustdb.gpg b/gcr/tests/files/gnupg-homedir/trustdb.gpg
new file mode 100644
index 0000000..0377c97
Binary files /dev/null and b/gcr/tests/files/gnupg-homedir/trustdb.gpg differ
diff --git a/gcr/tests/frob-gnupg-selector.c b/gcr/tests/frob-gnupg-selector.c
new file mode 100644
index 0000000..02fd837
--- /dev/null
+++ b/gcr/tests/frob-gnupg-selector.c
@@ -0,0 +1,79 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#include "config.h"
+
+#include "gcr.h"
+#include "gcr-gnupg-collection.h"
+#include "gcr-gnupg-key.h"
+
+#include <gtk/gtk.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+static void
+on_collection_loaded (GObject *source, GAsyncResult *result, gpointer unused)
+{
+	GError *error = NULL;
+
+	_gcr_gnupg_collection_load_finish (GCR_GNUPG_COLLECTION (source), result, &error);
+	if (error) {
+		g_warning ("collection load failed: %s", error->message);
+		g_clear_error (&error);
+	}
+}
+
+int
+main (int argc, char *argv[])
+{
+	GcrCollection *collection;
+	GcrSelector *selector;
+	GtkDialog *dialog;
+
+	gtk_init (&argc, &argv);
+
+	dialog = GTK_DIALOG (gtk_dialog_new ());
+	g_object_ref_sink (dialog);
+
+	collection = _gcr_gnupg_collection_new (NULL);
+	selector = gcr_selector_new (collection, GCR_GNUPG_KEY_COLUMNS, GCR_SELECTOR_MODE_MULTIPLE);
+
+	gtk_widget_show (GTK_WIDGET (selector));
+	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (dialog)), GTK_WIDGET (selector));
+
+	_gcr_gnupg_collection_load_async (GCR_GNUPG_COLLECTION (collection), NULL,
+	                                  on_collection_loaded, NULL);
+
+	gtk_window_set_default_size (GTK_WINDOW (dialog), 550, 400);
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 20);
+
+	g_object_unref (collection);
+
+	gtk_dialog_run (dialog);
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+	g_object_unref (dialog);
+
+	return 0;
+}
diff --git a/gcr/tests/test-colons.c b/gcr/tests/test-colons.c
new file mode 100644
index 0000000..1bca077
--- /dev/null
+++ b/gcr/tests/test-colons.c
@@ -0,0 +1,193 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2011 Collabora Ltd.
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring Library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: Stef Walter <stefw collabora co uk>
+*/
+
+#include "config.h"
+
+#include "gcr/gcr-colons.h"
+
+#include "egg/egg-testing.h"
+
+#include <glib.h>
+
+typedef struct {
+	GcrColons *colons;
+} Test;
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+	test->colons = _gcr_colons_parse ("one:tab\\there::four:f\xfc""nf:", -1);
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+	_gcr_colons_free (test->colons);
+}
+
+static void
+test_parse (void)
+{
+	GcrColons *colons;
+
+	colons = _gcr_colons_parse ("one:two::four::six", -1);
+	g_assert (colons);
+
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 0), ==, "one");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 1), ==, "two");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 2), ==, "");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 3), ==, "four");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 4), ==, "");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 5), ==, "six");
+	g_assert (_gcr_colons_get_raw (colons, 6) == NULL);
+
+	_gcr_colons_free (colons);
+}
+
+static void
+test_parse_part (void)
+{
+	GcrColons *colons;
+
+	colons = _gcr_colons_parse ("one:two::four::six", 8);
+	g_assert (colons);
+
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 0), ==, "one");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 1), ==, "two");
+	g_assert_cmpstr (_gcr_colons_get_raw (colons, 2), ==, "");
+	g_assert (_gcr_colons_get_raw (colons, 3) == NULL);
+
+	_gcr_colons_free (colons);
+}
+
+static void
+test_parse_too_long (void)
+{
+	GcrColons *colons;
+
+	/* Too many columns */
+	colons = _gcr_colons_parse (":::::::::::::::::::::::::::::::::::::::::::::::::::::", -1);
+	g_assert (colons == NULL);
+}
+
+static void
+test_find (void)
+{
+	GcrColons *uid, *pub, *one, *check;
+	GPtrArray *dataset;
+
+	dataset = g_ptr_array_new_with_free_func (_gcr_colons_free);
+
+	one = _gcr_colons_parse ("one:two::four::six", -1);
+	g_ptr_array_add (dataset, one);
+	pub = _gcr_colons_parse ("pub:two", -1);
+	g_ptr_array_add (dataset, pub);
+	uid = _gcr_colons_parse ("uid:two", -1);
+	g_ptr_array_add (dataset, uid);
+
+	check = _gcr_colons_find (dataset, GCR_COLONS_SCHEMA_PUB);
+	g_assert (check == pub);
+
+	check = _gcr_colons_find (dataset, GCR_COLONS_SCHEMA_UID);
+	g_assert (check == uid);
+
+	g_ptr_array_unref (dataset);
+}
+
+static void
+test_get_string (Test *test, gconstpointer unused)
+{
+	gchar *value = _gcr_colons_get_string (test->colons, 1);
+	g_assert (value);
+
+	g_assert_cmpstr (value, ==, "tab\there");
+	g_free (value);
+}
+
+static void
+test_get_string_null (Test *test, gconstpointer unused)
+{
+	gchar *value = _gcr_colons_get_string (test->colons, 35);
+	g_assert (value == NULL);
+}
+
+static void
+test_get_string_latin1 (Test *test, gconstpointer unused)
+{
+	gchar *value = _gcr_colons_get_string (test->colons, 4);
+	g_assert (value);
+
+	g_assert_cmpstr (value, ==, "f\xc3\xbc""nf");
+	g_assert (g_utf8_validate (value, -1, NULL));
+	g_free (value);
+}
+
+static void
+test_free_null (void)
+{
+	_gcr_colons_free (NULL);
+}
+
+static void
+test_get_schema (Test *test, gconstpointer unused)
+{
+	GQuark schema;
+	GQuark check;
+
+	/* Initialize this quark */
+	check = g_quark_from_static_string ("one");
+
+	schema = _gcr_colons_get_schema (test->colons);
+	g_assert (check == schema);
+	g_assert_cmpstr (g_quark_to_string (schema), ==, "one");
+}
+
+static void
+test_schemas (void)
+{
+	GQuark check;
+
+	check = _gcr_colons_get_schema_uid_quark ();
+	g_assert_cmpstr (g_quark_to_string (check), ==, "uid");
+
+	check = _gcr_colons_get_schema_pub_quark ();
+	g_assert_cmpstr (g_quark_to_string (check), ==, "pub");
+}
+
+int
+main (int argc, char **argv)
+{
+	g_test_init (&argc, &argv, NULL);
+
+	g_test_add_func ("/gcr/colons/parse", test_parse);
+	g_test_add_func ("/gcr/colons/parse_part", test_parse_part);
+	g_test_add_func ("/gcr/colons/parse_too_long", test_parse_too_long);
+	g_test_add_func ("/gcr/colons/free_null", test_free_null);
+	g_test_add_func ("/gcr/colons/schemas", test_schemas);
+	g_test_add_func ("/gcr/colons/find", test_find);
+	g_test_add ("/gcr/colons/get_string", Test, NULL, setup, test_get_string, teardown);
+	g_test_add ("/gcr/colons/get_string_null", Test, NULL, setup, test_get_string_null, teardown);
+	g_test_add ("/gcr/colons/get_string_latin1", Test, NULL, setup, test_get_string_latin1, teardown);
+	g_test_add ("/gcr/colons/get_schema", Test, NULL, setup, test_get_schema, teardown);
+
+	return g_test_run ();
+}
diff --git a/gcr/tests/test-gnupg-collection.c b/gcr/tests/test-gnupg-collection.c
new file mode 100644
index 0000000..d156a82
--- /dev/null
+++ b/gcr/tests/test-gnupg-collection.c
@@ -0,0 +1,82 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2010 Collabora Ltd
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring Library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: Stef Walter <stefw collabora co uk>
+*/
+
+#include "config.h"
+
+#include "gcr/gcr.h"
+#include "gcr/gcr-gnupg-collection.h"
+
+#include "egg/egg-testing.h"
+
+#include <glib.h>
+
+#include <errno.h>
+#include <string.h>
+
+#if 0
+typedef struct {
+
+} Test;
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+
+}
+#endif
+
+static void
+test_create (void)
+{
+	GcrCollection *collection;
+
+	collection = _gcr_gnupg_collection_new ("files/gnupg-homedir/");
+
+	g_object_unref (collection);
+}
+
+
+int
+main (int argc, char **argv)
+{
+	const gchar *srcdir;
+
+	g_type_init ();
+	g_test_init (&argc, &argv, NULL);
+
+	srcdir = g_getenv ("SRCDIR");
+	if (srcdir && chdir (srcdir) < 0)
+		g_error ("couldn't change directory to: %s: %s", srcdir, g_strerror (errno));
+
+	g_test_add_func ("/gcr/gnupg-collection/create", test_create);
+#if 0
+	g_test_add ("/gcr/certificate/issuer_dn", Test, NULL, setup, test_issuer_dn, teardown);
+#endif
+
+	return g_test_run ();
+}
diff --git a/gcr/tests/test-gnupg-key.c b/gcr/tests/test-gnupg-key.c
new file mode 100644
index 0000000..5f1efac
--- /dev/null
+++ b/gcr/tests/test-gnupg-key.c
@@ -0,0 +1,132 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2011 Collabora Ltd
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring Library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: Stef Walter <stefw collabora co uk>
+*/
+
+#include "config.h"
+
+#include "gcr/gcr-colons.h"
+#include "gcr/gcr-gnupg-key.h"
+
+#include "egg/egg-testing.h"
+
+#include <glib.h>
+
+#include <errno.h>
+#include <string.h>
+
+typedef struct {
+	GPtrArray *dataset;
+	GcrGnupgKey *key;
+} Test;
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+	GPtrArray *dataset;
+
+	dataset = g_ptr_array_new_with_free_func (_gcr_colons_free);
+	g_ptr_array_add (dataset, _gcr_colons_parse ("pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("fpr:::::::::ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("uid:f::::::::Werner Koch <wk g10code com>:\n", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("uid:f::::::::Werner Koch <wk gnupg org>:\n", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("sub:f:1536:16:06AD222CADF6A6E1:919537416:1036177416:::::e:\n", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("fpr:::::::::CF8BCC4B18DE08FCD8A1615906AD222CADF6A6E1:\n", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("sub:r:1536:20:5CE086B5B5A18FF4:899817788:1025961788:::::esc:\n", -1));
+	g_ptr_array_add (dataset, _gcr_colons_parse ("fpr:::::::::AB059359A3B81F410FCFF97F5CE086B5B5A18FF4:", -1));
+
+	test->key = _gcr_gnupg_key_new (dataset);
+	test->dataset = dataset;
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+	g_object_unref (test->key);
+	g_ptr_array_unref (test->dataset);
+}
+
+static void
+test_label (Test *test, gconstpointer unused)
+{
+	gchar *label;
+
+	g_object_get (test->key, "label", &label, NULL);
+	g_assert_cmpstr (label, ==, "Werner Koch <wk g10code com>");
+
+	g_free (label);
+}
+
+static void
+test_markup (Test *test, gconstpointer unused)
+{
+	gchar *markup;
+
+	g_object_get (test->key, "markup", &markup, NULL);
+	g_assert_cmpstr (markup, ==, "Werner Koch &lt;wk g10code com&gt;");
+
+	g_free (markup);
+}
+
+static void
+test_description (Test *test, gconstpointer unused)
+{
+	gchar *description;
+
+	g_object_get (test->key, "description", &description, NULL);
+	g_assert_cmpstr (description, ==, "PGP Key");
+
+	g_free (description);
+}
+
+static void
+test_dataset (Test *test, gconstpointer unused)
+{
+	GPtrArray *dataset;
+
+	g_object_get (test->key, "dataset", &dataset, NULL);
+	g_assert (dataset == test->dataset);
+
+	g_ptr_array_unref (dataset);
+}
+
+static void
+test_keyid_for_colons (Test *test, gconstpointer unused)
+{
+	const gchar *keyid;
+
+	keyid = _gcr_gnupg_key_get_keyid_for_colons (test->dataset);
+	g_assert_cmpstr (keyid, ==, "6C7EE1B8621CC013");
+}
+
+int
+main (int argc, char **argv)
+{
+	g_type_init ();
+	g_test_init (&argc, &argv, NULL);
+
+	g_test_add ("/gcr/gnupg-key/label", Test, NULL, setup, test_label, teardown);
+	g_test_add ("/gcr/gnupg-key/description", Test, NULL, setup, test_description, teardown);
+	g_test_add ("/gcr/gnupg-key/markup", Test, NULL, setup, test_markup, teardown);
+	g_test_add ("/gcr/gnupg-key/dataset", Test, NULL, setup, test_dataset, teardown);
+	g_test_add ("/gcr/gnupg-key/keyid_for_colons", Test, NULL, setup, test_keyid_for_colons, teardown);
+
+	return g_test_run ();
+}
diff --git a/gcr/tests/test-util.c b/gcr/tests/test-util.c
new file mode 100644
index 0000000..015424f
--- /dev/null
+++ b/gcr/tests/test-util.c
@@ -0,0 +1,112 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2011 Collabora Ltd.
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring Library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: Stef Walter <stefw collabora co uk>
+*/
+
+#include "config.h"
+
+#include "gcr.h"
+#include "gcr/gcr-util.h"
+
+#include <errno.h>
+
+static void
+on_line_parsed_match_template (const gchar *line, gpointer user_data)
+{
+	const gchar ***matching = user_data;
+
+	g_assert (matching);
+	g_assert (*matching);
+
+	/* Must be another line to match */
+	g_assert ((*matching)[0]);
+
+	/* Match this line against expected, and increment to next */
+	g_assert_cmpstr ((*matching)[0], ==, line);
+	(*matching)++;
+}
+
+static void
+test_parse_lines (void)
+{
+	GString *string = g_string_new ("first line\nsecond line\n\nlast line");
+	const gchar *matches[] = { "first line", "second line", "", NULL };
+	const gchar **matching = matches;
+
+	_gcr_util_parse_lines (string, FALSE, on_line_parsed_match_template, &matching);
+
+	/* All lines should have matched */
+	g_assert (*matching == NULL);
+
+	/* The last line should still be here */
+	g_assert_cmpstr (string->str, ==, "last line");
+	g_string_free (string, TRUE);
+}
+
+static void
+test_parse_lines_and_last (void)
+{
+	GString *string = g_string_new ("first line\nsecond line\n\nlast line");
+	const gchar *matches[] = { "first line", "second line", "", "last line", NULL };
+	const gchar **matching = matches;
+
+	_gcr_util_parse_lines (string, FALSE, on_line_parsed_match_template, &matching);
+	_gcr_util_parse_lines (string, TRUE, on_line_parsed_match_template, &matching);
+
+	/* All lines should have matched */
+	g_assert (*matching == NULL);
+
+	/* No more data */
+	g_assert_cmpstr (string->str, ==, "");
+	g_assert_cmpuint (string->len, ==, 0);
+	g_string_free (string, TRUE);
+}
+
+static void
+test_parse_lines_dos (void)
+{
+	GString *string = g_string_new ("first line\r\nsecond line\r\n\r\nlast line");
+	const gchar *matches[] = { "first line", "second line", "", "last line", NULL };
+	const gchar **matching = matches;
+
+	_gcr_util_parse_lines (string, FALSE, on_line_parsed_match_template, &matching);
+	_gcr_util_parse_lines (string, TRUE, on_line_parsed_match_template, &matching);
+
+	/* All lines should have matched */
+	g_assert (*matching == NULL);
+
+	/* No more data */
+	g_assert_cmpstr (string->str, ==, "");
+	g_assert_cmpuint (string->len, ==, 0);
+	g_string_free (string, TRUE);
+}
+
+int
+main (int argc, char **argv)
+{
+	g_type_init ();
+	g_test_init (&argc, &argv, NULL);
+
+	g_test_add_func ("/gcr/util/test_parse_lines", test_parse_lines);
+	g_test_add_func ("/gcr/util/test_parse_lines_and_last", test_parse_lines_and_last);
+	g_test_add_func ("/gcr/util/test_parse_lines_dos", test_parse_lines_dos);
+
+	return g_test_run ();
+}



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