[glom/gtkmm4v4] libglom: Copy in the libepc source code.



commit 1c8939ebdc626b812abb4fe40db391ad1c313e27
Author: Murray Cumming <murrayc murrayc com>
Date:   Tue Oct 31 23:06:23 2017 +0100

    libglom: Copy in the libepc source code.
    
    Intead of using it as a shared library. This avoids the need
    for distro to package it, though its only used by Glom now,
    and avoids the need for me to maintain the libepc-ui code against
    the changing GTK+ 4 API, though Glom doesn't use libepc-ui.
    
    This does not include the libepc-ui code from libepc.

 configure.ac                          |    5 +-
 glom/appwindow.cc                     |    2 +-
 glom/dialog_existing_or_new.cc        |    2 +-
 glom/dialog_existing_or_new.h         |    2 +-
 glom/libglom/filelist.am              |   26 +-
 glom/libglom/libepc/.gitignore        |    7 +
 glom/libglom/libepc/consumer.c        | 1249 +++++++++++++++
 glom/libglom/libepc/consumer.h        |  120 ++
 glom/libglom/libepc/contents.c        |  359 +++++
 glom/libglom/libepc/contents.h        |   81 +
 glom/libglom/libepc/dispatcher.c      | 1109 +++++++++++++
 glom/libglom/libepc/dispatcher.h      |  119 ++
 glom/libglom/libepc/enums.c           |  258 +++
 glom/libglom/libepc/enums.h           |   44 +
 glom/libglom/libepc/marshal.c         |  202 +++
 glom/libglom/libepc/marshal.h         |   49 +
 glom/libglom/libepc/marshal.list      |    4 +
 glom/libglom/libepc/protocol.c        |  173 ++
 glom/libglom/libepc/protocol.h        |   59 +
 glom/libglom/libepc/publisher.c       | 2814 +++++++++++++++++++++++++++++++++
 glom/libglom/libepc/publisher.h       |  224 +++
 glom/libglom/libepc/service-info.c    |  319 ++++
 glom/libglom/libepc/service-info.h    |   81 +
 glom/libglom/libepc/service-monitor.c |  592 +++++++
 glom/libglom/libepc/service-monitor.h |   97 ++
 glom/libglom/libepc/service-type.c    |  233 +++
 glom/libglom/libepc/service-type.h    |   52 +
 glom/libglom/libepc/shell.c           |  616 +++++++
 glom/libglom/libepc/shell.h           |  119 ++
 glom/libglom/libepc/tls.c             |  665 ++++++++
 glom/libglom/libepc/tls.h             |   86 +
 31 files changed, 9760 insertions(+), 8 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index ebc2e8c..e1cd015 100644
--- a/configure.ac
+++ b/configure.ac
@@ -163,10 +163,7 @@ AS_IF([test "x$glom_enable_postgresql" = xyes],
 
 
 # Libraries used by libglom:
-REQUIRED_LIBGLOM_LIBS='giomm-2.54 >= 2.47.4 libxml++-4.0 >= 3.0.0 libxslt >= 1.1.10 pygobject-3.0 >= 2.29.0 
libgdamm-6.0 >= 4.99.10 libgda-6.0 >= 5.2.1 libgda-postgres-6.0 libgda-postgres-6.0 libgda-mysql-6.0 
libarchive >= 3.0'
-
-AS_IF([test "x$glom_host_win32" != xyes],
-      [REQUIRED_LIBGLOM_LIBS="$REQUIRED_LIBGLOM_LIBS libepc-2.0 >= 0.4.0"])
+REQUIRED_LIBGLOM_LIBS='giomm-2.56 >= 2.47.4 libxml++-4.0 >= 3.0.0 libxslt >= 1.1.10 pygobject-3.0 >= 2.29.0 
libgdamm-6.0 >= 4.99.10 libgda-6.0 >= 5.2.1 libgda-postgres-6.0 libgda-postgres-6.0 libgda-mysql-6.0 
libarchive >= 3.0 libsoup-2.4 >= 2.3'
 
 # Libraries used by Glom:
 REQUIRED_GLOM_LIBS="$REQUIRED_LIBGLOM_LIBS gtkmm-4.0 >= 3.18.0 goocanvasmm-3.0 >= 1.90.11"
diff --git a/glom/appwindow.cc b/glom/appwindow.cc
index 2a98e2e..64d3345 100644
--- a/glom/appwindow.cc
+++ b/glom/appwindow.cc
@@ -53,7 +53,7 @@
 #include <sstream> //For stringstream.
 
 #ifndef G_OS_WIN32
-#include <libepc/consumer.h>
+#include <libglom/libepc/consumer.h>
 #include <libsoup/soup-status.h>
 #endif // !G_OS_WIN32
 
diff --git a/glom/dialog_existing_or_new.cc b/glom/dialog_existing_or_new.cc
index 9f2406a..3a8e212 100644
--- a/glom/dialog_existing_or_new.cc
+++ b/glom/dialog_existing_or_new.cc
@@ -36,7 +36,7 @@
 #include <glibmm/fileutils.h>
 #include <glib.h>
 #else
-#include <libepc/service-type.h>
+#include <libglom/libepc/service-type.h>
 #endif
 
 #include <iostream>
diff --git a/glom/dialog_existing_or_new.h b/glom/dialog_existing_or_new.h
index e6db38a..fefff6e 100644
--- a/glom/dialog_existing_or_new.h
+++ b/glom/dialog_existing_or_new.h
@@ -36,7 +36,7 @@
 #include <gtkmm/builder.h>
 
 #ifndef G_OS_WIN32
-# include <libepc/service-monitor.h>
+# include <libglom/libepc/service-monitor.h>
 #endif
 
 namespace Glom
diff --git a/glom/libglom/filelist.am b/glom/libglom/filelist.am
index 6243b49..077167e 100644
--- a/glom/libglom/filelist.am
+++ b/glom/libglom/filelist.am
@@ -199,7 +199,31 @@ libglom_sources =                                                  \
        glom/libglom/connectionpool_backends/postgres.cc                \
        glom/libglom/connectionpool_backends/postgres.h                 \
        glom/libglom/connectionpool_backends/postgres_central.cc        \
-       glom/libglom/connectionpool_backends/postgres_central.h
+       glom/libglom/connectionpool_backends/postgres_central.h \
+        glom/libglom/libepc/consumer.c \
+        glom/libglom/libepc/consumer.h \
+        glom/libglom/libepc/contents.c \
+        glom/libglom/libepc/contents.h \
+        glom/libglom/libepc/dispatcher.c \
+        glom/libglom/libepc/dispatcher.h \
+        glom/libglom/libepc/enums.c \
+        glom/libglom/libepc/enums.h \
+        glom/libglom/libepc/protocol.c \
+        glom/libglom/libepc/protocol.h \
+        glom/libglom/libepc/publisher.c \
+        glom/libglom/libepc/publisher.h \
+        glom/libglom/libepc/service-info.c \
+        glom/libglom/libepc/service-info.h \
+        glom/libglom/libepc/service-monitor.c \
+        glom/libglom/libepc/service-monitor.h \
+        glom/libglom/libepc/service-type.c \
+        glom/libglom/libepc/service-type.h \
+        glom/libglom/libepc/shell.c \
+        glom/libglom/libepc/shell.h \
+        glom/libglom/libepc/tls.c \
+        glom/libglom/libepc/tls.h
+
+
 
 if !GLOM_ENABLE_CLIENT_ONLY
 libglom_sources +=                                             \
diff --git a/glom/libglom/libepc/.gitignore b/glom/libglom/libepc/.gitignore
new file mode 100644
index 0000000..0a8a653
--- /dev/null
+++ b/glom/libglom/libepc/.gitignore
@@ -0,0 +1,7 @@
+*.[ao]
+*.l[ao]
+*.l[ao]T
+*.stamp
+*.tmp
+.dirstamp
+
diff --git a/glom/libglom/libepc/consumer.c b/glom/libglom/libepc/consumer.c
new file mode 100644
index 0000000..22786c1
--- /dev/null
+++ b/glom/libglom/libepc/consumer.c
@@ -0,0 +1,1249 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "libglom/libepc/consumer.h"
+
+#include "libglom/libepc/enums.h"
+#include "libglom/libepc/marshal.h"
+#include "libglom/libepc/service-monitor.h"
+#include "libglom/libepc/shell.h"
+
+#include <glib/gi18n-lib.h>
+#include <libsoup/soup.h>
+#include <string.h>
+
+/**
+ * SECTION:consumer
+ * @short_description: lookup published values
+ * @see_also: #EpcPublisher
+ * @include: libepc/consumer.h
+ * @stability: Unstable
+ *
+ * The #EpcConsumer object is used to lookup values published by an
+ * #EpcPublisher service. Currently HTTP is used for communication.
+ * To find a publisher, use DNS-SD (also known as ZeroConf) to
+ * list #EPC_PUBLISHER_SERVICE_TYPE services.
+ *
+ * <example id="lookup-value">
+ *  <title>Lookup a value</title>
+ *  <programlisting>
+ *   service_name = choose_recently_used_service ();
+ *
+ *   if (service_name)
+ *     consumer = epc_consumer_new_for_name (service_name);
+ *   else
+ *     consumer = epc_consumer_new (your_app_find_service ());
+ *
+ *   value = epc_consumer_lookup (consumer, "glom-settings", NULL, &error);
+ *   g_object_unref (consumer);
+ *
+ *   your_app_consume_value (value);
+ *   g_free (value);
+ *  </programlisting>
+ * </example>
+ *
+ * <example id="find-publisher">
+ *  <title>Find a publisher</title>
+ *  <programlisting>
+ *   dialog = aui_service_dialog_new ("Choose a Service", main_window,
+ *                                    GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ *                                    GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT,
+ *                                    NULL);
+ *
+ *   aui_service_dialog_set_browse_service_types (AUI_SERVICE_DIALOG (dialog),
+ *                                                EPC_SERVICE_TYPE_HTTPS,
+ *                                                EPC_SERVICE_TYPE_HTTP,
+ *                                                NULL);
+ *
+ *  aui_service_dialog_set_service_type_name (AUI_SERVICE_DIALOG (dialog),
+ *                                            EPC_SERVICE_TYPE_HTTPS,
+ *                                            "Secure Transport");
+ *  aui_service_dialog_set_service_type_name (AUI_SERVICE_DIALOG (dialog),
+ *                                            EPC_SERVICE_TYPE_HTTP,
+ *                                            "Insecure Transport");
+ *
+ *  if (GTK_RESPONSE_ACCEPT == gtk_dialog_run (GTK_DIALOG (dialog)))
+ *   {
+ *      EpcServiceInfo *service =
+ *        epc_service_info_new (aui_service_dialog_get_service_type (AUI_SERVICE_DIALOG (dialog)),
+ *                              aui_service_dialog_get_host_name    (AUI_SERVICE_DIALOG (dialog)),
+ *                              aui_service_dialog_get_port         (AUI_SERVICE_DIALOG (dialog)),
+ *                              aui_service_dialog_get_txt_data     (AUI_SERVICE_DIALOG (dialog)));
+ *
+ *      consumer = epc_consumer_new (service);
+ *      epc_service_info_unref (service);
+ *      ...
+ *   }
+ *  </programlisting>
+ * </example>
+ */
+
+#define EPC_CONSUMER_DEFAULT_TIMEOUT 5000
+
+typedef struct _EpcListingState EpcListingState;
+
+typedef enum
+{
+  EPC_LISTING_ELEMENT_NONE,
+  EPC_LISTING_ELEMENT_LIST,
+  EPC_LISTING_ELEMENT_ITEM,
+  EPC_LISTING_ELEMENT_NAME
+}
+EpcListingElementType;
+
+enum
+{
+  PROP_NONE,
+  PROP_NAME,
+  PROP_DOMAIN,
+  PROP_APPLICATION,
+  PROP_PROTOCOL,
+  PROP_HOSTNAME,
+  PROP_PORT,
+  PROP_PATH,
+  PROP_USERNAME,
+  PROP_PASSWORD
+};
+
+enum
+{
+  SIGNAL_AUTHENTICATE,
+  SIGNAL_PUBLISHER_RESOLVED,
+  SIGNAL_LAST
+};
+
+/**
+ * EpcConsumerPrivate:
+ *
+ * Private fields of the #EpcConsumer class.
+ */
+struct _EpcConsumerPrivate
+{
+  /* supportive objects */
+
+  EpcServiceMonitor *service_monitor;
+  SoupSession       *session;
+  GMainLoop         *loop;
+
+  /* search parameters */
+
+  gchar       *application;
+  EpcProtocol  protocol;
+
+  /* service credentials */
+
+  gchar       *username;
+  gchar       *password;
+
+  /* service description */
+
+  gchar       *name;
+  gchar       *domain;
+  gchar       *hostname;
+  gchar       *path;
+  guint16      port;
+};
+
+struct _EpcListingState
+{
+  EpcListingElementType element;
+  GString              *name;
+  GList                *items;
+};
+
+static guint signals[SIGNAL_LAST];
+
+G_DEFINE_TYPE (EpcConsumer, epc_consumer, G_TYPE_OBJECT);
+
+#ifdef HAVE_LIBSOUP22
+
+static void
+epc_consumer_authenticate_cb (SoupSession  *session G_GNUC_UNUSED,
+                              SoupMessage  *message,
+                              gchar        *auth_type G_GNUC_UNUSED,
+                              gchar        *auth_realm,
+                              gchar       **username,
+                              gchar       **password,
+                              gpointer      data)
+{
+  EpcConsumer *self = EPC_CONSUMER (data);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: path=%s, realm=%s, username=%s, password=%s",
+             G_STRLOC, soup_message_get_uri (message)->path,
+             auth_realm, *username, *password);
+
+  g_free (*username);
+  g_free (*password);
+
+  *username = g_strdup (self->priv->username ? self->priv->username : "");
+  *password = g_strdup (self->priv->password ? self->priv->password : "");
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: path=%s, realm=%s, username=%s, password=%s",
+             G_STRLOC, soup_message_get_uri (message)->path,
+             auth_realm, *username, *password);
+}
+
+static void
+epc_consumer_reauthenticate_cb (SoupSession  *session,
+                                SoupMessage  *message,
+                                gchar        *auth_type,
+                                gchar        *auth_realm,
+                                gchar       **username,
+                                gchar       **password,
+                                gpointer      data)
+{
+  EpcConsumer *self = EPC_CONSUMER (data);
+  gboolean handled = FALSE;
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: path=%s, realm=%s, username=%s, password=%s, handled=%d",
+             G_STRLOC, soup_message_get_uri (message)->path,
+             auth_realm, *username, *password, handled);
+
+  g_signal_emit (self, signals[SIGNAL_AUTHENTICATE],
+                 0, auth_realm, &handled);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: path=%s, realm=%s, username=%s, password=%s, handled=%d",
+             G_STRLOC, soup_message_get_uri (message)->path,
+             auth_realm, *username, *password, handled);
+
+  if (handled)
+    epc_consumer_authenticate_cb (session, message, auth_realm,
+                                  auth_type, username, password, data);
+}
+
+#else
+
+static void
+epc_consumer_authenticate_cb (SoupSession  *session G_GNUC_UNUSED,
+                              SoupMessage  *message,
+                              SoupAuth     *auth,
+                              gboolean      retrying,
+                              gpointer      data)
+{
+  EpcConsumer *self = EPC_CONSUMER (data);
+  const char *username, *password;
+  gboolean handled = FALSE;
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: path=%s, realm=%s, retrying=%d",
+             G_STRLOC, soup_message_get_uri (message)->path,
+             soup_auth_get_realm (auth), retrying);
+
+  if (retrying)
+    {
+      g_signal_emit (self, signals[SIGNAL_AUTHENTICATE],
+                     0, soup_auth_get_realm (auth), &handled);
+
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: path=%s, realm=%s, handled=%d",
+                 G_STRLOC, soup_message_get_uri (message)->path,
+                 soup_auth_get_realm (auth), handled);
+    }
+  else
+    handled = TRUE;
+
+  if (handled)
+    {
+      username = (self->priv->username ? self->priv->username : "");
+      password = (self->priv->password ? self->priv->password : "");
+
+      soup_auth_authenticate (auth, username, password);
+
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: path=%s, realm=%s, retrying=%d, username=%s, password=%s",
+                 G_STRLOC, soup_message_get_uri (message)->path,
+                 soup_auth_get_realm (auth), retrying,
+                 username, password);
+    }
+}
+
+#endif
+
+static void
+epc_consumer_init (EpcConsumer *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EPC_TYPE_CONSUMER, EpcConsumerPrivate);
+  self->priv->loop = g_main_loop_new (NULL, FALSE);
+  self->priv->session = soup_session_async_new ();
+
+  g_signal_connect (self->priv->session, "authenticate",
+                    G_CALLBACK (epc_consumer_authenticate_cb), self);
+#ifdef HAVE_LIBSOUP22
+  g_signal_connect (self->priv->session, "reauthenticate",
+                    G_CALLBACK (epc_consumer_reauthenticate_cb), self);
+#endif
+}
+
+static void
+epc_consumer_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  EpcConsumer *self = EPC_CONSUMER (object);
+
+  switch (prop_id)
+    {
+      case PROP_NAME:
+        g_assert (NULL == self->priv->name);
+        self->priv->name = g_value_dup_string (value);
+        break;
+
+      case PROP_DOMAIN:
+        g_assert (NULL == self->priv->domain);
+        self->priv->domain = g_value_dup_string (value);
+        break;
+
+      case PROP_APPLICATION:
+        g_assert (NULL == self->priv->application);
+        self->priv->application = g_value_dup_string (value);
+        break;
+
+      case PROP_PROTOCOL:
+        g_return_if_fail (NULL == self->priv->service_monitor &&
+                          NULL == self->priv->hostname);
+        self->priv->protocol = g_value_get_enum (value);
+        break;
+
+      case PROP_HOSTNAME:
+        g_assert (NULL == self->priv->hostname);
+        self->priv->hostname = g_value_dup_string (value);
+        break;
+
+      case PROP_PORT:
+        g_assert (0 == self->priv->port);
+        self->priv->port = g_value_get_int (value);
+        break;
+
+      case PROP_PATH:
+        g_assert (NULL == self->priv->path);
+        self->priv->path = g_value_dup_string (value);
+        break;
+
+      case PROP_USERNAME:
+        g_free (self->priv->username);
+        self->priv->username = g_value_dup_string (value);
+        break;
+
+      case PROP_PASSWORD:
+        g_free (self->priv->password);
+        self->priv->password = g_value_dup_string (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+epc_consumer_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  EpcConsumer *self = EPC_CONSUMER (object);
+
+  switch (prop_id)
+    {
+      case PROP_NAME:
+        g_value_set_string (value, self->priv->name);
+        break;
+
+      case PROP_DOMAIN:
+        g_value_set_string (value, self->priv->domain);
+        break;
+
+      case PROP_APPLICATION:
+        g_value_set_string (value, self->priv->application);
+        break;
+
+      case PROP_PROTOCOL:
+        g_value_set_enum (value, self->priv->protocol);
+        break;
+
+      case PROP_HOSTNAME:
+        g_value_set_string (value, self->priv->hostname);
+        break;
+
+      case PROP_PORT:
+        g_value_set_int (value, self->priv->port);
+        break;
+
+      case PROP_PATH:
+        g_value_set_string (value, self->priv->path);
+        break;
+
+      case PROP_USERNAME:
+        g_value_set_string (value, self->priv->username);
+        break;
+
+      case PROP_PASSWORD:
+        g_value_set_string (value, self->priv->password);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+epc_consumer_service_found_cb (EpcConsumer    *self,
+                               const gchar    *name,
+                               EpcServiceInfo *info)
+{
+  const gchar *type = epc_service_info_get_service_type (info);
+  EpcProtocol transport = epc_service_type_get_protocol (type);
+
+  const gchar *path = epc_service_info_get_detail (info, "path");
+  const gchar *host = epc_service_info_get_host (info);
+  guint port = epc_service_info_get_port (info);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: Service resolved: type='%s', host='%s', port=%d, path='%s'", 
+             G_STRLOC, type, host, port, path);
+
+  if (name && strcmp (name, self->priv->name))
+    return;
+
+  g_assert (EPC_PROTOCOL_HTTPS > EPC_PROTOCOL_HTTP);
+
+  if (transport > self->priv->protocol)
+    {
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: Upgrading to %s protocol", G_STRLOC, epc_protocol_get_service_type (transport));
+
+      g_signal_emit (self, signals[SIGNAL_PUBLISHER_RESOLVED], 0, transport, host, port);
+      self->priv->protocol = transport;
+    }
+
+  g_main_loop_quit (self->priv->loop);
+
+  g_free (self->priv->path);
+  g_free (self->priv->hostname);
+
+  /* Use /get path as fallback for libepc-0.2 publishers */
+  self->priv->path = g_strdup (path ? path : "/get");
+  self->priv->hostname = g_strdup (host);
+  self->priv->port = port;
+}
+
+static void
+epc_consumer_constructed (GObject *object)
+{
+  EpcConsumer *self = EPC_CONSUMER (object);
+
+  if (G_OBJECT_CLASS (epc_consumer_parent_class)->constructed)
+    G_OBJECT_CLASS (epc_consumer_parent_class)->constructed (object);
+
+  if (!self->priv->hostname)
+    {
+      self->priv->service_monitor = epc_service_monitor_new (self->priv->application,
+                                                             self->priv->domain,
+                                                             self->priv->protocol,
+                                                             EPC_PROTOCOL_UNKNOWN);
+
+      g_signal_connect_swapped (self->priv->service_monitor, "service-found",
+                                G_CALLBACK (epc_consumer_service_found_cb),
+                                self);
+    }
+}
+
+static void
+epc_consumer_dispose (GObject *object)
+{
+  EpcConsumer *self = EPC_CONSUMER (object);
+
+  if (self->priv->service_monitor)
+    {
+      g_object_unref (self->priv->service_monitor);
+      self->priv->service_monitor = NULL;
+    }
+
+  if (self->priv->session)
+    {
+      g_object_unref (self->priv->session);
+      self->priv->session = NULL;
+    }
+
+  if (self->priv->loop)
+    {
+      g_main_loop_unref (self->priv->loop);
+      self->priv->loop = NULL;
+    }
+
+  g_free (self->priv->name);
+  self->priv->name = NULL;
+
+  g_free (self->priv->domain);
+  self->priv->domain = NULL;
+
+  g_free (self->priv->application);
+  self->priv->application = NULL;
+
+  g_free (self->priv->hostname);
+  self->priv->hostname = NULL;
+
+  g_free (self->priv->username);
+  self->priv->username = NULL;
+
+  g_free (self->priv->password);
+  self->priv->password = NULL;
+
+  g_free (self->priv->path);
+  self->priv->path = NULL;
+
+  G_OBJECT_CLASS (epc_consumer_parent_class)->dispose (object);
+}
+
+static void
+epc_consumer_class_init (EpcConsumerClass *cls)
+{
+  GObjectClass *oclass = G_OBJECT_CLASS (cls);
+
+  oclass->set_property = epc_consumer_set_property;
+  oclass->get_property = epc_consumer_get_property;
+  oclass->constructed = epc_consumer_constructed;
+  oclass->dispose = epc_consumer_dispose;
+
+  g_object_class_install_property (oclass, PROP_NAME,
+                                   g_param_spec_string ("name", "Name",
+                                                        "Service name of the publisher to use", NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_DOMAIN,
+                                   g_param_spec_string ("domain", "Domain",
+                                                        "DNS domain of the publisher to use", NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_APPLICATION,
+                                   g_param_spec_string ("application", "Application",
+                                                        "Program name the publisher to use", NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_PROTOCOL,
+                                   g_param_spec_enum ("protocol", "Protocol",
+                                                      "The transport protocol to use for contacting the 
publisher",
+                                                      EPC_TYPE_PROTOCOL, EPC_PROTOCOL_UNKNOWN,
+                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                      G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_HOSTNAME,
+                                   g_param_spec_string ("hostname", "Host Name",
+                                                        "Host name of the publisher to use", NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_PORT,
+                                   g_param_spec_int ("port", "Port",
+                                                     "TCP/IP port of the publisher to use",
+                                                     0, G_MAXUINT16, 0,
+                                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+                                                     G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                     G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_PATH,
+                                   g_param_spec_string ("path", "Path",
+                                                        "The path the publisher uses for contents",
+                                                        "/contents",
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_USERNAME,
+                                   g_param_spec_string ("username", "User Name",
+                                                        "The user name to use for authentication",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_PASSWORD,
+                                   g_param_spec_string ("password", "Password",
+                                                        "The password to use for authentication",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  /**
+   * EpcConsumer::authenticate:
+   * @consumer: the #EpcConsumer emitting the signal
+   * @realm: the realm being authenticated to
+   *
+   * Emitted when the #EpcConsumer requires authentication. The signal
+   * handler should provide these credentials, which may come from the
+   * user or from cached information by setting the #EpcConsumer:username
+   * and #EpcConsumer:password properties. When providing credentials
+   * the signal handler should return %TRUE to stop signal emission.
+   *
+   * If the provided credentials fail then the signal will be emmitted again.
+   *
+   * Returns: %TRUE when the signal handler handled the authentication request,
+   * and %FALSE otherwise.
+   */
+  signals[SIGNAL_AUTHENTICATE] = g_signal_new ("authenticate",
+                                               EPC_TYPE_CONSUMER, G_SIGNAL_RUN_LAST,
+                                               G_STRUCT_OFFSET (EpcConsumerClass, authenticate),
+                                               g_signal_accumulator_true_handled, NULL,
+                                               _epc_marshal_BOOLEAN__STRING,
+                                               G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
+
+  /**
+   * EpcConsumer::publisher-resolved:
+   * @consumer: the #EpcConsumer emitting the signal
+   * @protocol: the publisher's transport protocol
+   * @hostname: the publisher's host name
+   * @port:  the publisher's TCP/IP port
+   *
+   * This signal is emitted when a #EpcConsumer created with
+   * epc_consumer_new_for_name() or #epc_consumer_new_for_name_full
+   * has found its #EpcPublisher.
+   *
+   * Publisher detection is integrated with the GLib main loop. Therefore the
+   * signal will not be emitted before a main loop is run (g_main_loop_run(),
+   * gtk_main()). So to reliably consume this signal connect to it directly
+   * after creating the #EpcConsumer.
+   *
+   * See also: epc_consumer_resolve_publisher(), #epc_consumer_is_pulisher_resolved
+   */
+  signals[SIGNAL_PUBLISHER_RESOLVED] = g_signal_new ("publisher-resolved", EPC_TYPE_CONSUMER, 
G_SIGNAL_RUN_FIRST,
+                                                     G_STRUCT_OFFSET (EpcConsumerClass, publisher_resolved), 
NULL, NULL,
+                                                     _epc_marshal_VOID__ENUM_STRING_UINT, G_TYPE_NONE,
+                                                     3, EPC_TYPE_PROTOCOL, G_TYPE_STRING, G_TYPE_UINT);
+
+  g_type_class_add_private (cls, sizeof (EpcConsumerPrivate));
+}
+
+/**
+ * epc_consumer_new:
+ * @service: the publisher's service description
+ *
+ * Creates a new #EpcConsumer object and associates it with a known
+ * #EpcPublisher. The @service description can be retrieved, for instance,
+ * by using #EpcServiceMonitor, or by using the service selection dialog
+ * of <citetitle>avahi-ui</citetitle> (#AuiServiceDialog).
+ *
+ * The connection is not established until functions like epc_consumer_lookup(),
+ * epc_consumer_list() or #epc_consumer_resolve_publisher are called.
+ *
+ * Returns: The newly created #EpcConsumer object
+ */
+EpcConsumer*
+epc_consumer_new (const EpcServiceInfo *service)
+{
+  EpcProtocol protocol;
+  const gchar *type;
+
+  g_return_val_if_fail (EPC_IS_SERVICE_INFO (service), NULL);
+
+  type = epc_service_info_get_service_type (service);
+  protocol = epc_service_type_get_protocol (type);
+
+  g_return_val_if_fail (EPC_PROTOCOL_UNKNOWN != protocol, NULL);
+
+  return g_object_new (EPC_TYPE_CONSUMER,
+                       "protocol", protocol,
+                       "hostname", epc_service_info_get_host (service),
+                       "port", epc_service_info_get_port (service),
+                       "path", epc_service_info_get_detail (service, "path"),
+                       NULL);
+}
+
+/**
+ * epc_consumer_new_for_name:
+ * @name: the service name of an #EpcPublisher
+ *
+ * Creates a new #EpcConsumer object and associates it with the #EpcPublisher
+ * announcing itself with @name on the local network. The DNS-SD service name
+ * used for searching the #EpcPublisher is derived from the application's
+ * program name as returned by g_get_prgname().
+ *
+ * See epc_consumer_new_for_name_full() for additional notes
+ * and a method allowing better control over the search process.
+ *
+ * Returns: The newly created #EpcConsumer object
+ */
+EpcConsumer*
+epc_consumer_new_for_name (const gchar *name)
+{
+  return epc_consumer_new_for_name_full (name, NULL, NULL);
+}
+
+/**
+ * epc_consumer_new_for_name_full:
+ * @name: the service name of an #EpcPublisher
+ * @application: the publisher's program name
+ * @domain: the DNS domain of the #EpcPublisher
+ *
+ * Creates a new #EpcConsumer object and associates it with the #EpcPublisher
+ * announcing itself with @name on @domain. The DNS-SD service of the
+ * #EpcPublisher is derived from @application using epc_service_type_new().
+ *
+ * <note><para>
+ *  This function shall be used to re-connect to a formerly used #EpcPublisher,
+ *  selected for instance from a list for recently used services. Therefore
+ *  using epc_consumer_new_for_name_full() is a quite optimistic approach for
+ *  contacting a publisher: You call it without really knowing if the
+ *  publisher you requested really exists. You only know that it existed
+ *  in the past when you added it to your list of recently used publishers,
+ *  but you do not know if it still exists.
+ *
+ *  To let your users choose from an up-to-date service list, you have to
+ *  use a dynamic service list as provided by avahi-ui for choosing a service
+ *  and pass the information this widget provides (hostname, port, protocol)
+ *  to epc_consumer_new().
+ * </para></note>
+ *
+ * <note><para>
+ *  The connection is not established until a function retrieving
+ *  data, like for instance epc_consumer_lookup(), is called.
+ *
+ *  Explicitly call epc_consumer_resolve_publisher() or connect to
+ *  the #EpcConsumer::publisher-resolved signal, when your application
+ *  needs reliable information about the existance of the #EpcPublisher
+ *  described by @name.
+ * </para></note>
+ *
+ * Returns: The newly created #EpcConsumer object
+ */
+EpcConsumer*
+epc_consumer_new_for_name_full (const gchar *name,
+                                const gchar *application,
+                                const gchar *domain)
+{
+  g_return_val_if_fail (NULL != name, NULL);
+
+  return g_object_new (EPC_TYPE_CONSUMER,
+                       "application", application,
+                       "domain", domain,
+                       "name", name, NULL);
+}
+
+/**
+ * epc_consumer_set_protocol:
+ * @consumer: a #EpcConsumer
+ * @protocol: the new transport protocol
+ *
+ * Changes the transport protocol to use for contacting the publisher.
+ * See #EpcConsumer:protocol for details.
+ */
+void
+epc_consumer_set_protocol (EpcConsumer *self,
+                           EpcProtocol  protocol)
+{
+  g_return_if_fail (EPC_IS_CONSUMER (self));
+  g_object_set (self, "protocol", protocol, NULL);
+}
+
+/**
+ * epc_consumer_set_username:
+ * @consumer: a #EpcConsumer
+ * @username: the new user name, or %NULL
+ *
+ * Changes the user name used for authentication.
+ * See #EpcConsumer:username for details.
+ */
+void
+epc_consumer_set_username (EpcConsumer *self,
+                           const gchar *username)
+{
+  g_return_if_fail (EPC_IS_CONSUMER (self));
+  g_object_set (self, "username", username, NULL);
+}
+
+/**
+ * epc_consumer_set_password:
+ * @consumer: a #EpcConsumer
+ * @password: the new password, or %NULL
+ *
+ * Changes the password used for authentication.
+ * See #EpcConsumer:password for details.
+ */
+void
+epc_consumer_set_password (EpcConsumer *self,
+                           const gchar *password)
+{
+  g_return_if_fail (EPC_IS_CONSUMER (self));
+  g_object_set (self, "password", password, NULL);
+}
+
+/**
+ * epc_consumer_get_protocol:
+ * @consumer: a #EpcConsumer
+ *
+ * Queries the transport protocol to use for contacting the publisher.
+ * See #EpcConsumer:protocol for details.
+ *
+ * Returns: The transport protocol this consumer uses.
+ */
+EpcProtocol
+epc_consumer_get_protocol (EpcConsumer *self)
+{
+  g_return_val_if_fail (EPC_IS_CONSUMER (self), EPC_PROTOCOL_UNKNOWN);
+  return self->priv->protocol;
+}
+
+/**
+ * epc_consumer_get_username:
+ * @consumer: a #EpcConsumer
+ *
+ * Queries the user name used for authentication.
+ * See #EpcConsumer:username for details.
+ *
+ * Returns: The user name this consumer uses.
+ */
+const gchar*
+epc_consumer_get_username (EpcConsumer *self)
+{
+  g_return_val_if_fail (EPC_IS_CONSUMER (self), NULL);
+  return self->priv->username;
+}
+
+/**
+ * epc_consumer_get_password:
+ * @consumer: a #EpcConsumer
+ *
+ * Queries the password used for authentication.
+ * See #EpcConsumer:password for details.
+ *
+ * Returns: The password this consumer uses.
+ */
+const gchar*
+epc_consumer_get_password (EpcConsumer *self)
+{
+  g_return_val_if_fail (EPC_IS_CONSUMER (self), NULL);
+  return self->priv->password;
+}
+
+static gboolean
+epc_consumer_wait_cb (gpointer data)
+{
+  EpcConsumer *self = data;
+
+  g_warning ("%s: Timeout reached when waiting for publisher", G_STRFUNC);
+  g_main_loop_quit (self->priv->loop);
+
+  return FALSE;
+}
+
+/**
+ * epc_consumer_resolve_publisher:
+ * @consumer: a #EpcConsumer
+ * @timeout: the amount of milliseconds to wait
+ *
+ * Waits until the @consumer has found its #EpcPublisher.
+ * A @timeout of 0 requests infinite waiting.
+ *
+ * See also: #EpcConsumer::publisher-resolved
+ *
+ * Returns: %TRUE when a publisher has been found, %FALSE otherwise.
+ */
+gboolean
+epc_consumer_resolve_publisher (EpcConsumer *self,
+                                guint        timeout)
+{
+  g_return_val_if_fail (EPC_IS_CONSUMER (self), FALSE);
+
+  if (NULL == self->priv->hostname)
+    {
+      if (timeout > 0)
+        g_timeout_add (timeout, epc_consumer_wait_cb, self);
+
+      g_main_loop_run (self->priv->loop);
+    }
+
+  return epc_consumer_is_publisher_resolved (self);
+}
+
+/**
+ * epc_consumer_is_publisher_resolved:
+ * @consumer: a #EpcConsumer
+ *
+ * Checks if the host name of this consumer's #EpcPublisher
+ * has been resolved already.
+ *
+ * See also: epc_consumer_resolve_publisher(), #EpcPublisher::publisher-resolved
+ *
+ * Returns: %TRUE when the host name has been resolved, and %FALSE otherwise.
+ */
+gboolean
+epc_consumer_is_publisher_resolved (EpcConsumer *self)
+{
+  g_return_val_if_fail (EPC_IS_CONSUMER (self), FALSE);
+  return (NULL != self->priv->hostname);
+}
+
+static SoupMessage*
+epc_consumer_create_request (EpcConsumer *self,
+                             const gchar *path)
+{
+  SoupMessage *request = NULL;
+  char *request_uri;
+
+  if (NULL == path)
+    path = "/";
+
+  g_assert ('/' == path[0]);
+
+  g_return_val_if_fail (NULL != self->priv->hostname, NULL);
+  g_return_val_if_fail (self->priv->port > 0, NULL);
+
+  request_uri = epc_protocol_build_uri (self->priv->protocol,
+                                        self->priv->hostname,
+                                        self->priv->port,
+                                        path);
+
+  g_return_val_if_fail (NULL != request_uri, NULL);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: Connecting to `%s'", G_STRLOC, request_uri);
+
+  request = soup_message_new ("GET", request_uri);
+  g_free (request_uri);
+
+  return request;
+}
+
+static void
+epc_consumer_set_http_error (GError     **error,
+                             SoupMessage *request,
+                             guint        status)
+{
+  const gchar *details = NULL;
+
+  if (request)
+    details = request->reason_phrase;
+  if (!details)
+    details = soup_status_get_phrase (status);
+
+  g_set_error (error, EPC_HTTP_ERROR, status,
+               "HTTP library error %d: %s.",
+               status, details);
+}
+
+/**
+ * epc_consumer_lookup:
+ * @consumer: the consumer
+ * @key: unique key of the value
+ * @length: location to store length in bytes of the contents, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * If the call was successful, this returns a newly allocated buffer containing
+ * the value the publisher provides for @key. If the call was not
+ * successful it returns %NULL and sets @error. The error domain is
+ * #EPC_HTTP_ERROR. Error codes are taken from the #SoupKnownStatusCode
+ * enumeration.
+ *
+ * For instance, the error code will be #SOUP_STATUS_FORBIDDEN if
+ * authentication failed (see epc_publisher_set_auth_handler()). You must
+ * include <filename class="headerfile">libsoup/soup-status.h</filename>
+ * to use this error code.
+ *
+ * The returned buffer should be freed when no longer needed.
+ *
+ * See the description of #EpcPublisher for discussion of %NULL values.
+ *
+ * Returns: A copy of the publisher's value for the the requested @key,
+ * or %NULL when an error occurred.
+ */
+gpointer
+epc_consumer_lookup (EpcConsumer  *self,
+                     const gchar  *key,
+                     gsize        *length,
+                     GError      **error)
+{
+  SoupMessage *request = NULL;
+  gchar *contents = NULL;
+  gint status = 0;
+
+  g_return_val_if_fail (EPC_IS_CONSUMER (self), NULL);
+  g_return_val_if_fail (NULL != key, NULL);
+
+  if (epc_consumer_resolve_publisher (self, EPC_CONSUMER_DEFAULT_TIMEOUT))
+    {
+      gchar *keyuri = NULL;
+      gchar *path = NULL;
+
+      keyuri = soup_uri_encode (key, NULL);
+      path = g_strconcat (self->priv->path, "/", keyuri, NULL);
+      request = epc_consumer_create_request (self, path);
+
+      g_free (keyuri);
+      g_free (path);
+    }
+
+  if (request)
+    status = soup_session_send_message (self->priv->session, request);
+  else
+    status = SOUP_STATUS_CANT_RESOLVE;
+
+  if (SOUP_STATUS_IS_SUCCESSFUL (status))
+    {
+#ifdef HAVE_LIBSOUP22
+      const gsize response_length = request->response.length;
+      gconstpointer response_data = request->response.body;
+#else
+      const gsize response_length = request->response_body->length;
+      gconstpointer response_data = request->response_body->data;
+#endif
+
+      if (length)
+        *length = response_length;
+
+      contents = g_malloc (response_length + 1);
+      contents[response_length] = '\0';
+
+      memcpy (contents, response_data, response_length);
+    }
+  else
+    epc_consumer_set_http_error (error, request, status);
+
+  if (request)
+    g_object_unref (request);
+
+  return contents;
+}
+
+static void
+epc_consumer_list_parser_start_element (GMarkupParseContext *context G_GNUC_UNUSED,
+                                        const gchar         *element_name,
+                                        const gchar        **attribute_names G_GNUC_UNUSED,
+                                        const gchar        **attribute_values G_GNUC_UNUSED,
+                                        gpointer             data,
+                                        GError             **error)
+{
+  EpcListingElementType element = EPC_LISTING_ELEMENT_NONE;
+  EpcListingState *state = data;
+
+  switch (state->element)
+    {
+      case EPC_LISTING_ELEMENT_NONE:
+        if (g_str_equal (element_name, "list"))
+          element = EPC_LISTING_ELEMENT_LIST;
+
+        break;
+
+      case EPC_LISTING_ELEMENT_LIST:
+        if (g_str_equal (element_name, "item"))
+          element = EPC_LISTING_ELEMENT_ITEM;
+
+        break;
+
+      case EPC_LISTING_ELEMENT_ITEM:
+        if (g_str_equal (element_name, "name"))
+          element = EPC_LISTING_ELEMENT_NAME;
+
+        break;
+
+      case EPC_LISTING_ELEMENT_NAME:
+        break;
+
+      default:
+        g_warning ("%s: Unexpected element.", G_STRFUNC);
+        break;
+    }
+
+  if (element)
+    state->element = element;
+  else
+    g_set_error (error, G_MARKUP_ERROR,
+                 G_MARKUP_ERROR_INVALID_CONTENT,
+                 _("Unexpected element: '%s'"),
+                element_name);
+}
+
+static void
+epc_consumer_list_parser_end_element (GMarkupParseContext *context G_GNUC_UNUSED,
+                                      const gchar         *element_name G_GNUC_UNUSED,
+                                      gpointer             data,
+                                      GError             **error G_GNUC_UNUSED)
+{
+  EpcListingState *state = data;
+
+  switch (state->element)
+    {
+      case EPC_LISTING_ELEMENT_NAME:
+        state->element = EPC_LISTING_ELEMENT_ITEM;
+        break;
+
+      case EPC_LISTING_ELEMENT_ITEM:
+        state->element = EPC_LISTING_ELEMENT_LIST;
+        state->items = g_list_prepend (state->items, g_string_free (state->name, FALSE));
+        state->name = NULL;
+        break;
+
+      case EPC_LISTING_ELEMENT_LIST:
+        state->element = EPC_LISTING_ELEMENT_NONE;
+        break;
+
+      case EPC_LISTING_ELEMENT_NONE:
+        break;
+
+      default:
+        g_warning ("%s: Unexpected element.", G_STRFUNC);
+        break;
+    }
+}
+
+static void
+epc_consumer_list_parser_text (GMarkupParseContext *context G_GNUC_UNUSED,
+                               const gchar         *text,
+                               gsize                text_len,
+                               gpointer             data,
+                               GError             **error G_GNUC_UNUSED)
+{
+  EpcListingState *state = data;
+
+  switch (state->element)
+    {
+      case EPC_LISTING_ELEMENT_NAME:
+        if (!state->name)
+          state->name = g_string_new (NULL);
+
+        g_string_append_len (state->name, text, text_len);
+        break;
+
+      case EPC_LISTING_ELEMENT_ITEM:
+      case EPC_LISTING_ELEMENT_LIST:
+      case EPC_LISTING_ELEMENT_NONE:
+        break;
+
+      default:
+        g_warning ("%s: Unexpected element.", G_STRFUNC);
+        break;
+    }
+}
+
+/**
+ * epc_consumer_list:
+ * @consumer: a #EpcConsumer
+ * @pattern: a glob-style pattern, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Matches published keys against patterns containing '*' (wildcard) and '?'
+ * (joker). Passing %NULL as @pattern is equivalent to passing "*" and returns
+ * all published keys. This function is useful to find and select dynamically
+ * published values. See #GPatternSpec for information about glob-style
+ * patterns.
+ *
+ * If the call was successful, a list of keys matching @pattern is returned.
+ * If the call was not successful, it returns %NULL and sets @error.
+ * The error domain is #EPC_HTTP_ERROR. Error codes are taken from the
+ * #SoupKnownStatusCode enumeration.
+ *
+ * The returned list should be freed when no longer needed:
+ *
+ * <programlisting>
+ *  g_list_foreach (keys, (GFunc) g_free, NULL);
+ *  g_list_free (keys);
+ * </programlisting>
+ *
+ * See also epc_publisher_list() for creating custom listings.
+ *
+ * Returns: A newly allocated list of keys, or %NULL when an error occurred.
+ */
+GList*
+epc_consumer_list (EpcConsumer  *self,
+                   const gchar  *pattern G_GNUC_UNUSED,
+                   GError      **error G_GNUC_UNUSED)
+{
+  SoupMessage *request = NULL;
+  EpcListingState state;
+  gint status = 0;
+
+  g_return_val_if_fail (EPC_IS_CONSUMER (self), NULL);
+  g_return_val_if_fail (NULL == pattern || *pattern, NULL);
+
+  if (epc_consumer_resolve_publisher (self, EPC_CONSUMER_DEFAULT_TIMEOUT))
+    {
+      gchar *path = g_strconcat ("/list/", pattern, NULL);
+      request = epc_consumer_create_request (self, path);
+      g_free (path);
+    }
+
+  if (request)
+    status = soup_session_send_message (self->priv->session, request);
+  else
+    status = SOUP_STATUS_CANT_RESOLVE;
+
+  memset (&state, 0, sizeof state);
+
+  if (SOUP_STATUS_IS_SUCCESSFUL (status))
+    {
+      GMarkupParseContext *context;
+      GMarkupParser parser;
+
+      memset (&parser, 0, sizeof parser);
+
+      parser.start_element = epc_consumer_list_parser_start_element;
+      parser.end_element = epc_consumer_list_parser_end_element;
+      parser.text = epc_consumer_list_parser_text;
+
+      context = g_markup_parse_context_new (&parser,
+                                            G_MARKUP_TREAT_CDATA_AS_TEXT,
+                                            &state, NULL);
+
+#ifdef HAVE_LIBSOUP22
+      g_markup_parse_context_parse (context,
+                                    request->response.body,
+                                    request->response.length,
+                                    error);
+#else
+      g_markup_parse_context_parse (context,
+                                    request->response_body->data,
+                                    request->response_body->length,
+                                    error);
+#endif
+
+      g_markup_parse_context_free (context);
+    }
+  else
+    epc_consumer_set_http_error (error, request, status);
+
+  if (request)
+    g_object_unref (request);
+
+  return state.items;
+}
+
+GQuark
+epc_http_error_quark (void)
+{
+  return g_quark_from_static_string ("epc-http-error-quark");
+}
diff --git a/glom/libglom/libepc/consumer.h b/glom/libglom/libepc/consumer.h
new file mode 100644
index 0000000..d52dca0
--- /dev/null
+++ b/glom/libglom/libepc/consumer.h
@@ -0,0 +1,120 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+#ifndef __EPC_CONSUMER_H__
+#define __EPC_CONSUMER_H__
+
+#include <libglom/libepc/service-monitor.h>
+#include <libglom/libepc/service-type.h>
+
+G_BEGIN_DECLS
+
+/**
+ * EPC_HTTP_ERROR:
+ *
+ * Error domain for HTTP operations. Errors in this domain will
+ * be from the #SoupKnownStatusCode enumeration. See GError for
+ * information on error domains.
+ */
+#define EPC_HTTP_ERROR              (epc_http_error_quark())
+
+#define EPC_TYPE_CONSUMER           (epc_consumer_get_type())
+#define EPC_CONSUMER(obj)           (G_TYPE_CHECK_INSTANCE_CAST(obj, EPC_TYPE_CONSUMER, EpcConsumer))
+#define EPC_CONSUMER_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST(cls, EPC_TYPE_CONSUMER, EpcConsumerClass))
+#define EPC_IS_CONSUMER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE(obj, EPC_TYPE_CONSUMER))
+#define EPC_IS_CONSUMER_CLASS(obj)  (G_TYPE_CHECK_CLASS_TYPE(obj, EPC_TYPE_CONSUMER))
+#define EPC_CONSUMER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPC_TYPE_CONSUMER, EpcConsumerClass))
+
+typedef struct _EpcConsumer        EpcConsumer;
+typedef struct _EpcConsumerClass   EpcConsumerClass;
+typedef struct _EpcConsumerPrivate EpcConsumerPrivate;
+
+/**
+ * EpcConsumer:
+ *
+ * Public fields of the #EpcConsumer class.
+ */
+struct _EpcConsumer
+{
+  /*< private >*/
+  GObject parent_instance;
+  EpcConsumerPrivate *priv;
+
+  /*< public >*/
+};
+
+/**
+ * EpcConsumerClass:
+ * @authenticate: virtual method of the #EpcConsumer::authenticate signal
+ * @publisher_resolved: virtual method of the #EpcConsumer::publisher-resolved signal
+ *
+ * Virtual methods of the #EpcConsumer class.
+ */
+struct _EpcConsumerClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+  void (*authenticate)       (EpcConsumer  *consumer,
+                              const gchar  *realm);
+
+  void (*publisher_resolved) (EpcConsumer  *consumer,
+                              EpcProtocol   protocol,
+                              const gchar  *hostname,
+                              guint         port);
+};
+
+GType                 epc_consumer_get_type              (void) G_GNUC_CONST;
+
+EpcConsumer*          epc_consumer_new                   (const EpcServiceInfo *service);
+EpcConsumer*          epc_consumer_new_for_name          (const gchar          *name);
+EpcConsumer*          epc_consumer_new_for_name_full     (const gchar          *name,
+                                                          const gchar          *application,
+                                                          const gchar          *domain);
+
+void                  epc_consumer_set_protocol          (EpcConsumer          *consumer,
+                                                          EpcProtocol           protocol);
+void                  epc_consumer_set_username          (EpcConsumer          *consumer,
+                                                          const gchar          *username);
+void                  epc_consumer_set_password          (EpcConsumer          *consumer,
+                                                          const gchar          *password);
+
+EpcProtocol           epc_consumer_get_protocol          (EpcConsumer          *consumer);
+const gchar* epc_consumer_get_username          (EpcConsumer          *consumer);
+const gchar* epc_consumer_get_password          (EpcConsumer          *consumer);
+
+gboolean              epc_consumer_resolve_publisher     (EpcConsumer          *consumer,
+                                                          guint                 timeout);
+gboolean              epc_consumer_is_publisher_resolved (EpcConsumer          *consumer);
+
+gpointer              epc_consumer_lookup                (EpcConsumer          *consumer,
+                                                          const gchar          *key,
+                                                          gsize                *length,
+                                                          GError              **error);
+GList*                epc_consumer_list                  (EpcConsumer          *consumer,
+                                                          const gchar          *pattern,
+                                                          GError              **error);
+
+GQuark                epc_http_error_quark               (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __EPC_CONSUMER_H__ */
diff --git a/glom/libglom/libepc/contents.c b/glom/libglom/libepc/contents.c
new file mode 100644
index 0000000..16146d2
--- /dev/null
+++ b/glom/libglom/libepc/contents.c
@@ -0,0 +1,359 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#include <libglom/libepc/contents.h>
+#include <libglom/libepc/shell.h>
+
+#include <string.h>
+#include <unistd.h>
+
+/**
+ * SECTION:contents
+ * @short_description: custom contents
+ * @see_also: #EpcPublisher
+ * @include: libepc/contents.h
+ * @stability: Unstable
+ *
+ * #EpcContents is a reference counted structure for storing custom contents.
+ * To publish custom content call epc_publisher_add_handler() to register a
+ * #EpcContentsHandler like this:
+ *
+ * <example id="custom-contents-handler">
+ *  <title>A custom contents handler</title>
+ *  <programlisting>
+ *   static EpcContents*
+ *   timestamp_handler (EpcPublisher *publisher G_GNUC_UNUSED,
+ *                      const gchar  *key G_GNUC_UNUSED,
+ *                      gpointer      data)
+ *   {
+ *     time_t now = time (NULL);
+ *     struct tm *tm = localtime (&now);
+ *     const gchar *format = data;
+ *     gsize length = 60;
+ *     gchar *buffer;
+ *
+ *     buffer = g_malloc (length);
+ *     length = strftime (buffer, length, format, tm);
+ *
+ *     return epc_content_new ("text/plain", buffer, length);
+ *   }
+ *  </programlisting>
+ * </example>
+ */
+
+/**
+ * EpcContents:
+ *
+ * A reference counted buffer for storing contents to deliver by the
+ * #EpcPublisher. Use epc_contents_new() or #epc_contents_new_dup to create
+ * instances of this buffer.
+ */
+struct _EpcContents
+{
+  volatile gint       ref_count;
+  gchar              *type;
+
+  gpointer            buffer;
+  gsize               buffer_size;
+  GDestroyNotify      destroy_buffer;
+
+  EpcContentsReadFunc callback;
+  gpointer            user_data;
+  GDestroyNotify      destroy_data;
+};
+
+/**
+ * epc_contents_new:
+ * @type: the MIME type of this contents, or %NULL
+ * @data: static contents for the buffer
+ * @length: the contents length in bytes, or -1 if @data is a null-terminated string.
+ * @destroy_data: This function will be called to free @data when it is no longer needed.
+ *
+ * Creates a new #EpcContents buffer, and takes ownership of the @data passed.
+ * Passing %NULL for @type is equivalent to passing "application/octet-stream".
+ *
+ * See also: epc_contents_new_dup, epc_contents_stream_new
+ *
+ * Returns: The newly created #EpcContents buffer.
+ */
+EpcContents*
+epc_contents_new (const gchar    *type,
+                  gpointer        data,
+                  gssize          length,
+                  GDestroyNotify  destroy_data)
+{
+  EpcContents *self;
+
+  g_return_val_if_fail (NULL != data, NULL);
+
+  self = g_slice_new0 (EpcContents);
+  self->ref_count = 1;
+
+  if (type)
+    self->type = g_strdup (type);
+  if (-1 == length)
+    length = strlen (data);
+
+  self->buffer = data;
+  self->buffer_size = length;
+  self->destroy_buffer = destroy_data;
+
+  return self;
+}
+
+/**
+ * epc_contents_new_dup:
+ * @type: the MIME type of this contents, or %NULL
+ * @data: static contents for the buffer
+ * @length: the content's length in bytes, or -1 if @data is a null-terminated string. 
+ *
+ * Creates a new #EpcContents buffer, and copies the @data passed.
+ * Passing %NULL for @type is equivalent to passing "application/octet-stream".
+ *
+ * See also: epc_contents_new, epc_contents_stream_new
+ *
+ * Returns: The newly created #EpcContents buffer.
+ */
+EpcContents*
+epc_contents_new_dup (const gchar  *type,
+                      gconstpointer data,
+                      gssize        length)
+{
+  gpointer cloned_data;
+
+  g_return_val_if_fail (NULL != data, NULL);
+
+  if (-1 == length)
+    length = strlen (data);
+
+  cloned_data = g_malloc (MAX (1, length));
+  memcpy (cloned_data, data, length);
+
+  return epc_contents_new (type, cloned_data, length, g_free);
+}
+
+/**
+ * epc_contents_stream_new:
+ * @type: the MIME type of this contents, or %NULL
+ * @callback: the function for retrieving chunks
+ * @user_data: data which will be passed to @callback
+ * @destroy_data:  This function will be called to free @user_data when it is no longer needed.
+ *
+ * Creates a new #EpcContents buffer for large contents like movie files,
+ * which cannot, or should be delivered as solid blob of data.
+ *
+ * Passing %NULL for @type is equivalent to passing "application/octet-stream".
+ *
+ * See also: epc_contents_stream_read(), #epc_contents_is_stream
+ *
+ * Returns: The newly created #EpcContents buffer.
+ */
+EpcContents*
+epc_contents_stream_new (const gchar         *type,
+                         EpcContentsReadFunc  callback,
+                         gpointer             user_data,
+                         GDestroyNotify       destroy_data)
+{
+  EpcContents *self;
+
+  g_return_val_if_fail (NULL != callback, NULL);
+
+  self = g_slice_new0 (EpcContents);
+  self->ref_count = 1;
+
+  if (type)
+    self->type = g_strdup (type);
+
+  self->callback = callback;
+  self->user_data = user_data;
+  self->destroy_data = destroy_data;
+  self->destroy_buffer = g_free;
+
+  return self;
+}
+
+/**
+ * epc_contents_ref:
+ * @contents: a #EpcContents buffer
+ *
+ * Increases the reference count of @contents.
+ *
+ * Returns: the same @contents buffer.
+ */
+EpcContents*
+epc_contents_ref (EpcContents *self)
+{
+  g_return_val_if_fail (NULL != self, NULL);
+
+  g_atomic_int_inc (&self->ref_count);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: self=%p, ref_count=%d", G_STRFUNC, self, self->ref_count);
+
+  return self;
+}
+
+/**
+ * epc_contents_unref:
+ * @contents: a #EpcContents buffer
+ *
+ * Decreases the reference count of @contents.
+ * When its reference count drops to 0, the buffer is released
+ * (i.e. its memory is freed).
+ */
+void
+epc_contents_unref (EpcContents *self)
+{
+  g_return_if_fail (NULL != self);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: self=%p, ref_count=%d", G_STRFUNC, self, self->ref_count);
+
+  if (g_atomic_int_dec_and_test (&self->ref_count))
+    {
+      if (self->destroy_buffer)
+        self->destroy_buffer (self->buffer);
+      if (self->destroy_data)
+        self->destroy_data (self->user_data);
+
+      g_free (self->type);
+
+      g_slice_free (EpcContents, self);
+    }
+}
+
+/**
+ * epc_contents_is_stream:
+ * @contents: a #EpcContents buffer
+ *
+ * Checks if stream routines can be used for retreiving
+ * the contents of the buffer.
+ *
+ * See also: epc_contents_stream_new(), #epc_contents_stream_read
+ *
+ * Returns: Returns %TRUE when stream routines have to be used.
+ */
+gboolean
+epc_contents_is_stream (EpcContents *contents)
+{
+  return contents && contents->callback;
+}
+
+/**
+ * epc_contents_get_mime_type:
+ * @contents: a #EpcContents buffer
+ *
+ * Queries the MIME type associated with the buffer. Returns the MIME
+ * type specified for epc_contents_new() or #epc_contents_stream_new,
+ * or "application/octet-stream" when %NULL was passed.
+ *
+ * Returns: Returns the MIME type of the buffer.
+ */
+const gchar*
+epc_contents_get_mime_type (EpcContents *self)
+{
+  g_return_val_if_fail (NULL != self, NULL);
+
+  if (self->type)
+    return self->type;
+
+  return "application/octet-stream";
+}
+
+/**
+ * epc_contents_get_data:
+ * @contents: a #EpcContents buffer
+ * @length: a location for storing the contents length
+ *
+ * Retrieves the contents of a static contents buffer created with
+ * epc_contents_new(). Any other buffer returns %NULL. The data returned
+ * is owned by the #EpcContents buffer and must not be freeded.
+ *
+ * See also: epc_contents_stream_read().
+ *
+ * Returns: Returns the static buffer contents, or %NULL. This should not be freed or modified.
+ */
+gconstpointer
+epc_contents_get_data (EpcContents *contents,
+                       gsize       *length)
+{
+  g_return_val_if_fail (NULL != contents, NULL);
+
+  if (epc_contents_is_stream (contents))
+    return NULL;
+
+  if (length)
+    *length = contents->buffer_size;
+
+  return contents->buffer;
+}
+
+/**
+ * epc_contents_stream_read:
+ * @contents: a #EpcContents buffer
+ * @length: a location for storing the contents length
+ *
+ * Retrieves the next chunk of data for a streaming contents buffer created
+ * with epc_contents_stream_read(). %NULL is returned, when the buffer has
+ * reached its end, or isn't a streaming contents buffer.
+ *
+ * The data returned is owned by the #EpcContents buffer and must not be
+ * freeded by the called. Make sure to copy the returned data before the
+ * function again, as repeated calls to the function might return the
+ * same buffer, but filled with new data.
+ *
+ * See also: epc_contents_stream_new(), #epc_contents_is_stream
+ *
+ * Returns: Returns the next chunk of data, or %NULL. The should not be freed or modified.
+ */
+gconstpointer
+epc_contents_stream_read (EpcContents *self,
+                          gsize       *length)
+{
+  gconstpointer data = NULL;
+
+  g_return_val_if_fail (epc_contents_is_stream (self), NULL);
+  g_return_val_if_fail (NULL != length, NULL);
+
+
+  if (0 == self->buffer_size)
+    self->buffer_size = sysconf (_SC_PAGESIZE);
+
+  *length = self->buffer_size;
+
+  if (self->callback (self, self->buffer, length, self->user_data))
+    data = self->buffer;
+  else if (*length > 0)
+    {
+      gssize page_size = sysconf (_SC_PAGESIZE);
+      gsize page_count = (*length + page_size - 1) / page_size;
+
+      self->buffer_size = page_count * page_size;
+      self->buffer = g_realloc (self->buffer, self->buffer_size);
+
+      *length = self->buffer_size;
+
+      if (self->callback (self, self->buffer, length, self->user_data))
+        data = self->buffer;
+    }
+
+  return data;
+}
diff --git a/glom/libglom/libepc/contents.h b/glom/libglom/libepc/contents.h
new file mode 100644
index 0000000..ce7cae3
--- /dev/null
+++ b/glom/libglom/libepc/contents.h
@@ -0,0 +1,81 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+#ifndef __EPC_CONTENTS_H__
+#define __EPC_CONTENTS_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _EpcContents EpcContents;
+
+/**
+ * EpcContentsReadFunc:
+ * @contents: a #EpcContents buffer
+ * @buffer: a location for storing the contents, or %NULL
+ * @length: a location for passing and storing the contents length in bytes.
+ * @user_data: the user_data passed to #epc_contents_stream_new
+ *
+ * This callback is used to retrieve the next chunk of data for a streaming
+ * contents buffer created with #epc_contents_stream_read.
+ *
+ * Return %FALSE when the buffer has reached its end, and no more data is
+ * available. Also return %FALSE, when the buffer size passed in @length is
+ * not sufficient. Copy your minimal buffer size to @length in that situation.
+ *
+ * The library might pass %NULL for @buffer on the first call to start buffer
+ * size negotation.
+ *
+ * See also: #epc_contents_stream_new, #epc_contents_stream_read
+ *
+ * Returns: Returns %TRUE when the next chunk could be read, and %FALSE on error.
+ */
+typedef gboolean    (*EpcContentsReadFunc)       (EpcContents         *contents,
+                                                  gpointer             buffer,
+                                                  gsize               *length,
+                                                  gpointer             user_data);
+
+EpcContents*          epc_contents_new           (const gchar         *type,
+                                                  gpointer             data,
+                                                  gssize               length,
+                                                  GDestroyNotify       destroy_data);
+EpcContents*          epc_contents_new_dup       (const gchar         *type,
+                                                  gconstpointer        data,
+                                                  gssize               length);
+EpcContents*          epc_contents_stream_new    (const gchar         *type,
+                                                  EpcContentsReadFunc  callback,
+                                                  gpointer             user_data,
+                                                  GDestroyNotify       destroy_data);
+
+EpcContents*          epc_contents_ref           (EpcContents         *contents);
+void                  epc_contents_unref         (EpcContents         *contents);
+
+gboolean              epc_contents_is_stream     (EpcContents         *contents);
+const gchar* epc_contents_get_mime_type (EpcContents         *contents);
+
+gconstpointer         epc_contents_get_data      (EpcContents         *contents,
+                                                  gsize               *length);
+gconstpointer         epc_contents_stream_read   (EpcContents         *contents,
+                                                  gsize               *length);
+
+G_END_DECLS
+
+#endif /* __EPC_CONTENTS_H__ */
diff --git a/glom/libglom/libepc/dispatcher.c b/glom/libglom/libepc/dispatcher.c
new file mode 100644
index 0000000..0e8d266
--- /dev/null
+++ b/glom/libglom/libepc/dispatcher.c
@@ -0,0 +1,1109 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#include <libglom/libepc/dispatcher.h>
+
+#include <libglom/libepc/enums.h>
+#include <libglom/libepc/service-monitor.h>
+#include <libglom/libepc/service-type.h>
+#include <libglom/libepc/shell.h>
+
+#include <avahi-common/alternative.h>
+#include <avahi-common/error.h>
+#include <uuid/uuid.h>
+#include <string.h>
+
+/**
+ * SECTION:dispatcher
+ * @short_description: publish DNS-SD services
+ * @include: libepc/dispatcher.h
+ * @stability: Unstable
+ *
+ * The #EpcDispatcher object provides an easy method for publishing
+ * DNS-SD services. Unlike established APIs like Avahi or HOWL the
+ * #EpcDispatcher doesn't expose any state changes reported by the
+ * DNS-SD daemon, but instead tries to handle them automatically. Such state
+ * changes include, for instance, name collisions or a restart of
+ * the DNS-SD daemon.
+ *
+ * <example id="publish-printing-service">
+ *  <title>Publish a printing service</title>
+ *  <programlisting>
+ *   dispatcher = epc_dispatcher_new ("Dead Tree Desecrator");
+ *
+ *   epc_dispatcher_add_service (dispatcher, EPC_ADDRESS_IPV4, "_ipp._tcp",
+ *                               NULL, NULL, 651, "path=/printers", NULL);
+ *   epc_dispatcher_add_service (dispatcher, EPC_ADDRESS_UNSPEC,
+ *                               "_duplex._sub._printer._tcp",
+ *                               NULL, NULL, 515, NULL);
+ *  </programlisting>
+ * </example>
+ */
+
+typedef struct _EpcService EpcService;
+
+typedef void (*EpcServiceCallback) (EpcService *service);
+
+enum
+{
+  PROP_NONE,
+  PROP_NAME,
+  PROP_COOKIE,
+  PROP_COLLISION_HANDLING
+};
+
+struct _EpcService
+{
+  EpcDispatcher   *dispatcher;
+  AvahiEntryGroup *group;
+  AvahiProtocol    protocol;
+  guint            commit_handler;
+
+  gchar           *type;
+  gchar           *domain;
+  gchar           *host;
+  guint16          port;
+
+  GList           *subtypes;
+  AvahiStringList *details;
+};
+
+/**
+ * EpcDispatcherPrivate:
+ * @name: the service name
+ * @client: the Avahi client
+ * @services: all services announced with the service type as key
+ *
+ * Private fields of the #EpcDispatcher class.
+ */
+struct _EpcDispatcherPrivate
+{
+  gchar                *name;
+  gchar                *cookie;
+  EpcCollisionHandling  collisions;
+  EpcServiceMonitor    *monitor;
+  GHashTable           *services;
+  guint                 watch_id;
+};
+
+static void epc_dispatcher_handle_collision (EpcDispatcher *self,
+                                             const gchar   *domain);
+static void epc_service_run                 (EpcService    *self);
+
+G_DEFINE_TYPE (EpcDispatcher, epc_dispatcher, G_TYPE_OBJECT);
+
+static gboolean
+epc_service_commit_cb (gpointer data)
+{
+  EpcService *self = data;
+
+  self->commit_handler = 0;
+  g_return_val_if_fail (NULL != self->group, FALSE);
+  avahi_entry_group_commit (self->group);
+
+  return FALSE;
+}
+
+static void
+epc_service_schedule_commit (EpcService *self)
+{
+  if (!self->commit_handler)
+    self->commit_handler = g_idle_add (epc_service_commit_cb, self);
+}
+
+static void
+epc_service_publish_subtype (EpcService  *self,
+                             const gchar *subtype)
+{
+  gint result;
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: Publishing sub-service `%s' for `%s'...",
+             G_STRLOC, subtype, self->dispatcher->priv->name);
+
+  result = avahi_entry_group_add_service_subtype (self->group,
+                                                  AVAHI_IF_UNSPEC,
+                                                  self->protocol, 0,
+                                                  self->dispatcher->priv->name,
+                                                  self->type, self->domain,
+                                                  subtype);
+
+  if (AVAHI_OK != result)
+    g_warning ("%s: Failed to publish sub-service `%s' for `%s': %s (%d)",
+               G_STRLOC, subtype, self->dispatcher->priv->name,
+               avahi_strerror (result), result);
+
+  epc_service_schedule_commit (self);
+}
+
+static void
+epc_service_publish_details (EpcService *self)
+{
+  gint result;
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: Publishing details for `%s'...",
+             G_STRLOC, self->dispatcher->priv->name);
+
+  result = avahi_entry_group_update_service_txt_strlst (self->group,
+                                                        AVAHI_IF_UNSPEC, self->protocol, 0,
+                                                        self->dispatcher->priv->name,
+                                                        self->type, self->domain,
+                                                        self->details);
+
+  if (AVAHI_OK != result)
+    g_warning ("%s: Failed publish details for `%s': %s (%d)",
+               G_STRLOC, self->dispatcher->priv->name,
+               avahi_strerror (result), result);
+
+  epc_service_schedule_commit (self);
+}
+
+static void
+epc_service_publish (EpcService *self)
+{
+  if (self->group)
+    {
+      gint result;
+      GList *iter;
+
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: Publishing service `%s' for `%s'...",
+                 G_STRLOC, self->type, self->dispatcher->priv->name);
+
+      result = avahi_entry_group_add_service_strlst (self->group,
+                                                     AVAHI_IF_UNSPEC, self->protocol, 0,
+                                                     self->dispatcher->priv->name,
+                                                     self->type, self->domain,
+                                                     self->host, self->port,
+                                                     self->details);
+
+      if (AVAHI_ERR_COLLISION == result)
+        epc_dispatcher_handle_collision (self->dispatcher, self->domain);
+      else if (AVAHI_OK != result)
+        g_warning ("%s: Failed to publish service `%s' for `%s': %s (%d)",
+                   G_STRLOC, self->type, self->dispatcher->priv->name,
+                   avahi_strerror (result), result);
+      else
+        {
+          for (iter = self->subtypes; iter; iter = iter->next)
+            epc_service_publish_subtype (self, iter->data);
+
+          epc_service_schedule_commit (self);
+        }
+    }
+  else
+    epc_service_run (self);
+}
+
+static void
+epc_service_reset (EpcService *self)
+{
+  if (self->group)
+    {
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: Resetting `%s' for `%s'...",
+                 G_STRLOC, self->type, self->dispatcher->priv->name);
+
+      avahi_entry_group_reset (self->group);
+    }
+  else
+    epc_service_run (self);
+}
+
+static void
+epc_service_group_cb (AvahiEntryGroup      *group,
+                      AvahiEntryGroupState  state,
+                      gpointer              data)
+{
+  EpcService *self = data;
+  GError *error = NULL;
+
+  if (self->group)
+    g_assert (group == self->group);
+  else
+    self->group = group;
+
+  switch (state)
+    {
+      case AVAHI_ENTRY_GROUP_REGISTERING:
+      case AVAHI_ENTRY_GROUP_ESTABLISHED:
+        break;
+
+      case AVAHI_ENTRY_GROUP_UNCOMMITED:
+        epc_service_publish (self);
+        break;
+
+      case AVAHI_ENTRY_GROUP_COLLISION:
+        epc_dispatcher_handle_collision (self->dispatcher, self->domain);
+        break;
+
+      case AVAHI_ENTRY_GROUP_FAILURE:
+        {
+          AvahiClient *client = avahi_entry_group_get_client (group);
+          gint error_code = avahi_client_errno (client);
+
+          g_warning ("%s: Failed to publish service records: %s.",
+                     G_STRFUNC, avahi_strerror (error_code));
+
+          epc_shell_restart_avahi_client (G_STRLOC);
+          break;
+        }
+
+      default:
+        g_warning ("%s: Unexpected state.", G_STRFUNC);
+        break;
+    }
+
+  g_clear_error (&error);
+}
+
+static void
+epc_service_run (EpcService *self)
+{
+  if (NULL == self->group)
+    {
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: Creating service `%s' group for `%s'...",
+                 G_STRLOC, self->type, self->dispatcher->priv->name);
+
+      epc_shell_create_avahi_entry_group (epc_service_group_cb, self);
+    }
+}
+
+static void
+epc_service_add_subtype (EpcService  *service,
+                         const gchar *subtype)
+{
+  service->subtypes = g_list_prepend (service->subtypes, g_strdup (subtype));
+}
+
+static EpcService*
+epc_service_new (EpcDispatcher *dispatcher,
+                 AvahiProtocol  protocol,
+                 const gchar   *type,
+                 const gchar   *domain,
+                 const gchar   *host,
+                 guint16        port,
+                 va_list        args)
+{
+  const gchar *service = epc_service_type_get_base (type);
+  EpcService *self = g_slice_new0 (EpcService);
+
+  self->dispatcher = dispatcher;
+  self->details = avahi_string_list_new_va (args);
+  self->type = g_strdup (service);
+  self->protocol = protocol;
+  self->port = port;
+
+  if (domain)
+    self->domain = g_strdup (domain);
+  if (host)
+    self->host = g_strdup (host);
+  if (service > type)
+    epc_service_add_subtype (self, type);
+
+  return self;
+}
+
+static void
+epc_service_suspend (EpcService *self)
+{
+  if (self->commit_handler)
+    {
+      g_source_remove (self->commit_handler);
+      self->commit_handler = 0;
+    }
+
+  if (self->group)
+    {
+      avahi_entry_group_free (self->group);
+      self->group = NULL;
+    }
+}
+
+static void
+epc_service_remove_detail (EpcService  *self,
+                           const gchar *key)
+{
+  AvahiStringList *curr = self->details;
+  AvahiStringList *prev = NULL;
+
+  gsize len = strlen(key);
+
+  while (curr)
+    {
+      if (!memcmp (curr->text, key, len) && '=' == curr->text[len])
+        {
+          AvahiStringList *next = curr->next;
+
+          curr->next = NULL;
+
+          if (!prev)
+            self->details = next;
+          else
+            prev->next = next;
+
+          avahi_string_list_free (curr);
+          curr = next;
+        }
+      else
+        curr = avahi_string_list_get_next (prev = curr);
+    }
+}
+
+static void
+epc_service_set_detail (EpcService  *self,
+                        const gchar *key,
+                        const gchar *value)
+{
+  epc_service_remove_detail (self, key);
+  self->details = avahi_string_list_add_pair (self->details, key, value);
+}
+
+static void
+epc_service_free (gpointer data)
+{
+  EpcService *self = data;
+
+  epc_service_suspend (self);
+
+  avahi_string_list_free (self->details);
+
+  g_list_foreach (self->subtypes, (GFunc)g_free, NULL);
+  g_list_free (self->subtypes);
+
+  g_free (self->type);
+  g_free (self->domain);
+  g_free (self->host);
+
+  g_slice_free (EpcService, self);
+}
+
+static void
+epc_dispatcher_services_cb (gpointer key G_GNUC_UNUSED,
+                            gpointer value,
+                            gpointer data)
+{
+  ((EpcServiceCallback) data) (value);
+}
+
+static void
+epc_dispatcher_foreach_service (EpcDispatcher      *self,
+                                EpcServiceCallback  callback)
+{
+  g_hash_table_foreach (self->priv->services, epc_dispatcher_services_cb, callback);
+}
+
+static void
+epc_dispatcher_client_cb (AvahiClient      *client G_GNUC_UNUSED,
+                          AvahiClientState  state,
+                          gpointer          data)
+{
+  EpcDispatcher *self = data;
+  GError *error = NULL;
+
+  switch (state)
+    {
+      case AVAHI_CLIENT_S_RUNNING:
+        if (EPC_DEBUG_LEVEL (1))
+          g_debug ("%s: Avahi client is running...", G_STRLOC);
+
+        epc_dispatcher_foreach_service (self, epc_service_publish);
+        break;
+
+      case AVAHI_CLIENT_S_REGISTERING:
+        if (EPC_DEBUG_LEVEL (1))
+          g_debug ("%s: Avahi client is registering...", G_STRLOC);
+
+        epc_dispatcher_foreach_service (self, epc_service_reset);
+        break;
+
+      case AVAHI_CLIENT_S_COLLISION:
+        if (EPC_DEBUG_LEVEL (1))
+          g_debug ("%s: Collision detected...", G_STRLOC);
+
+        epc_dispatcher_handle_collision (self, NULL);
+        break;
+
+      case AVAHI_CLIENT_FAILURE:
+        if (EPC_DEBUG_LEVEL (1))
+          g_debug ("%s: Suspending entry groups...", G_STRLOC);
+
+        epc_dispatcher_foreach_service (self, epc_service_suspend);
+        break;
+
+      case AVAHI_CLIENT_CONNECTING:
+        if (EPC_DEBUG_LEVEL (1))
+          g_debug ("%s: Waiting for Avahi server...", G_STRLOC);
+
+        break;
+
+      default:
+        g_warning ("%s: Unexpected state.", G_STRFUNC);
+        break;
+    }
+
+  g_clear_error (&error);
+}
+
+static void
+epc_dispatcher_change_name (EpcDispatcher *self)
+{
+  gchar *alternative = avahi_alternative_service_name (self->priv->name);
+
+  g_message ("%s: Service name collision for `%s', renaming to `%s'.",
+             G_STRFUNC, self->priv->name, alternative);
+
+  g_free (self->priv->name);
+  self->priv->name = alternative;
+  g_object_notify (G_OBJECT (self), "name");
+
+  epc_dispatcher_foreach_service (self, epc_service_publish);
+}
+
+static void
+epc_dispatcher_service_removed_cb (EpcServiceMonitor *monitor,
+                                   const gchar       *name,
+                                   const gchar       *type G_GNUC_UNUSED,
+                                   gpointer           data)
+{
+  EpcDispatcher *self = EPC_DISPATCHER (data);
+  g_return_if_fail (monitor == self->priv->monitor);
+
+  if (g_str_equal (name, self->priv->name))
+    {
+      g_message ("%s: Conflicting service for `%s' disappeared, republishing.",
+                 G_STRFUNC, self->priv->name);
+
+      g_object_unref (self->priv->monitor);
+      self->priv->monitor = NULL;
+
+      epc_dispatcher_foreach_service (self, epc_service_reset);
+    }
+}
+
+static void
+epc_dispatcher_service_found_cb (EpcServiceMonitor *monitor,
+                                 const gchar       *name,
+                                 EpcServiceInfo    *info,
+                                 gpointer           data)
+{
+  EpcDispatcher *self = EPC_DISPATCHER (data);
+  g_return_if_fail (monitor == self->priv->monitor);
+
+  if (g_str_equal (name, self->priv->name))
+    {
+      const gchar *cookie = epc_service_info_get_detail (info, "cookie");
+
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: foreign cookie: %s, own cookie: %s",
+                 G_STRFUNC, cookie, self->priv->cookie);
+
+      if (NULL == cookie || NULL == self->priv->cookie ||
+          strcmp (cookie, self->priv->cookie))
+        {
+          g_message ("%s: Conflicting service for `%s' has different cookie, "
+                     "resorting to rename strategy.", G_STRFUNC, self->priv->name);
+
+          g_signal_handlers_disconnect_by_func(monitor, epc_dispatcher_service_removed_cb, self);
+          g_signal_handlers_disconnect_by_func(monitor, epc_dispatcher_service_found_cb, self);
+
+          epc_dispatcher_change_name (self);
+        }
+    }
+}
+
+static void
+epc_dispatcher_get_service_types_cb (gpointer key,
+                                     gpointer value G_GNUC_UNUSED,
+                                     gpointer data)
+{
+  gchar ***types = data;
+  **types = key;
+  *types += 1;
+}
+
+static gchar**
+epc_dispatcher_get_service_types (EpcDispatcher *self)
+{
+  gchar **types, **iter;
+
+  types = iter = g_new0 (gchar*, g_hash_table_size (self->priv->services) + 1);
+  g_hash_table_foreach (self->priv->services, epc_dispatcher_get_service_types_cb, &iter);
+
+  return types;
+}
+
+static void
+epc_dispatcher_watch_other (EpcDispatcher *self,
+                            const gchar   *domain)
+{
+  gchar **types;
+
+  g_return_if_fail (NULL == self->priv->monitor);
+
+
+  types = epc_dispatcher_get_service_types (self);
+  self->priv->monitor = epc_service_monitor_new_for_types_strv (domain, types);
+  g_free (types);
+
+  g_signal_connect (self->priv->monitor, "service-found",
+                    G_CALLBACK (epc_dispatcher_service_found_cb),
+                    self);
+  g_signal_connect (self->priv->monitor, "service-removed",
+                    G_CALLBACK (epc_dispatcher_service_removed_cb),
+                    self);
+
+  g_message ("%s: Service name collision for `%s', "
+             "waiting for other service to disappear.",
+             G_STRFUNC, self->priv->name);
+}
+
+static void
+epc_dispatcher_handle_collision (EpcDispatcher *self,
+                                 const gchar   *domain)
+{
+  epc_dispatcher_foreach_service (self, epc_service_suspend);
+
+  switch (self->priv->collisions)
+    {
+      case EPC_COLLISIONS_IGNORE:
+        break; /* nothing to do */
+
+      case EPC_COLLISIONS_CHANGE_NAME:
+        epc_dispatcher_change_name (self);
+        break;
+
+      case EPC_COLLISIONS_UNIQUE_SERVICE:
+        epc_dispatcher_watch_other (self, domain);
+        break;
+
+      default:
+        g_warning ("%s: Unexpected collisions enum value.", G_STRFUNC);
+        break;
+    }
+}
+
+static void
+epc_dispatcher_init (EpcDispatcher *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                            EPC_TYPE_DISPATCHER,
+                                            EpcDispatcherPrivate);
+
+  self->priv->services = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                NULL, epc_service_free);
+}
+
+static void
+epc_dispatcher_set_cookie_cb (gpointer key G_GNUC_UNUSED,
+                              gpointer value,
+                              gpointer data)
+{
+  EpcService *service = value;
+  const gchar *cookie = data;
+
+  if (cookie)
+    epc_service_set_detail (service, "cookie", cookie);
+  else
+    epc_service_remove_detail (service, "cookie");
+
+  epc_service_reset (service);
+}
+
+static void
+epc_dispatcher_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  EpcDispatcher *self = EPC_DISPATCHER (object);
+
+  switch (prop_id)
+    {
+      case PROP_NAME:
+        g_return_if_fail (NULL != g_value_get_string (value));
+
+        g_free (self->priv->name);
+        self->priv->name = g_value_dup_string (value);
+
+        /* The reset also causes a transition into the UNCOMMITED state,
+         * which causes re-publication of the services.
+         */
+        epc_dispatcher_foreach_service (self, epc_service_reset);
+        break;
+
+      case PROP_COOKIE:
+        g_free (self->priv->cookie);
+        self->priv->cookie = g_value_dup_string (value);
+        g_hash_table_foreach (self->priv->services,
+                              epc_dispatcher_set_cookie_cb,
+                              self->priv->cookie);
+        break;
+
+      case PROP_COLLISION_HANDLING:
+        self->priv->collisions = g_value_get_enum (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static const gchar*
+epc_dispatcher_ensure_cookie (EpcDispatcher *self)
+{
+  if (EPC_COLLISIONS_UNIQUE_SERVICE == self->priv->collisions && !self->priv->cookie)
+    {
+      uuid_t cookie;
+
+      self->priv->cookie = g_new0 (gchar, 37);
+
+      uuid_generate_time (cookie);
+      uuid_unparse_lower (cookie, self->priv->cookie);
+
+      g_debug ("%s: generating service cookie: %s", G_STRLOC, self->priv->cookie);
+    }
+
+  return self->priv->cookie;
+}
+
+static void
+epc_dispatcher_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  EpcDispatcher *self = EPC_DISPATCHER (object);
+
+  switch (prop_id)
+    {
+      case PROP_NAME:
+        g_value_set_string (value, self->priv->name);
+        break;
+
+      case PROP_COOKIE:
+        g_value_set_string (value, epc_dispatcher_ensure_cookie (self));
+        break;
+
+      case PROP_COLLISION_HANDLING:
+        g_value_set_enum (value, self->priv->collisions);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+epc_dispatcher_dispose (GObject *object)
+{
+  EpcDispatcher *self = EPC_DISPATCHER (object);
+
+  if (self->priv->monitor)
+    {
+      g_object_unref (self->priv->monitor);
+      self->priv->monitor = NULL;
+    }
+
+  if (self->priv->services)
+    {
+      g_hash_table_unref (self->priv->services);
+      self->priv->services = NULL;
+    }
+
+  if (self->priv->watch_id)
+    {
+      epc_shell_watch_remove (self->priv->watch_id);
+      self->priv->watch_id = 0;
+    }
+
+  g_free (self->priv->name);
+  self->priv->name = NULL;
+
+  g_free (self->priv->cookie);
+  self->priv->cookie = NULL;
+
+  G_OBJECT_CLASS (epc_dispatcher_parent_class)->dispose (object);
+}
+
+static void
+epc_dispatcher_class_init (EpcDispatcherClass *cls)
+{
+  GObjectClass *oclass = G_OBJECT_CLASS (cls);
+
+  oclass->set_property = epc_dispatcher_set_property;
+  oclass->get_property = epc_dispatcher_get_property;
+  oclass->dispose = epc_dispatcher_dispose;
+
+  g_object_class_install_property (oclass, PROP_NAME,
+                                   g_param_spec_string ("name", "Name",
+                                                        "User friendly name of the service", NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  /**
+   * EpcConsumer:cookie:
+   *
+   * Unique identifier of the service. This cookie is used for implementing
+   * #EPC_COLLISIONS_UNIQUE_SERVICE, and usually is a UUID or the MD5/SHA1/...
+   * checksum of a central document. When passing %NULL, but using the
+   * #EPC_COLLISIONS_UNIQUE_SERVICE strategy a time based UUID is
+   * generated and used as service identifier.
+   *
+   * Since: 0.3.1
+   */
+  g_object_class_install_property (oclass, PROP_COOKIE,
+                                   g_param_spec_string ("cookie", "Cookie",
+                                                        "Unique identifier of the service",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  /**
+   * EpcConsume:collision-handling:
+   *
+   * The collision handling method to use.
+   *
+   * Since: 0.3.1
+   */
+  g_object_class_install_property (oclass, PROP_COLLISION_HANDLING,
+                                   g_param_spec_enum ("collision-handling", "Collision Handling",
+                                                      "The collision handling method to use",
+                                                      EPC_TYPE_COLLISION_HANDLING,
+                                                      EPC_COLLISIONS_CHANGE_NAME,
+                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                      G_PARAM_STATIC_BLURB));
+
+  g_type_class_add_private (cls, sizeof (EpcDispatcherPrivate));
+}
+
+/**
+ * epc_dispatcher_new:
+ * @name: the human friendly name of the service
+ *
+ * Creates a new #EpcDispatcher object for announcing a DNS-SD service.
+ * The service is announced on all network interfaces.
+ *
+ * Call epc_dispatcher_add_service() to actually announce a service.
+ *
+ * Returns: the newly created #EpcDispatcher object.
+ */
+EpcDispatcher*
+epc_dispatcher_new (const gchar *name)
+{
+  return g_object_new (EPC_TYPE_DISPATCHER, "name", name, NULL);
+}
+
+/**
+ * epc_dispatcher_run:
+ * @dispatcher: a #EpcDispatcher
+ * @error: return location for a #GError, or %NULL
+ *
+ * Starts the <citetitle>Avahi</citetitle> client of the #EpcDispatcher. If the
+ * client was not started, the function returns %FALSE and sets @error. The
+ * error domain is #EPC_AVAHI_ERROR. Possible error codes are those of the
+ * <citetitle>Avahi</citetitle> library.
+ *
+ * Returns: %TRUE when the dispatcher was started successfully,
+ * %FALSE if an error occurred.
+ */
+gboolean
+epc_dispatcher_run (EpcDispatcher  *self,
+                    GError        **error)
+{
+  g_return_val_if_fail (EPC_IS_DISPATCHER (self), FALSE);
+  g_return_val_if_fail (0 == self->priv->watch_id, FALSE);
+
+  self->priv->watch_id =
+    epc_shell_watch_avahi_client_state (epc_dispatcher_client_cb,
+                                        self, NULL, error);
+
+  return (0 != self->priv->watch_id);
+}
+
+/**
+ * epc_dispatcher_reset:
+ * @dispatcher: a #EpcDispatcher
+ *
+ * Revokes all service announcements of this #EpcDispatcher.
+ */
+void
+epc_dispatcher_reset (EpcDispatcher *self)
+{
+  g_return_if_fail (EPC_IS_DISPATCHER (self));
+  g_hash_table_remove_all (self->priv->services);
+}
+
+/**
+ * epc_dispatcher_add_service:
+ * @dispatcher: a #EpcDispatcher
+ * @protocol: the #EpcAddressFamily this service supports
+ * @type: the machine friendly name of the service
+ * @domain: the DNS domain for the announcement, or %NULL
+ * @host: the fully qualified host name of the service, or %NULL
+ * @port: the TCP/IP port of the service
+ * @...: an optional list of TXT records, terminated by %NULL
+ *
+ * Announces a TCP/IP service via DNS-SD.
+ *
+ * The service @type shall be a well-known DNS-SD service type as listed on
+ * <ulink url="http://www.dns-sd.org/ServiceTypes.html"; />. This function tries
+ * to announce both the base service type and the sub service type when the
+ * service name contains more than just one dot: Passing "_anon._sub._ftp._tcp"
+ * for @type will announce the services "_ftp._tcp" and "_anon._sub._ftp._tcp".
+ *
+ * The function can be called more than once. Is this necessary when the server
+ * provides different access methods. For instance a web server could provide
+ * HTTP and encrypted HTTPS services at the same time. Calling this function
+ * multiple times also is useful for servers providing the same service at
+ * different, but not all network interfaces of the host.
+ *
+ * When passing %NULL for @domain, the service is announced within the local
+ * network only, otherwise it is announced at the specified DNS domain. The
+ * responsible server must be <ulink url="http://www.dns-sd.org/ServerSetup.html";>
+ * configured to support DNS-SD</ulink>.
+ *
+ * Pass %NULL for @host to use the official host name of the machine to announce
+ * the service. On machines with multiple DNS entries you might want to explictly
+ * choose a fully qualified DNS name to announce the service.
+ */
+void
+epc_dispatcher_add_service (EpcDispatcher    *self,
+                            EpcAddressFamily  protocol,
+                            const gchar      *type,
+                            const gchar      *domain,
+                            const gchar      *host,
+                            guint16           port,
+                                              ...)
+{
+  EpcService *service;
+  va_list args;
+
+  g_return_if_fail (EPC_IS_DISPATCHER (self));
+  g_return_if_fail (port > 0);
+
+  g_return_if_fail (NULL != type);
+  g_return_if_fail (type == epc_service_type_get_base (type));
+  g_return_if_fail (NULL == g_hash_table_lookup (self->priv->services, type));
+
+  va_start (args, port);
+
+  service = epc_service_new (self, avahi_af_to_proto (protocol),
+                             type, domain, host, port, args);
+
+  va_end (args);
+
+  if (epc_dispatcher_ensure_cookie (self))
+    epc_service_set_detail (service, "cookie", self->priv->cookie);
+
+  g_hash_table_insert (self->priv->services, service->type, service);
+
+  if (self->priv->watch_id)
+    epc_service_run (service);
+}
+
+/**
+ * epc_dispatcher_add_service_subtype:
+ * @dispatcher: a #EpcDispatcher
+ * @type: the base service type
+ * @subtype: the sub service type
+ *
+ * Announces an additional sub service for a registered DNS-SD service.
+ *
+ * <note><para>
+ * This function will fail silently, when the service specified by
+ * @type hasn't been registered yet.
+ * </para></note>
+ */
+void
+epc_dispatcher_add_service_subtype (EpcDispatcher *self,
+                                    const gchar   *type,
+                                    const gchar   *subtype)
+{
+  EpcService *service;
+
+  g_return_if_fail (EPC_IS_DISPATCHER (self));
+  g_return_if_fail (NULL != subtype);
+  g_return_if_fail (NULL != type);
+
+  service = g_hash_table_lookup (self->priv->services, type);
+
+  g_return_if_fail (NULL != service);
+
+  epc_service_add_subtype (service, subtype);
+
+  if (self->priv->watch_id && service->group)
+    epc_service_publish_subtype (service, subtype);
+}
+
+/**
+ * epc_dispatcher_set_service_details:
+ * @dispatcher: a #EpcDispatcher
+ * @type: the service type
+ * @...: a list of TXT records, terminated by %NULL
+ *
+ * Updates the list of TXT records for a registered DNS-SD service.
+ * The TXT records are specified by the service type and usually
+ * have the form of key-value pairs:
+ *
+ * <informalexample><programlisting>
+ *  path=/dwarf-blog/
+ * </programlisting></informalexample>
+ *
+ * <note><para>
+ * This function will fail silently, when the service specified by
+ * @type hasn't been registered yet.
+ * </para></note>
+ */
+void
+epc_dispatcher_set_service_details (EpcDispatcher *self,
+                                    const gchar   *type,
+                                                   ...)
+{
+  EpcService *service;
+  va_list args;
+
+  g_return_if_fail (EPC_IS_DISPATCHER (self));
+  g_return_if_fail (NULL != type);
+
+  service = g_hash_table_lookup (self->priv->services, type);
+
+  g_return_if_fail (NULL != service);
+
+  va_start (args, type);
+  avahi_string_list_free (service->details);
+  service->details = avahi_string_list_new_va (args);
+  va_end (args);
+
+  epc_service_publish_details (service);
+}
+
+/**
+ * epc_dispatcher_set_name:
+ * @dispatcher: a #EpcDispatcher
+ * @name: the new user friendly name
+ *
+ * Changes the user friendly name used for announcing services.
+ * See #EpcDispatcher:name.
+ */
+void
+epc_dispatcher_set_name (EpcDispatcher *self,
+                         const gchar   *name)
+{
+  g_return_if_fail (EPC_IS_DISPATCHER (self));
+  g_object_set (self, "name", name, NULL);
+}
+
+/**
+ * epc_dispatcher_set_cookie:
+ * @dispatcher: a #EpcDispatcher
+ * @cookie: the new service identifier, or %NULL
+ *
+ * Changes the unique identifier of the service.
+ * See #EpcDispatcher:cookie for details.
+ *
+ * Since: 0.3.1
+ */
+void
+epc_dispatcher_set_cookie (EpcDispatcher *self,
+                           const gchar   *cookie)
+{
+  g_return_if_fail (EPC_IS_DISPATCHER (self));
+  g_object_set (self, "cookie", cookie, NULL);
+}
+
+/**
+ * epc_dispatcher_set_collision_handling:
+ * @dispatcher: a #EpcDispatcher
+ * @method: the new strategy
+ *
+ * Changes the collision handling strategy the dispatcher uses.
+ * See #EpcDispatcher:collision-handling for details.
+ *
+ * Since: 0.3.1
+ */
+void
+epc_dispatcher_set_collision_handling (EpcDispatcher       *self,
+                                       EpcCollisionHandling method)
+{
+  g_return_if_fail (EPC_IS_DISPATCHER (self));
+  g_object_set (self, "collision-handling", method, NULL);
+}
+
+/**
+ * epc_dispatcher_get_name:
+ * @dispatcher: a #EpcDispatcher
+ *
+ * Queries the user friendly name used for announcing services.
+ * See #EpcDispatcher:name.
+ *
+ * Returns: The user friendly name of the service.
+ */
+const gchar*
+epc_dispatcher_get_name (EpcDispatcher *self)
+{
+  g_return_val_if_fail (EPC_IS_DISPATCHER (self), NULL);
+  return self->priv->name;
+}
+
+/**
+ * epc_dispatcher_get_cookie:
+ * @dispatcher: a #EpcDispatcher
+ *
+ * Queries the unique identifier of the service.
+ * See #EpcDispatcher:cookie for details.
+ *
+ * Returns: The unique identifier of the service, or %NULL on error.
+ * Since: 0.3.1
+ */
+const gchar*
+epc_dispatcher_get_cookie (EpcDispatcher *self)
+{
+  g_return_val_if_fail (EPC_IS_DISPATCHER (self), NULL);
+  return epc_dispatcher_ensure_cookie (self);
+}
+
+/**
+ * epc_dispatcher_get_collision_handling:
+ * @dispatcher: a #EpcDispatcher
+ *
+ * Queries the collision handling strategy the dispatcher uses.
+ * See #EpcDispatcher:collision-handling for details.
+ *
+ * Returns: The dispatcher's collision handling strategy,
+ * or #EPC_COLLISIONS_IGNORE on error.
+ * Since: 0.3.1
+ */
+EpcCollisionHandling
+epc_dispatcher_get_collision_handling (EpcDispatcher *self)
+{
+  g_return_val_if_fail (EPC_IS_DISPATCHER (self), EPC_COLLISIONS_IGNORE);
+  return self->priv->collisions;
+}
+
+
diff --git a/glom/libglom/libepc/dispatcher.h b/glom/libglom/libepc/dispatcher.h
new file mode 100644
index 0000000..8c7e8a2
--- /dev/null
+++ b/glom/libglom/libepc/dispatcher.h
@@ -0,0 +1,119 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+#ifndef __EPC_DISPATCHER_H__
+#define __EPC_DISPATCHER_H__
+
+#include <libepc/service-info.h>
+
+G_BEGIN_DECLS
+
+#define EPC_TYPE_DISPATCHER           (epc_dispatcher_get_type())
+#define EPC_DISPATCHER(obj)           (G_TYPE_CHECK_INSTANCE_CAST(obj, EPC_TYPE_DISPATCHER, EpcDispatcher))
+#define EPC_DISPATCHER_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST(cls, EPC_TYPE_DISPATCHER, EpcDispatcherClass))
+#define EPC_IS_DISPATCHER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE(obj, EPC_TYPE_DISPATCHER))
+#define EPC_IS_DISPATCHER_CLASS(obj)  (G_TYPE_CHECK_CLASS_TYPE(obj, EPC_TYPE_DISPATCHER))
+#define EPC_DISPATCHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPC_TYPE_DISPATCHER, 
EpcDispatcherClass))
+
+typedef struct _EpcDispatcher        EpcDispatcher;
+typedef struct _EpcDispatcherClass   EpcDispatcherClass;
+typedef struct _EpcDispatcherPrivate EpcDispatcherPrivate;
+
+/**
+ * EpcCollisionHandling:
+ * @EPC_COLLISIONS_IGNORE: Don't handle collisions at all, just fail silently.
+ * @EPC_COLLISIONS_CHANGE_NAME: Try to announce the service with another name.
+ * @EPC_COLLISIONS_UNIQUE_SERVICE: Defer own service announcement until the other service.
+ * disappears.
+ *
+ * Various strategies for handling service name collisions.
+ */
+typedef enum
+{
+  EPC_COLLISIONS_IGNORE,
+  EPC_COLLISIONS_CHANGE_NAME,
+  EPC_COLLISIONS_UNIQUE_SERVICE
+}
+EpcCollisionHandling;
+
+/**
+ * EpcDispatcher:
+ *
+ * Public fields of the #EpcDispatcher class.
+ */
+struct _EpcDispatcher
+{
+  /*< private >*/
+  GObject parent_instance;
+  EpcDispatcherPrivate *priv;
+
+  /*< public >*/
+};
+
+/**
+ * EpcDispatcherClass:
+ *
+ * Virtual methods of the #EpcDispatcher class.
+ */
+struct _EpcDispatcherClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+};
+
+GType                 epc_dispatcher_get_type               (void) G_GNUC_CONST;
+
+EpcDispatcher*        epc_dispatcher_new                    (const gchar          *name);
+gboolean              epc_dispatcher_run                    (EpcDispatcher        *dispatcher,
+                                                             GError              **error);
+void                  epc_dispatcher_reset                  (EpcDispatcher        *dispatcher);
+
+void                  epc_dispatcher_add_service            (EpcDispatcher        *dispatcher,
+                                                             EpcAddressFamily      protocol,
+                                                             const gchar          *type,
+                                                             const gchar          *domain,
+                                                             const gchar          *host,
+                                                             guint16               port,
+                                                                                   ...)
+                                                             G_GNUC_NULL_TERMINATED;
+void                  epc_dispatcher_add_service_subtype    (EpcDispatcher        *dispatcher,
+                                                             const gchar          *type,
+                                                             const gchar          *subtype);
+void                  epc_dispatcher_set_service_details    (EpcDispatcher        *dispatcher,
+                                                             const gchar          *type,
+                                                                                   ...)
+                                                             G_GNUC_NULL_TERMINATED;
+
+void                  epc_dispatcher_set_name               (EpcDispatcher        *dispatcher,
+                                                             const gchar          *name);
+void                  epc_dispatcher_set_cookie             (EpcDispatcher        *dispatcher,
+                                                             const gchar          *cookie);
+void                  epc_dispatcher_set_collision_handling (EpcDispatcher        *dispatcher,
+                                                             EpcCollisionHandling  method);
+
+const gchar* epc_dispatcher_get_name               (EpcDispatcher        *dispatcher);
+EpcCollisionHandling  epc_dispatcher_get_collision_handling (EpcDispatcher        *dispatcher);
+const gchar* epc_dispatcher_get_cookie             (EpcDispatcher        *dispatcher);
+
+G_END_DECLS
+
+#endif /* __EPC_DISPATCHER_H__ */
diff --git a/glom/libglom/libepc/enums.c b/glom/libglom/libepc/enums.c
new file mode 100644
index 0000000..331e39e
--- /dev/null
+++ b/glom/libglom/libepc/enums.c
@@ -0,0 +1,258 @@
+
+/* Generated data (by glib-mkenums) */
+
+#include "enums.h"
+/* Generated by glib-mkenums from "./libepc/dispatcher.h" */
+
+#define __EPC_COLLISION_HANDLING_IS_ENUM__ 1
+
+GType
+epc_collision_handling_get_type (void)
+{
+  static GType etype = G_TYPE_INVALID;
+
+  if (G_UNLIKELY (G_TYPE_INVALID == etype))
+    {
+      static const GEnumValue values[] =
+        {
+          { EPC_COLLISIONS_IGNORE, "EPC_COLLISIONS_IGNORE", "ignore" },
+          { EPC_COLLISIONS_CHANGE_NAME, "EPC_COLLISIONS_CHANGE_NAME", "change-name" },
+          { EPC_COLLISIONS_UNIQUE_SERVICE, "EPC_COLLISIONS_UNIQUE_SERVICE", "unique-service" },
+          { 0, NULL, NULL }
+        };
+
+      etype = g_enum_register_static (g_intern_static_string ("EpcCollisionHandling"), values);
+    }
+
+  return etype;
+}
+
+/**
+ * epc_collision_handling_get_class:
+ *
+ * Retrieves the GEnumClass describing the EpcCollisionHandling enum.
+ *
+ * Returns: The GEnumClass describing EpcCollisionHandling.
+ */
+GEnumClass*
+epc_collision_handling_get_class (void)
+{
+  static GEnumClass *enum_class = NULL;
+
+  if (G_UNLIKELY (NULL == enum_class))
+    enum_class = g_type_class_ref (epc_collision_handling_get_type ());
+
+  return enum_class;
+}
+
+#if __EPC_COLLISION_HANDLING_IS_ENUM__
+
+/**
+ * epc_collision_handling_to_string:
+ * @value: a EpcCollisionHandling value
+ *
+ * Retrieves the name of a EpcCollisionHandling @value, or %NULL when @value is invalid.
+ *
+ * Returns: The string representation of @value, or %NULL.
+ */
+const gchar*
+epc_collision_handling_to_string (EpcCollisionHandling value)
+{
+  const GEnumValue *enum_value = g_enum_get_value (epc_collision_handling_get_class (), value);
+
+  g_return_val_if_fail (NULL != enum_value, NULL);
+  return enum_value->value_name;
+}
+
+#endif
+/* Generated by glib-mkenums from "./libepc/protocol.h" */
+
+#define __EPC_PROTOCOL_IS_ENUM__ 1
+
+GType
+epc_protocol_get_type (void)
+{
+  static GType etype = G_TYPE_INVALID;
+
+  if (G_UNLIKELY (G_TYPE_INVALID == etype))
+    {
+      static const GEnumValue values[] =
+        {
+          { EPC_PROTOCOL_UNKNOWN, "EPC_PROTOCOL_UNKNOWN", "unknown" },
+          { EPC_PROTOCOL_HTTP, "EPC_PROTOCOL_HTTP", "http" },
+          { EPC_PROTOCOL_HTTPS, "EPC_PROTOCOL_HTTPS", "https" },
+          { 0, NULL, NULL }
+        };
+
+      etype = g_enum_register_static (g_intern_static_string ("EpcProtocol"), values);
+    }
+
+  return etype;
+}
+
+/**
+ * epc_protocol_get_class:
+ *
+ * Retrieves the GEnumClass describing the EpcProtocol enum.
+ *
+ * Returns: The GEnumClass describing EpcProtocol.
+ */
+GEnumClass*
+epc_protocol_get_class (void)
+{
+  static GEnumClass *enum_class = NULL;
+
+  if (G_UNLIKELY (NULL == enum_class))
+    enum_class = g_type_class_ref (epc_protocol_get_type ());
+
+  return enum_class;
+}
+
+#if __EPC_PROTOCOL_IS_ENUM__
+
+/**
+ * epc_protocol_to_string:
+ * @value: a EpcProtocol value
+ *
+ * Retrieves the name of a EpcProtocol @value, or %NULL when @value is invalid.
+ *
+ * Returns: The string representation of @value, or %NULL.
+ */
+const gchar*
+epc_protocol_to_string (EpcProtocol value)
+{
+  const GEnumValue *enum_value = g_enum_get_value (epc_protocol_get_class (), value);
+
+  g_return_val_if_fail (NULL != enum_value, NULL);
+  return enum_value->value_name;
+}
+
+#endif
+/* Generated by glib-mkenums from "./libepc/publisher.h" */
+
+#define __EPC_AUTH_FLAGS_IS_FLAGS__ 1
+
+GType
+epc_auth_flags_get_type (void)
+{
+  static GType etype = G_TYPE_INVALID;
+
+  if (G_UNLIKELY (G_TYPE_INVALID == etype))
+    {
+      static const GFlagsValue values[] =
+        {
+          { EPC_AUTH_DEFAULT, "EPC_AUTH_DEFAULT", "default" },
+          { EPC_AUTH_PASSWORD_TEXT_NEEDED, "EPC_AUTH_PASSWORD_TEXT_NEEDED", "password-text-needed" },
+          { 0, NULL, NULL }
+        };
+
+      etype = g_flags_register_static (g_intern_static_string ("EpcAuthFlags"), values);
+    }
+
+  return etype;
+}
+
+/**
+ * epc_auth_flags_get_class:
+ *
+ * Retrieves the GFlagsClass describing the EpcAuthFlags flags.
+ *
+ * Returns: The GFlagsClass describing EpcAuthFlags.
+ */
+GFlagsClass*
+epc_auth_flags_get_class (void)
+{
+  static GFlagsClass *flags_class = NULL;
+
+  if (G_UNLIKELY (NULL == flags_class))
+    flags_class = g_type_class_ref (epc_auth_flags_get_type ());
+
+  return flags_class;
+}
+
+#if __EPC_AUTH_FLAGS_IS_ENUM__
+
+/**
+ * epc_auth_flags_to_string:
+ * @value: a EpcAuthFlags value
+ *
+ * Retrieves the name of a EpcAuthFlags @value, or %NULL when @value is invalid.
+ *
+ * Returns: The string representation of @value, or %NULL.
+ */
+const gchar*
+epc_auth_flags_to_string (EpcAuthFlags value)
+{
+  const GFlagsValue *flags_value = g_flags_get_value (epc_auth_flags_get_class (), value);
+
+  g_return_val_if_fail (NULL != flags_value, NULL);
+  return flags_value->value_name;
+}
+
+#endif
+/* Generated by glib-mkenums from "./libepc/service-info.h" */
+
+#define __EPC_ADDRESS_FAMILY_IS_ENUM__ 1
+
+GType
+epc_address_family_get_type (void)
+{
+  static GType etype = G_TYPE_INVALID;
+
+  if (G_UNLIKELY (G_TYPE_INVALID == etype))
+    {
+      static const GEnumValue values[] =
+        {
+          { EPC_ADDRESS_UNSPEC, "EPC_ADDRESS_UNSPEC", "unspec" },
+          { EPC_ADDRESS_IPV4, "EPC_ADDRESS_IPV4", "ipv4" },
+          { EPC_ADDRESS_IPV6, "EPC_ADDRESS_IPV6", "ipv6" },
+          { 0, NULL, NULL }
+        };
+
+      etype = g_enum_register_static (g_intern_static_string ("EpcAddressFamily"), values);
+    }
+
+  return etype;
+}
+
+/**
+ * epc_address_family_get_class:
+ *
+ * Retrieves the GEnumClass describing the EpcAddressFamily enum.
+ *
+ * Returns: The GEnumClass describing EpcAddressFamily.
+ */
+GEnumClass*
+epc_address_family_get_class (void)
+{
+  static GEnumClass *enum_class = NULL;
+
+  if (G_UNLIKELY (NULL == enum_class))
+    enum_class = g_type_class_ref (epc_address_family_get_type ());
+
+  return enum_class;
+}
+
+#if __EPC_ADDRESS_FAMILY_IS_ENUM__
+
+/**
+ * epc_address_family_to_string:
+ * @value: a EpcAddressFamily value
+ *
+ * Retrieves the name of a EpcAddressFamily @value, or %NULL when @value is invalid.
+ *
+ * Returns: The string representation of @value, or %NULL.
+ */
+const gchar*
+epc_address_family_to_string (EpcAddressFamily value)
+{
+  const GEnumValue *enum_value = g_enum_get_value (epc_address_family_get_class (), value);
+
+  g_return_val_if_fail (NULL != enum_value, NULL);
+  return enum_value->value_name;
+}
+
+#endif
+
+/* Generated data ends here */
+
diff --git a/glom/libglom/libepc/enums.h b/glom/libglom/libepc/enums.h
new file mode 100644
index 0000000..c7b103c
--- /dev/null
+++ b/glom/libglom/libepc/enums.h
@@ -0,0 +1,44 @@
+
+/* Generated data (by glib-mkenums) */
+
+#ifndef __EPC_ENUMS_H__
+#define __EPC_ENUMS_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+/* Generated by glib-mkenums from <libglom/libepc/dispatcher.h> */
+#include <libglom/libepc/dispatcher.h>
+#define EPC_TYPE_COLLISION_HANDLING (epc_collision_handling_get_type())
+
+GType                 epc_collision_handling_get_type  (void) G_GNUC_CONST;
+GEnumClass*           epc_collision_handling_get_class (void) G_GNUC_CONST;
+const gchar* epc_collision_handling_to_string (EpcCollisionHandling value) G_GNUC_PURE;
+/* Generated by glib-mkenums from <libglom/libepc/protocol.h> */
+#include <libglom/libepc/protocol.h>
+#define EPC_TYPE_PROTOCOL (epc_protocol_get_type())
+
+GType                 epc_protocol_get_type  (void) G_GNUC_CONST;
+GEnumClass*           epc_protocol_get_class (void) G_GNUC_CONST;
+const gchar* epc_protocol_to_string (EpcProtocol value) G_GNUC_PURE;
+/* Generated by glib-mkenums from <libglom/libepc/publisher.h> */
+#include <libglom/libepc/publisher.h>
+#define EPC_TYPE_AUTH_FLAGS (epc_auth_flags_get_type())
+
+GType                 epc_auth_flags_get_type  (void) G_GNUC_CONST;
+GFlagsClass*           epc_auth_flags_get_class (void) G_GNUC_CONST;
+const gchar* epc_auth_flags_to_string (EpcAuthFlags value) G_GNUC_PURE;
+/* Generated by glib-mkenums from <libglom/libepc/service-info.h> */
+#include <libglom/libepc/service-info.h>
+#define EPC_TYPE_ADDRESS_FAMILY (epc_address_family_get_type())
+
+GType                 epc_address_family_get_type  (void) G_GNUC_CONST;
+GEnumClass*           epc_address_family_get_class (void) G_GNUC_CONST;
+const gchar* epc_address_family_to_string (EpcAddressFamily value) G_GNUC_PURE;
+
+G_END_DECLS
+
+#endif /* __EPC_ENUMS_H__ */
+
+/* Generated data ends here */
+
diff --git a/glom/libglom/libepc/marshal.c b/glom/libglom/libepc/marshal.c
new file mode 100644
index 0000000..43e431d
--- /dev/null
+++ b/glom/libglom/libepc/marshal.c
@@ -0,0 +1,202 @@
+/* Generated by glib-genmarshal from libepc/marshal.list */
+#include "marshal.h"
+#include <glib-object.h>
+
+#ifdef G_ENABLE_DEBUG
+#define g_marshal_value_peek_boolean(v)  g_value_get_boolean (v)
+#define g_marshal_value_peek_char(v)     g_value_get_schar (v)
+#define g_marshal_value_peek_uchar(v)    g_value_get_uchar (v)
+#define g_marshal_value_peek_int(v)      g_value_get_int (v)
+#define g_marshal_value_peek_uint(v)     g_value_get_uint (v)
+#define g_marshal_value_peek_long(v)     g_value_get_long (v)
+#define g_marshal_value_peek_ulong(v)    g_value_get_ulong (v)
+#define g_marshal_value_peek_int64(v)    g_value_get_int64 (v)
+#define g_marshal_value_peek_uint64(v)   g_value_get_uint64 (v)
+#define g_marshal_value_peek_enum(v)     g_value_get_enum (v)
+#define g_marshal_value_peek_flags(v)    g_value_get_flags (v)
+#define g_marshal_value_peek_float(v)    g_value_get_float (v)
+#define g_marshal_value_peek_double(v)   g_value_get_double (v)
+#define g_marshal_value_peek_string(v)   (char*) g_value_get_string (v)
+#define g_marshal_value_peek_param(v)    g_value_get_param (v)
+#define g_marshal_value_peek_boxed(v)    g_value_get_boxed (v)
+#define g_marshal_value_peek_pointer(v)  g_value_get_pointer (v)
+#define g_marshal_value_peek_object(v)   g_value_get_object (v)
+#define g_marshal_value_peek_variant(v)  g_value_get_variant (v)
+#else /* !G_ENABLE_DEBUG */
+/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
+ *          Do not access GValues directly in your code. Instead, use the
+ *          g_value_get_*() functions
+ */
+#define g_marshal_value_peek_boolean(v)  (v)->data[0].v_int
+#define g_marshal_value_peek_char(v)     (v)->data[0].v_int
+#define g_marshal_value_peek_uchar(v)    (v)->data[0].v_uint
+#define g_marshal_value_peek_int(v)      (v)->data[0].v_int
+#define g_marshal_value_peek_uint(v)     (v)->data[0].v_uint
+#define g_marshal_value_peek_long(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_ulong(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_int64(v)    (v)->data[0].v_int64
+#define g_marshal_value_peek_uint64(v)   (v)->data[0].v_uint64
+#define g_marshal_value_peek_enum(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_flags(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_float(v)    (v)->data[0].v_float
+#define g_marshal_value_peek_double(v)   (v)->data[0].v_double
+#define g_marshal_value_peek_string(v)   (v)->data[0].v_pointer
+#define g_marshal_value_peek_param(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_boxed(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_pointer(v)  (v)->data[0].v_pointer
+#define g_marshal_value_peek_object(v)   (v)->data[0].v_pointer
+#define g_marshal_value_peek_variant(v)  (v)->data[0].v_pointer
+#endif /* !G_ENABLE_DEBUG */
+
+/* VOID:ENUM,STRING,UINT (libepc/marshal.list:1) */
+void
+_epc_marshal_VOID__ENUM_STRING_UINT (GClosure     *closure,
+                                     GValue       *return_value G_GNUC_UNUSED,
+                                     guint         n_param_values,
+                                     const GValue *param_values,
+                                     gpointer      invocation_hint G_GNUC_UNUSED,
+                                     gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__ENUM_STRING_UINT) (gpointer data1,
+                                                       gint arg1,
+                                                       gpointer arg2,
+                                                       guint arg3,
+                                                       gpointer data2);
+  GCClosure *cc = (GCClosure *) closure;
+  gpointer data1, data2;
+  GMarshalFunc_VOID__ENUM_STRING_UINT callback;
+
+  g_return_if_fail (n_param_values == 4);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__ENUM_STRING_UINT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_enum (param_values + 1),
+            g_marshal_value_peek_string (param_values + 2),
+            g_marshal_value_peek_uint (param_values + 3),
+            data2);
+}
+
+/* BOOLEAN:STRING (libepc/marshal.list:2) */
+void
+_epc_marshal_BOOLEAN__STRING (GClosure     *closure,
+                              GValue       *return_value,
+                              guint         n_param_values,
+                              const GValue *param_values,
+                              gpointer      invocation_hint G_GNUC_UNUSED,
+                              gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__STRING) (gpointer data1,
+                                                    gpointer arg1,
+                                                    gpointer data2);
+  GCClosure *cc = (GCClosure *) closure;
+  gpointer data1, data2;
+  GMarshalFunc_BOOLEAN__STRING callback;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 2);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__STRING) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_string (param_values + 1),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* VOID:STRING,BOXED (libepc/marshal.list:3) */
+void
+_epc_marshal_VOID__STRING_BOXED (GClosure     *closure,
+                                 GValue       *return_value G_GNUC_UNUSED,
+                                 guint         n_param_values,
+                                 const GValue *param_values,
+                                 gpointer      invocation_hint G_GNUC_UNUSED,
+                                 gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__STRING_BOXED) (gpointer data1,
+                                                   gpointer arg1,
+                                                   gpointer arg2,
+                                                   gpointer data2);
+  GCClosure *cc = (GCClosure *) closure;
+  gpointer data1, data2;
+  GMarshalFunc_VOID__STRING_BOXED callback;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__STRING_BOXED) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_string (param_values + 1),
+            g_marshal_value_peek_boxed (param_values + 2),
+            data2);
+}
+
+/* VOID:STRING,STRING (libepc/marshal.list:4) */
+void
+_epc_marshal_VOID__STRING_STRING (GClosure     *closure,
+                                  GValue       *return_value G_GNUC_UNUSED,
+                                  guint         n_param_values,
+                                  const GValue *param_values,
+                                  gpointer      invocation_hint G_GNUC_UNUSED,
+                                  gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__STRING_STRING) (gpointer data1,
+                                                    gpointer arg1,
+                                                    gpointer arg2,
+                                                    gpointer data2);
+  GCClosure *cc = (GCClosure *) closure;
+  gpointer data1, data2;
+  GMarshalFunc_VOID__STRING_STRING callback;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__STRING_STRING) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_string (param_values + 1),
+            g_marshal_value_peek_string (param_values + 2),
+            data2);
+}
+
diff --git a/glom/libglom/libepc/marshal.h b/glom/libglom/libepc/marshal.h
new file mode 100644
index 0000000..8748d67
--- /dev/null
+++ b/glom/libglom/libepc/marshal.h
@@ -0,0 +1,49 @@
+/* Generated by glib-genmarshal from libepc/marshal.list */
+/* This file is generated, all changes will be lost */
+#ifndef ___EPC_MARSHAL_MARSHAL_H__
+#define ___EPC_MARSHAL_MARSHAL_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* VOID:ENUM,STRING,UINT (libepc/marshal.list:1) */
+extern
+void _epc_marshal_VOID__ENUM_STRING_UINT (GClosure     *closure,
+                                          GValue       *return_value,
+                                          guint         n_param_values,
+                                          const GValue *param_values,
+                                          gpointer      invocation_hint,
+                                          gpointer      marshal_data);
+
+/* BOOLEAN:STRING (libepc/marshal.list:2) */
+extern
+void _epc_marshal_BOOLEAN__STRING (GClosure     *closure,
+                                   GValue       *return_value,
+                                   guint         n_param_values,
+                                   const GValue *param_values,
+                                   gpointer      invocation_hint,
+                                   gpointer      marshal_data);
+
+/* VOID:STRING,BOXED (libepc/marshal.list:3) */
+extern
+void _epc_marshal_VOID__STRING_BOXED (GClosure     *closure,
+                                      GValue       *return_value,
+                                      guint         n_param_values,
+                                      const GValue *param_values,
+                                      gpointer      invocation_hint,
+                                      gpointer      marshal_data);
+
+/* VOID:STRING,STRING (libepc/marshal.list:4) */
+extern
+void _epc_marshal_VOID__STRING_STRING (GClosure     *closure,
+                                       GValue       *return_value,
+                                       guint         n_param_values,
+                                       const GValue *param_values,
+                                       gpointer      invocation_hint,
+                                       gpointer      marshal_data);
+
+
+G_END_DECLS
+
+#endif /* ___EPC_MARSHAL_MARSHAL_H__ */
diff --git a/glom/libglom/libepc/marshal.list b/glom/libglom/libepc/marshal.list
new file mode 100644
index 0000000..a902c10
--- /dev/null
+++ b/glom/libglom/libepc/marshal.list
@@ -0,0 +1,4 @@
+VOID:ENUM,STRING,UINT
+BOOLEAN:STRING
+VOID:STRING,BOXED
+VOID:STRING,STRING
diff --git a/glom/libglom/libepc/protocol.c b/glom/libglom/libepc/protocol.c
new file mode 100644
index 0000000..aca426d
--- /dev/null
+++ b/glom/libglom/libepc/protocol.c
@@ -0,0 +1,173 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#include <libglom/libepc/protocol.h>
+#include <libglom/libepc/service-type.h>
+#include <libglom/libepc/enums.h>
+
+#include <glib-object.h>
+
+/**
+ * SECTION:protocol
+ * @short_description: transport protocols
+ * @see_also: epc_service_type_new()
+ * @include: libepc/protcol.h
+ * @stability: Unstable
+ *
+ * Since the Easy Publish and Consume library hides the details of the
+ * transport mechanism used, it is possible to support different mechanisms.
+ * Currently there is support for HTTP and HTTPS.
+ *
+ * The #EpcProtocol enumeration is the maximum of information libepc wants to
+ * expose regarding its transport mechanisms.
+ */
+
+/**
+ * epc_protocol_build_uri:
+ * @protocol: a #EpcProtocol
+ * @hostname: the host to contact
+ * @port: the service port
+ * @path: the service path, or %NULL
+ *
+ * Builds the Unified Resource Identifier (URI) for a service.
+ * The returned string should be released when no longer needed.
+ *
+ * Returns: A newly allocated string with the URI for the service,
+ * or %NULL on error.
+ */
+gchar*
+epc_protocol_build_uri (EpcProtocol  protocol,
+                        const gchar *hostname,
+                        guint16      port,
+                        const gchar *path)
+{
+  const gchar *scheme;
+
+  if (NULL == path)
+    path = "/";
+
+  g_return_val_if_fail (NULL != hostname, NULL);
+  g_return_val_if_fail ('/' == path[0], NULL);
+  g_return_val_if_fail (port > 0, NULL);
+
+  scheme = epc_protocol_get_uri_scheme (protocol);
+  g_return_val_if_fail (NULL != scheme, NULL);
+
+  return g_strdup_printf ("%s://%s:%d/%s", scheme, hostname, port, path + 1);
+}
+
+/**
+ * epc_protocol_from_name:
+ * @name: a protocol name
+ * @fallback: the #EpcProtocol to use on errors
+ *
+ * Parses the protocol @name. Case of the name doesn't matter. Returns the
+ * matching #EpcProtocol, when the name was recognized, and the value of
+ * @fallback otherwise.
+ *
+ * Returns: The #EpcProtocol matching @name, or @fallback on error.
+ */
+EpcProtocol
+epc_protocol_from_name (const gchar *name,
+                        EpcProtocol  fallback)
+{
+  static GEnumClass *cls = NULL;
+  GEnumValue *result;
+  gchar *lower;
+
+  g_return_val_if_fail (NULL != name, fallback);
+
+  if (G_UNLIKELY (NULL == cls))
+    cls = g_type_class_ref (EPC_TYPE_PROTOCOL);
+
+  lower = g_utf8_strdown (name, -1);
+  result = g_enum_get_value_by_nick (cls, lower);
+  g_free (lower);
+
+  if (result && EPC_PROTOCOL_UNKNOWN != result->value)
+    return result->value;
+
+  return fallback;
+}
+
+/**
+ * epc_protocol_get_service_type:
+ * @protocol: a #EpcProtocol
+ *
+ * Queries the DNS-SD service type associated with a #EpcProtocol.
+ * See #EPC_SERVICE_TYPE_HTTP, #EPC_SERVICE_TYPE_HTTPS.
+ *
+ * Returns: Returns the DNS-SD service type associated
+ * with @protocol, or %NULL on unknown protocols.
+ */
+const gchar*
+epc_protocol_get_service_type (EpcProtocol protocol)
+{
+  switch (protocol)
+    {
+      case EPC_PROTOCOL_HTTPS:
+        return EPC_SERVICE_TYPE_HTTPS;
+
+      case EPC_PROTOCOL_HTTP:
+        return EPC_SERVICE_TYPE_HTTP;
+
+      case EPC_PROTOCOL_UNKNOWN:
+        return NULL;
+
+      default:
+        g_warning ("%s: Unexpected protocol.", G_STRFUNC);
+        break;
+    }
+
+  g_return_val_if_reached (NULL);
+}
+
+/**
+ * epc_protocol_get_uri_scheme:
+ * @protocol: a #EpcProtocol
+ *
+ * Queries the URI scheme associated with a #EpcProtocol.
+ * See epc_service_type_build_uri().
+ *
+ * Returns: Returns the URI scheme associated with @protocol,
+ * or %NULL on unknown protocols.
+ */
+const gchar*
+epc_protocol_get_uri_scheme (EpcProtocol  protocol)
+{
+  switch (protocol)
+    {
+      case EPC_PROTOCOL_HTTPS:
+        return "https";
+
+      case EPC_PROTOCOL_HTTP:
+        return "http";
+
+      case EPC_PROTOCOL_UNKNOWN:
+        return NULL;
+
+      default:
+        g_warning ("%s: Unexpected protocol.", G_STRFUNC);
+        break;
+    }
+
+  g_return_val_if_reached (NULL);
+}
diff --git a/glom/libglom/libepc/protocol.h b/glom/libglom/libepc/protocol.h
new file mode 100644
index 0000000..cb0d468
--- /dev/null
+++ b/glom/libglom/libepc/protocol.h
@@ -0,0 +1,59 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+#ifndef __EPC_PROTOCOL_H__
+#define __EPC_PROTOCOL_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/**
+ * EpcProtocol:
+ * @EPC_PROTOCOL_UNKNOWN: Used when the transport protocol is not known yet.
+ * @EPC_PROTOCOL_HTTP: Plain HTTP. Passwords are protected using HTTP digest
+ * authentication, remaining data is transfered without any encryption.
+ * @EPC_PROTOCOL_HTTPS: Encrypted HTTP. Attempts are made to use this
+ * transport method when ever possible.
+ *
+ * The transport protocols supported by libepc.
+ */
+typedef enum
+{
+  EPC_PROTOCOL_UNKNOWN,
+  EPC_PROTOCOL_HTTP,
+  EPC_PROTOCOL_HTTPS
+}
+EpcProtocol;
+
+EpcProtocol           epc_protocol_from_name        (const gchar  *name,
+                                                     EpcProtocol   fallback);
+
+gchar*                epc_protocol_build_uri        (EpcProtocol   protocol,
+                                                     const gchar  *hostname,
+                                                     guint16       port,
+                                                     const gchar  *path);
+
+const gchar* epc_protocol_get_service_type (EpcProtocol   protocol) G_GNUC_CONST;
+const gchar* epc_protocol_get_uri_scheme   (EpcProtocol   protocol) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __EPC_PROTOCOL_H__ */
diff --git a/glom/libglom/libepc/publisher.c b/glom/libglom/libepc/publisher.c
new file mode 100644
index 0000000..7ccc204
--- /dev/null
+++ b/glom/libglom/libepc/publisher.c
@@ -0,0 +1,2814 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libglom/libepc/publisher.h>
+#include <libglom/libepc/dispatcher.h>
+#include <libglom/libepc/enums.h>
+#include <libglom/libepc/shell.h>
+#include <libglom/libepc/tls.h>
+
+#include <glib/gi18n-lib.h>
+#include <libsoup/soup.h>
+#include <string.h>
+
+#ifdef HAVE_LIBSOUP22
+#include <libsoup/soup-address.h>
+#include <libsoup/soup-server.h>
+#include <libsoup/soup-server-auth.h>
+#include <libsoup/soup-server-message.h>
+
+#define SOUP_MEMORY_COPY SOUP_BUFFER_USER_OWNED
+#define SOUP_MEMORY_TAKE SOUP_BUFFER_SYSTEM_OWNED
+
+#define SoupServerCallback SoupServerCallbackFn
+#define SoupURI            SoupUri
+
+#endif
+
+#if GLIB_CHECK_VERSION(2,15,1)
+#include <gio/gio.h>
+#endif
+
+/**
+ * SECTION:auth-context
+ * @short_description: manage authentication
+ * @see_also: #EpcPublisher
+ * @include: libepc/publish.h
+ * @stability: Unstable
+ *
+ * With each request the #EpcPublisher verifies access authorization by calling
+ * the #EpcAuthHandler registered for the key in question, if any. Information about
+ * that process is stored in the #EpcAuthContext structure.
+ *
+ * To register an authentication handler call epc_publisher_set_auth_handler():
+ *
+ * <example id="register-auth-handler">
+ *  <title>Register an authentication handler</title>
+ *  <programlisting>
+ *   epc_publisher_set_auth_handler (publisher, "sensitive-key",
+ *                                   my_auth_handler, my_object);
+ *  </programlisting>
+ * </example>
+ *
+ * To verify that the user password provided password matches
+ * the expected one use epc_auth_context_check_password():
+ *
+ * <example id="check-password">
+ *  <title>Verify a password</title>
+ *  <programlisting>
+ *   static gboolean
+ *   my_auth_handler (EpcAuthContext *context,
+ *                    const gchar    *username,
+ *                    gpointer        user_data)
+ *   {
+ *     MyObject *self = user_data;
+ *     const gchar *expected_password;
+ *     const gchar *requested_key;
+ *
+ *     requested_key = epc_auth_context_get_key (context);
+ *     expected_password = lookup_password (self, requested_key);
+ *
+ *     return epc_auth_context_check_password (context, expected_password);
+ *   }
+ *  </programlisting>
+ * </example>
+ */
+
+/**
+ * SECTION:publisher
+ * @short_description: easily publish values
+ * @see_also: #EpcConsumer, #EpcAuthContext, #EpcContentsHandler
+ * @include: libepc/publish.h
+ * @stability: Unstable
+ *
+ * The #EpcPublisher starts a HTTP server to publish information.
+ * To allow #EpcConsumer to find the publisher it automatically publishes
+ * its contact information (host name, TCP/IP port) per DNS-SD.
+ *
+ * In future it might use DNS-DS to notify #EpcConsumer of changes.
+ *
+ * <example id="publish-value">
+ *  <title>Publish a value</title>
+ *  <programlisting>
+ *   publisher = epc_publisher_new ("Easy Publisher Example", NULL, NULL);
+ *
+ *   epc_publisher_add (publisher, "maman", "bar", -1);
+ *   epc_publisher_add_file (publisher, "source-code", __FILE__);
+ *
+ *   epc_publisher_run (NULL);
+ *  </programlisting>
+ * </example>
+ *
+ * #EpcPublisher doesn't provide a way to explicitly publish %NULL values, as
+ * publishing %NULL values doesn't seem very valueable in our scenario: Usually
+ * you want to "publish" %NULL values to express, that your application doesn't
+ * have any meaningful information for the requested identifier. By "publishing"
+ * a %NULL value essentially you say "this information does not exist". So
+ * publishing %NULL values is not different from not publishing any value at
+ * all or rejected access to some values. Without explicitly inspecting the
+ * details for not receiving a value, a consumer calling epc_consumer_lookup()
+ * has no chance to distinguish between the cases "never published", "network
+ * problem", "authorization rejected", "no meaningful value available".
+ *
+ * So if  feel like publishing a %NULL value, just remove the key in question
+ * from the #EpcPublisher by calling epc_publisher_remove(). When using a
+ * custom #EpcContentsHandler an alternate approach is returning %NULL from
+ * that handler. In that case the #EpcPublisher will behave exactly the same,
+ * as if the value has been removed.
+ */
+
+typedef struct _EpcListContext EpcListContext;
+typedef struct _EpcResource    EpcResource;
+
+enum
+{
+  PROP_NONE,
+  PROP_PROTOCOL,
+  PROP_APPLICATION,
+  PROP_SERVICE_NAME,
+  PROP_SERVICE_DOMAIN,
+  PROP_SERVICE_COOKIE,
+  PROP_COLLISION_HANDLING,
+
+  PROP_AUTH_FLAGS,
+  PROP_CONTENTS_PATH,
+  PROP_CERTIFICATE_FILE,
+  PROP_PRIVATE_KEY_FILE,
+};
+
+/**
+ * EpcAuthContext:
+ *
+ * This data structure describes a pending authentication request
+ * which shall be verified by an #EpcAuthHandler installed by
+ * epc_publisher_set_auth_handler().
+ *
+ * <note><para>
+ *  There is no way to retrieve the password from the #EpcAuthContext, as
+ *  the network protocol transfers just a hash code, not the actual password.
+ * </para></note>
+ */
+struct _EpcAuthContext
+{
+  /*< private >*/
+  EpcResource    *resource;
+  EpcPublisher   *publisher;
+  const gchar    *key;
+
+#ifdef HAVE_LIBSOUP22
+  SoupServerAuth *auth;
+#else
+  SoupMessage    *message;
+  const char     *username;
+  const char     *password;
+#endif
+
+  /*< public >*/
+};
+
+struct _EpcListContext
+{
+  GPatternSpec *pattern;
+  GList *matches;
+};
+
+struct _EpcResource
+{
+  EpcContentsHandler handler;
+  gpointer           user_data;
+  GDestroyNotify     destroy_data;
+
+  EpcAuthHandler     auth_handler;
+  gpointer           auth_user_data;
+  GDestroyNotify     auth_destroy_data;
+
+  EpcDispatcher     *dispatcher;
+};
+
+/**
+ * EpcPublisherPrivate:
+ *
+ * Private fields of the #EpcPublisher class.
+ */
+struct _EpcPublisherPrivate
+{
+  EpcDispatcher         *dispatcher;
+
+  GHashTable            *resources;
+  EpcResource           *default_resource;
+  gchar                 *default_bookmark;
+
+  gboolean               server_started;
+  GMainLoop             *server_loop;
+  SoupServer            *server;
+
+#ifdef HAVE_LIBSOUP22
+  SoupServerAuthContext  server_auth;
+#else
+  SoupAuthDomain        *server_auth;
+#endif
+
+  GHashTable            *clients;
+
+  EpcProtocol            protocol;
+  gchar                 *application;
+  gchar                 *service_name;
+  gchar                 *service_domain;
+  gchar                 *service_cookie;
+
+  EpcAuthFlags           auth_flags;
+  EpcCollisionHandling   collisions;
+  gchar                 *contents_path;
+  gchar                 *certificate_file;
+  gchar                 *private_key_file;
+};
+
+static GRecMutex epc_publisher_lock;
+
+G_DEFINE_TYPE (EpcPublisher, epc_publisher, G_TYPE_OBJECT);
+
+static EpcResource*
+epc_resource_new (EpcContentsHandler handler,
+                  gpointer           user_data,
+                  GDestroyNotify     destroy_data)
+{
+  EpcResource *self = g_slice_new0 (EpcResource);
+
+  self->handler = handler;
+  self->user_data = user_data;
+  self->destroy_data = destroy_data;
+
+  return self;
+}
+
+static void
+epc_resource_free (gpointer data)
+{
+  EpcResource *self = data;
+
+  if (self->dispatcher)
+    g_object_unref (self->dispatcher);
+  if (self->destroy_data)
+    self->destroy_data (self->user_data);
+  if (self->auth_destroy_data)
+    self->auth_destroy_data (self->auth_user_data);
+
+  g_slice_free (EpcResource, self);
+}
+
+static void
+epc_resource_set_auth_handler (EpcResource    *self,
+                               EpcAuthHandler  handler,
+                               gpointer        user_data,
+                               GDestroyNotify  destroy_data)
+
+{
+  if (self->auth_destroy_data)
+    self->auth_destroy_data (self->auth_user_data);
+
+  self->auth_handler = handler;
+  self->auth_user_data = user_data;
+  self->auth_destroy_data = destroy_data;
+}
+
+static void
+epc_resource_announce (EpcResource *self,
+                       const gchar *label)
+{
+  if (!self->dispatcher)
+    {
+      GError *error = NULL;
+
+      self->dispatcher = epc_dispatcher_new (label);
+
+      /* TODO: real error reporting */
+      if (!epc_dispatcher_run (self->dispatcher, &error))
+        {
+          g_warning ("%s: %s", G_STRFUNC, error->message);
+          g_clear_error (&error);
+        }
+    }
+  else
+    epc_dispatcher_set_name (self->dispatcher, label);
+}
+
+static EpcContents*
+epc_publisher_handle_static (EpcPublisher *publisher G_GNUC_UNUSED,
+                             const gchar  *key G_GNUC_UNUSED,
+                             gpointer      user_data)
+{
+  return epc_contents_ref (user_data);
+}
+
+static EpcContents*
+epc_publisher_handle_file (EpcPublisher *publisher G_GNUC_UNUSED,
+                           const gchar  *key G_GNUC_UNUSED,
+                           gpointer      user_data)
+{
+  const gchar *filename = user_data;
+  EpcContents *contents = NULL;
+  gchar *data = NULL;
+  gsize length = 0;
+
+  if (g_file_get_contents (filename, &data, &length, NULL))
+    {
+      gchar *type = NULL;
+
+#if GLIB_CHECK_VERSION(2,15,1)
+      type = g_content_type_guess (filename, (gpointer) data, length, NULL);
+#endif
+      contents = epc_contents_new (type, data, length, g_free);
+
+      g_free (type);
+    }
+
+  return contents;
+}
+
+static const gchar*
+epc_publisher_get_key (const gchar *path)
+{
+  const gchar *key;
+
+  g_return_val_if_fail (NULL != path, NULL);
+  g_return_val_if_fail ('/' == *path, NULL);
+
+  key = strchr (path + 1, '/');
+
+  if (key)
+    key += 1;
+
+  return key;
+}
+
+static void
+epc_publisher_chunk_cb (SoupMessage *message,
+                        gpointer     data)
+{
+  EpcContents *contents = data;
+  gconstpointer chunk;
+  gsize length;
+
+  chunk = epc_contents_stream_read (contents, &length);
+
+  if (chunk && length)
+    {
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: writing %" G_GSIZE_FORMAT " bytes", G_STRLOC, length);
+
+#ifdef HAVE_LIBSOUP22
+      soup_message_add_chunk (message, SOUP_MEMORY_COPY, chunk, length);
+#else
+      soup_message_body_append (message->response_body,
+                                SOUP_MEMORY_COPY, chunk, length);
+#endif
+    }
+  else
+    {
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: done", G_STRLOC);
+
+#ifdef HAVE_LIBSOUP22
+      soup_message_add_final_chunk (message);
+#else
+      soup_message_body_complete (message->response_body);
+#endif
+    }
+}
+
+static void
+epc_publisher_trace_client (const gchar *strfunc,
+                            const gchar *message,
+                            SoupSocket  *socket)
+{
+  SoupAddress *addr = soup_socket_get_remote_address (socket);
+
+  g_debug ("%s: %s: %s:%d", strfunc, message,
+           soup_address_get_physical (addr),
+           soup_address_get_port (addr));
+}
+
+static gboolean
+epc_publisher_check_client (EpcPublisher      *self,
+                            SoupServer        *server,
+                            SoupSocket        *socket)
+{
+  if (server == self->priv->server)
+    return TRUE;
+
+  if (EPC_DEBUG_LEVEL (1))
+    epc_publisher_trace_client (G_STRFUNC, "stale client", socket);
+
+  soup_socket_disconnect (socket);
+
+  return FALSE;
+}
+
+G_GNUC_WARN_UNUSED_RESULT static gboolean
+epc_publisher_track_client (EpcPublisher *self,
+                            SoupServer   *server,
+                            SoupSocket   *socket)
+{
+  g_rec_mutex_lock (&epc_publisher_lock);
+
+  if (epc_publisher_check_client (self, server, socket))
+    {
+      gpointer tag;
+
+      tag = g_hash_table_lookup (self->priv->clients, socket);
+      tag = GINT_TO_POINTER (GPOINTER_TO_INT (tag) + 1);
+
+      g_object_ref (socket);
+      g_hash_table_replace (self->priv->clients, socket, tag);
+
+      return TRUE;
+    }
+  else
+    g_rec_mutex_unlock (&epc_publisher_lock);
+
+  return FALSE;
+}
+
+static void
+epc_publisher_untrack_client (EpcPublisher *self,
+                              SoupServer   *server,
+                              SoupSocket   *socket)
+{
+  if (epc_publisher_check_client (self, server, socket))
+    {
+      gpointer tag;
+
+      tag = g_hash_table_lookup (self->priv->clients, socket);
+      tag = GINT_TO_POINTER (GPOINTER_TO_INT (tag) - 1);
+
+      g_object_ref (socket);
+      g_hash_table_replace (self->priv->clients, socket, tag);
+    }
+
+  g_rec_mutex_unlock (&epc_publisher_lock);
+}
+
+static void
+#ifdef HAVE_LIBSOUP22
+epc_publisher_handle_contents (SoupServerContext *context,
+                               SoupMessage       *message,
+                               gpointer           data)
+#else
+epc_publisher_handle_contents (SoupServer        *server,
+                               SoupMessage       *message,
+                               const gchar       *path,
+                               GHashTable        *query G_GNUC_UNUSED,
+                               SoupClientContext *context,
+                               gpointer           data)
+#endif
+{
+#ifdef HAVE_LIBSOUP22
+  SoupServer *server = context->server;
+  SoupSocket *socket = context->sock;
+  const gchar *path = context->path;
+#else
+  SoupSocket *socket = soup_client_context_get_socket (context);
+#endif
+
+  EpcPublisher *self = EPC_PUBLISHER (data);
+  EpcResource *resource = NULL;
+  EpcContents *contents = NULL;
+  const gchar *key = NULL;
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: method=%s, path=%s", G_STRFUNC, message->method, path);
+
+#ifdef HAVE_LIBSOUP22
+  if (SOUP_METHOD_ID_GET != context->method_id)
+#else
+  if (SOUP_METHOD_GET != message->method)
+#endif
+    {
+      soup_message_set_status (message, SOUP_STATUS_METHOD_NOT_ALLOWED);
+      return;
+    }
+
+  if (!epc_publisher_track_client (self, server, socket))
+    return;
+
+  key = epc_publisher_get_key (path);
+
+  if (key)
+    resource = g_hash_table_lookup (self->priv->resources, key);
+  if (resource && resource->handler)
+    contents = resource->handler (self, key, resource->user_data);
+
+  soup_message_set_status (message, SOUP_STATUS_NOT_FOUND);
+
+  if (contents)
+    {
+      gconstpointer contents_data;
+      const gchar *type;
+      gsize length = 0;
+
+      contents_data = epc_contents_get_data (contents, &length);
+      type = epc_contents_get_mime_type (contents);
+
+      if (contents_data)
+        {
+          soup_message_set_response (message, type, SOUP_MEMORY_COPY, (gpointer) contents_data, length);
+          soup_message_set_status (message, SOUP_STATUS_OK);
+        }
+      else if (epc_contents_is_stream (contents))
+        {
+          g_signal_connect (message, "wrote-chunk", G_CALLBACK (epc_publisher_chunk_cb), contents);
+          g_signal_connect (message, "wrote-headers", G_CALLBACK (epc_publisher_chunk_cb), contents);
+
+#ifdef HAVE_LIBSOUP22
+          soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (message), SOUP_TRANSFER_CHUNKED);
+#else
+          soup_message_headers_set_encoding (message->response_headers, SOUP_ENCODING_CHUNKED);
+#endif
+          soup_message_set_status (message, SOUP_STATUS_OK);
+        }
+
+      g_signal_connect_swapped (message, "finished", G_CALLBACK (epc_contents_unref), contents);
+    }
+
+  epc_publisher_untrack_client (self, server, socket);
+}
+
+static void
+#ifdef HAVE_LIBSOUP22
+epc_publisher_handle_list (SoupServerContext *context,
+                           SoupMessage       *message,
+                           gpointer           data)
+#else
+epc_publisher_handle_list (SoupServer        *server,
+                           SoupMessage       *message,
+                           const char        *path,
+                           GHashTable        *query G_GNUC_UNUSED,
+                           SoupClientContext *context,
+                           gpointer           data)
+#endif
+{
+#ifdef HAVE_LIBSOUP22
+  SoupServer *server = context->server;
+  SoupSocket *socket = context->sock;
+  const gchar *path = context->path;
+#else
+  SoupSocket *socket = soup_client_context_get_socket (context);
+#endif
+
+  const gchar *pattern = NULL;
+  EpcPublisher *self = data;
+  GList *files = NULL;
+  GList *iter;
+
+  GString *contents = g_string_new (NULL);
+
+  if (!epc_publisher_track_client (self, server, socket))
+    return;
+
+  if (g_str_has_prefix (path, "/list/") && '\0' != path[6])
+    pattern = path + 6;
+
+  files = epc_publisher_list (self, pattern);
+  g_string_append (contents, "<list>");
+
+  for (iter = files; iter; iter = iter->next)
+    {
+      gchar *markup = g_markup_escape_text (iter->data, -1);
+
+      g_string_append (contents, "<item><name>");
+      g_string_append (contents, markup);
+      g_string_append (contents, "</name></item>");
+
+      g_free (iter->data);
+      g_free (markup);
+    }
+
+  g_string_append (contents, "</list>");
+
+  soup_message_set_response (message, "text/xml", SOUP_MEMORY_TAKE,
+                             contents->str, contents->len);
+  soup_message_set_status (message, SOUP_STATUS_OK);
+
+  g_string_free (contents, FALSE);
+  g_list_free (files);
+
+  epc_publisher_untrack_client (self, server, socket);
+}
+
+static void
+#ifdef HAVE_LIBSOUP22
+epc_publisher_handle_root (SoupServerContext *context,
+                           SoupMessage       *message,
+                           gpointer           data)
+#else
+epc_publisher_handle_root (SoupServer        *server,
+                           SoupMessage       *message,
+                           const char        *path,
+                           GHashTable        *query G_GNUC_UNUSED,
+                           SoupClientContext *context,
+                           gpointer           data)
+#endif
+{
+#ifdef HAVE_LIBSOUP22
+  SoupServer *server = context->server;
+  SoupSocket *socket = context->sock;
+  const gchar *path = context->path;
+#else
+  SoupSocket *socket = soup_client_context_get_socket (context);
+#endif
+
+  EpcPublisher *self = data;
+
+  if (g_str_equal (path, "/") &&
+      epc_publisher_track_client (self, server, socket))
+    {
+      GString *contents = g_string_new (NULL);
+      gchar *markup;
+
+      GList *files;
+      GList *iter;
+
+      files = epc_publisher_list (self, NULL);
+      files = g_list_sort (files, (GCompareFunc) g_utf8_collate);
+
+      markup = g_markup_escape_text (self->priv->service_name, -1);
+
+      g_string_append (contents, "<html><head><title>");
+      g_string_append (contents, markup);
+      g_string_append (contents, "</title></head><body><h1>");
+      g_string_append (contents, markup);
+      g_string_append (contents, "</h1><h2>");
+      g_string_append (contents, _("Table of Contents"));
+      g_string_append (contents, "</h2>");
+
+      g_free (markup);
+
+      if (files)
+        {
+          g_string_append (contents, "<ul id=\"toc\">");
+
+          for (iter = files; iter; iter = iter->next)
+            {
+              markup = g_markup_escape_text (self->priv->contents_path, -1);
+
+              g_string_append (contents, "<li><a href=\"");
+              g_string_append (contents, markup);
+              g_string_append (contents, "/");
+
+              g_free (markup);
+              markup = g_markup_escape_text (iter->data, -1);
+
+              g_string_append (contents, markup);
+              g_string_append (contents, "\">");
+              g_string_append (contents, markup);
+              g_string_append (contents, "</a></li>");
+
+              g_free (markup);
+              g_free (iter->data);
+            }
+
+          g_string_append (contents, "</ul>");
+        }
+      else
+        {
+          g_string_append (contents, "<p id=\"toc\">");
+          g_string_append (contents, _("Sorry, no resources published yet."));
+          g_string_append (contents, "</ul>");
+        }
+
+      g_string_append (contents, "</ul></body></html>");
+
+      soup_message_set_response (message,
+                                 "text/html; charset=utf-8",
+                                 SOUP_MEMORY_TAKE,
+                                 contents->str,
+                                 contents->len);
+
+      soup_message_set_status (message, SOUP_STATUS_OK);
+
+      g_string_free (contents, FALSE);
+      g_list_free (files);
+
+      epc_publisher_untrack_client (self, server, socket);
+    }
+  else
+    soup_message_set_status (message, SOUP_STATUS_NOT_FOUND);
+}
+
+static void
+#ifdef HAVE_LIBSOUP22
+epc_auth_context_init (EpcAuthContext *context,
+                       EpcPublisher   *publisher,
+                       SoupMessage    *message,
+                       SoupServerAuth *auth)
+#else
+epc_auth_context_init (EpcAuthContext *context,
+                       EpcPublisher   *publisher,
+                       SoupMessage    *message,
+                       const gchar    *username,
+                       const gchar    *password)
+#endif
+{
+  const SoupURI *uri = soup_message_get_uri (message);
+
+  context->publisher = publisher;
+  context->key = epc_publisher_get_key (uri->path);
+  context->resource = NULL;
+
+#ifdef HAVE_LIBSOUP22
+  context->auth = auth;
+#else
+  context->message  = message;
+  context->username = username;
+  context->password = password;
+#endif
+
+  if (context->key)
+    context->resource = g_hash_table_lookup (publisher->priv->resources, context->key);
+  if (!context->resource)
+    context->resource = publisher->priv->default_resource;
+}
+
+#ifdef HAVE_LIBSOUP22
+
+static gboolean
+epc_publisher_server_auth_cb (SoupServerAuthContext *auth_ctx G_GNUC_UNUSED,
+                              SoupServerAuth        *auth,
+                              SoupMessage           *message,
+                              gpointer               data)
+{
+  gboolean authorized = TRUE;
+  const char *user = NULL;
+  EpcAuthContext context;
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+  epc_auth_context_init (&context, EPC_PUBLISHER (data), message, auth);
+
+  if (NULL != auth)
+    user = soup_server_auth_get_user (auth);
+
+  if (context.resource && context.resource->auth_handler)
+    authorized = context.resource->auth_handler (&context, user,
+                                                 context.resource->auth_user_data);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: key=%s, resource=%p, auth_handler=%p, authorized=%d", G_STRLOC,
+             context.key, context.resource, context.resource ? context.resource->auth_handler : NULL,
+             authorized);
+
+  g_rec_mutex_unlock (&epc_publisher_lock);
+
+  return authorized;
+}
+
+#else
+
+static gboolean
+epc_publisher_auth_filter (SoupAuthDomain *domain G_GNUC_UNUSED,
+                           SoupMessage    *message,
+                           gpointer        data)
+{
+  gboolean authorized = TRUE;
+  EpcAuthContext context;
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+  epc_auth_context_init (&context, EPC_PUBLISHER (data), message, NULL, NULL);
+  authorized = (!context.resource || !context.resource->auth_handler);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: key=%s, resource=%p, auth_handler=%p, authorized=%d", G_STRLOC,
+             context.key, context.resource, context.resource ? context.resource->auth_handler : NULL,
+             authorized);
+
+  g_rec_mutex_unlock (&epc_publisher_lock);
+
+  return !authorized;
+}
+
+static gboolean
+epc_publisher_basic_auth_cb (SoupAuthDomain *domain G_GNUC_UNUSED,
+                             SoupMessage    *message,
+                             const gchar    *username,
+                             const gchar    *password,
+                             gpointer        data)
+{
+  gboolean authorized = TRUE;
+  EpcAuthContext context;
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+  epc_auth_context_init (&context, EPC_PUBLISHER (data), message, username, password);
+
+  if (context.resource && context.resource->auth_handler)
+    authorized = context.resource->auth_handler (&context, username, context.resource->auth_user_data);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: key=%s, resource=%p, auth_handler=%p, authorized=%d", G_STRLOC,
+             context.key, context.resource, context.resource ? context.resource->auth_handler : NULL,
+             authorized);
+
+  g_rec_mutex_unlock (&epc_publisher_lock);
+
+  return authorized;
+}
+
+static gboolean
+epc_publisher_generic_auth_cb (SoupAuthDomain *domain G_GNUC_UNUSED,
+                               SoupMessage    *message,
+                               const char     *username,
+                               gpointer        data)
+{
+  gboolean authorized = TRUE;
+  EpcAuthContext context;
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+  epc_auth_context_init (&context, EPC_PUBLISHER (data), message, username, NULL);
+
+  if (context.resource && context.resource->auth_handler)
+    authorized = context.resource->auth_handler (&context, username, context.resource->auth_user_data);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: key=%s, resource=%p, auth_handler=%p, authorized=%d", G_STRLOC,
+             context.key, context.resource, context.resource ? context.resource->auth_handler : NULL,
+             authorized);
+
+  g_rec_mutex_unlock (&epc_publisher_lock);
+
+  return authorized;
+}
+
+#endif
+
+static void
+epc_publisher_init (EpcPublisher *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EPC_TYPE_PUBLISHER, EpcPublisherPrivate);
+  self->priv->protocol = EPC_PROTOCOL_HTTPS;
+
+#ifdef HAVE_LIBSOUP22
+  self->priv->server_auth.types = SOUP_AUTH_TYPE_DIGEST;
+  self->priv->server_auth.callback = epc_publisher_server_auth_cb;
+  self->priv->server_auth.user_data = self;
+#endif
+
+  self->priv->resources = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                 g_free, epc_resource_free);
+  self->priv->clients = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+                                               g_object_unref, NULL);
+}
+
+static const gchar*
+epc_publisher_get_host (EpcPublisher     *self,
+                        struct sockaddr **sockaddr,
+                        gint             *addrlen)
+{
+  SoupSocket *listener = soup_server_get_listener (self->priv->server);
+  SoupAddress *address = soup_socket_get_local_address (listener);
+
+  if (sockaddr && addrlen)
+    *sockaddr = soup_address_get_sockaddr (address, addrlen);
+
+  return soup_address_get_name (address);
+}
+
+static gint
+epc_publisher_get_port (EpcPublisher *self)
+{
+  return soup_server_get_port (self->priv->server);
+}
+
+static const gchar*
+epc_publisher_get_bookmark_type (EpcPublisher *self)
+{
+  switch (self->priv->protocol)
+    {
+      case EPC_PROTOCOL_HTTP:
+        return "_http._tcp";
+
+      case EPC_PROTOCOL_HTTPS:
+        return "_https._tcp";
+
+      case EPC_PROTOCOL_UNKNOWN:
+        break;
+
+      default:
+        g_warning ("%s: Unexpected protocol.", G_STRFUNC);
+        break;
+    }
+
+  return NULL;
+}
+
+static void
+epc_publisher_find_bookmarks_cb (gpointer key,
+                                 gpointer value,
+                                 gpointer data)
+{
+  EpcResource *resource = value;
+  GSList **bookmarks = data;
+
+  if (resource->dispatcher)
+    {
+      *bookmarks = g_slist_prepend (*bookmarks, resource);
+      *bookmarks = g_slist_prepend (*bookmarks, key);
+    }
+}
+
+static EpcResource*
+epc_publisher_find_resource (EpcPublisher   *self,
+                             const gchar    *key)
+{
+  if (NULL != key)
+    return g_hash_table_lookup (self->priv->resources, key);
+
+  if (NULL == self->priv->default_resource)
+    self->priv->default_resource = epc_resource_new (NULL, NULL, NULL);
+
+  return self->priv->default_resource;
+}
+
+static void
+epc_publisher_announce (EpcPublisher *self)
+{
+  EpcResource *default_bookmark = NULL;
+  GSList *bookmarks = NULL;
+  GSList *iter;
+
+  const gchar *bookmark_type;
+  const gchar *service_type;
+  gchar *service_sub_type;
+
+  const gchar *host;
+  struct sockaddr *addr;
+  gchar *path_record;
+  gint addrlen;
+  gint port;
+
+  g_return_if_fail (SOUP_IS_SERVER (self->priv->server));
+
+  /* compute service types */
+
+  service_sub_type = epc_service_type_new (self->priv->protocol,
+                                           self->priv->application);
+  service_type = epc_protocol_get_service_type (self->priv->protocol);
+  bookmark_type = epc_publisher_get_bookmark_type (self);
+
+  /* compute service address */
+
+  host = epc_publisher_get_host (self, &addr, &addrlen);
+  port = epc_publisher_get_port (self);
+
+  /* find all bookmark resources */
+
+  g_hash_table_foreach (self->priv->resources,
+                        epc_publisher_find_bookmarks_cb,
+                        &bookmarks);
+
+  if (self->priv->default_bookmark)
+    default_bookmark = epc_publisher_find_resource (self, self->priv->default_bookmark);
+
+  if (default_bookmark)
+    {
+      bookmarks = g_slist_prepend (bookmarks, default_bookmark);
+      bookmarks = g_slist_prepend (bookmarks, self->priv->default_bookmark);
+    }
+
+  /* announce the easy-publish service */
+
+  epc_dispatcher_reset (self->priv->dispatcher);
+
+  path_record = g_strconcat ("path=", self->priv->contents_path, NULL);
+  epc_dispatcher_add_service (self->priv->dispatcher, addr->sa_family,
+                              service_type, self->priv->service_domain,
+                              host, port, path_record, NULL);
+  g_free (path_record);
+
+  epc_dispatcher_add_service_subtype (self->priv->dispatcher,
+                                      service_type, service_sub_type);
+
+  /* announce dynamic bookmarks */
+
+  for (iter = bookmarks; iter; iter = iter->next->next)
+    {
+      EpcDispatcher *dispatcher = self->priv->dispatcher;
+      EpcResource *resource = iter->next->data;
+      const gchar *key = iter->data;
+      gchar *path;
+
+      if (resource->dispatcher)
+        {
+          dispatcher = resource->dispatcher;
+          epc_dispatcher_reset (dispatcher);
+        }
+
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: Creating dynamic %s bookmark for %s: %s", G_STRLOC,
+                 bookmark_type, key, epc_dispatcher_get_name (dispatcher));
+
+      path = epc_publisher_get_path (self, key);
+      path_record = g_strconcat ("path=", path, NULL);
+
+      epc_dispatcher_add_service (dispatcher, addr->sa_family, bookmark_type,
+                                  self->priv->service_domain, host, port,
+                                  path_record, NULL);
+
+      g_free (path_record);
+      g_free (path);
+    }
+
+  /* release resources */
+
+  g_free (service_sub_type);
+  g_slist_free (bookmarks);
+
+}
+
+static gboolean
+epc_publisher_is_server_created (EpcPublisher *self)
+{
+  return (NULL != self->priv->server);
+}
+
+static const gchar*
+epc_publisher_compute_name (EpcPublisher *self)
+{
+  const gchar *name = self->priv->service_name;
+
+  if (!name)
+    name = g_get_application_name ();
+  if (!name)
+    name = g_get_prgname ();
+
+  if (!name)
+    {
+      gint hash = g_random_int ();
+
+      name = G_OBJECT_TYPE_NAME (self);
+      self->priv->service_name = g_strdup_printf ("%s-%08x", name, hash);
+      name = self->priv->service_name;
+
+      g_warning ("%s: No service name set - using generated name (`%s'). "
+                 "Consider passing a service name to the publisher's "
+                 "constructor or call g_set_application_name().",
+                 G_STRFUNC, name);
+    }
+
+  if (!self->priv->service_name)
+    self->priv->service_name = g_strdup (name);
+
+  return name;
+}
+
+static void
+epc_publisher_remove_handlers (EpcPublisher *self)
+{
+#ifdef HAVE_LIBSOUP22
+  memset (&self->priv->server_auth.digest_info, 0,
+          sizeof self->priv->server_auth.digest_info);
+#else
+  if (self->priv->server_auth)
+    {
+      soup_server_remove_auth_domain (self->priv->server, self->priv->server_auth);
+      self->priv->server_auth = NULL;
+    }
+#endif
+
+  if (self->priv->server)
+    {
+      soup_server_remove_handler (self->priv->server, self->priv->contents_path);
+      soup_server_remove_handler (self->priv->server, "/list");
+      soup_server_remove_handler (self->priv->server, "/");
+    }
+}
+
+static void
+epc_publisher_add_server_callback (EpcPublisher       *self,
+                                   const gchar        *path,
+                                   SoupServerCallback  callback)
+{
+#ifdef HAVE_LIBSOUP22
+  soup_server_add_handler (self->priv->server, path,
+                           &self->priv->server_auth,
+                           callback, NULL, self);
+#else
+  soup_server_add_handler (self->priv->server, path,
+                           callback, self, NULL);
+#endif
+}
+
+static void
+epc_publisher_install_handlers (EpcPublisher *self)
+{
+#ifdef HAVE_LIBSOUP22
+
+  memset (&self->priv->server_auth.digest_info, 0,
+          sizeof self->priv->server_auth.digest_info);
+
+  switch (self->priv->server_auth.types)
+    {
+      case SOUP_AUTH_TYPE_BASIC:
+        self->priv->server_auth.basic_info.realm = self->priv->service_name;
+        break;
+
+      case SOUP_AUTH_TYPE_DIGEST:
+        self->priv->server_auth.digest_info.realm = self->priv->service_name;
+        self->priv->server_auth.digest_info.allow_algorithms = SOUP_ALGORITHM_MD5;
+        self->priv->server_auth.digest_info.force_integrity = FALSE; /* not implemented */
+        break;
+    }
+
+#else
+
+  g_assert (NULL == self->priv->server_auth);
+
+  if (self->priv->auth_flags & EPC_AUTH_PASSWORD_TEXT_NEEDED)
+    {
+      self->priv->server_auth =
+        soup_auth_domain_basic_new (SOUP_AUTH_DOMAIN_REALM,
+                                    self->priv->service_name,
+                                    SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK,
+                                    epc_publisher_basic_auth_cb,
+                                    SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA,
+                                    self, NULL);
+    }
+  else
+    {
+      /* Check for NULL, to avoid a crash, 
+       * though we do not yet know why this would be NULL.
+       * See bug #540631.
+       */
+      if(NULL == self->priv->service_name)
+        g_warning("libepc: epc_publisher_install_handlers() service_name was NULL.");
+      else
+      {
+        self->priv->server_auth =
+          soup_auth_domain_digest_new (SOUP_AUTH_DOMAIN_REALM,
+                                       self->priv->service_name,
+                                       SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK,
+                                       epc_publisher_generic_auth_cb,
+                                       SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA,
+                                       self, NULL);
+      }
+    }
+
+  soup_auth_domain_set_filter (self->priv->server_auth, epc_publisher_auth_filter, self, NULL);
+  soup_auth_domain_add_path (self->priv->server_auth, self->priv->contents_path);
+
+  soup_server_add_auth_domain (self->priv->server, self->priv->server_auth);
+
+#endif
+
+  epc_publisher_add_server_callback (self, self->priv->contents_path, epc_publisher_handle_contents);
+  epc_publisher_add_server_callback (self, "/list", epc_publisher_handle_list);
+  epc_publisher_add_server_callback (self, "/", epc_publisher_handle_root);
+}
+
+static void
+epc_publisher_client_disconnected_cb (EpcPublisher *self,
+                                      SoupSocket   *socket)
+{
+  if (EPC_DEBUG_LEVEL (1))
+    epc_publisher_trace_client (G_STRFUNC, "disconnected", socket);
+
+  g_hash_table_remove (self->priv->clients, socket);
+}
+
+static void
+epc_publisher_new_connection_cb (EpcPublisher *self,
+                                 SoupSocket   *socket)
+{
+  if (EPC_DEBUG_LEVEL (1))
+    epc_publisher_trace_client (G_STRFUNC, "new client", socket);
+
+  g_object_ref (socket);
+  g_hash_table_replace (self->priv->clients, socket, GINT_TO_POINTER (1));
+
+  g_signal_connect_swapped (socket, "disconnected",
+                            G_CALLBACK (epc_publisher_client_disconnected_cb),
+                            self);
+}
+
+static gboolean
+epc_publisher_create_server (EpcPublisher  *self,
+                             GError       **error)
+{
+  gchar *base_uri;
+
+  g_return_val_if_fail (!epc_publisher_is_server_created (self), FALSE);
+  g_return_val_if_fail (NULL == self->priv->dispatcher, FALSE);
+
+  self->priv->dispatcher = epc_dispatcher_new (epc_publisher_compute_name (self));
+
+  if (self->priv->service_cookie)
+    epc_dispatcher_set_cookie (self->priv->dispatcher, self->priv->service_cookie);
+  epc_dispatcher_set_collision_handling (self->priv->dispatcher, self->priv->collisions);
+
+  if (!epc_dispatcher_run (self->priv->dispatcher, error))
+    return FALSE;
+
+  if (EPC_PROTOCOL_UNKNOWN == self->priv->protocol)
+    self->priv->protocol = EPC_PROTOCOL_HTTPS;
+
+  if (EPC_PROTOCOL_HTTPS == self->priv->protocol && (
+      NULL == self->priv->certificate_file ||
+      NULL == self->priv->private_key_file))
+    {
+      GError *tls_error = NULL;
+      const gchar *host;
+
+      g_free (self->priv->certificate_file);
+      g_free (self->priv->private_key_file);
+
+      host = epc_shell_get_host_name (error);
+
+      if (NULL != host &&
+          !epc_tls_get_server_credentials (host,
+                                           &self->priv->certificate_file,
+                                           &self->priv->private_key_file,
+                                           &tls_error))
+        {
+          self->priv->protocol = EPC_PROTOCOL_HTTP;
+          g_warning ("%s: Cannot retrieve server credentials, using insecure transport protocol: %s",
+                     G_STRFUNC, tls_error ? tls_error->message : "No error details available.");
+          g_clear_error (&tls_error);
+
+        }
+    }
+
+  self->priv->server =
+    soup_server_new (SOUP_SERVER_SSL_CERT_FILE, self->priv->certificate_file,
+                     SOUP_SERVER_SSL_KEY_FILE, self->priv->private_key_file,
+                     SOUP_SERVER_PORT, SOUP_ADDRESS_ANY_PORT,
+                     NULL);
+
+  g_signal_connect_swapped (soup_server_get_listener (self->priv->server), "new-connection",
+                            G_CALLBACK (epc_publisher_new_connection_cb), self);
+
+  epc_publisher_install_handlers (self);
+  epc_publisher_announce (self);
+
+  base_uri = epc_publisher_get_uri (self, NULL, NULL);
+  g_print ("%s: listening on %s\n", G_STRFUNC, base_uri);
+  g_free (base_uri);
+
+  return TRUE;
+}
+
+static void
+epc_publisher_real_set_service_name (EpcPublisher *self,
+                                     const GValue *value)
+{
+  if (self->priv->server)
+    epc_publisher_remove_handlers (self);
+
+  g_free (self->priv->service_name);
+  self->priv->service_name = g_value_dup_string (value);
+
+  if (self->priv->server)
+    epc_publisher_install_handlers (self);
+
+  if (self->priv->dispatcher)
+    epc_dispatcher_set_name (self->priv->dispatcher,
+                             epc_publisher_compute_name (self));
+
+}
+
+static void
+epc_publisher_real_set_auth_flags (EpcPublisher *self,
+                                   const GValue *value)
+{
+  EpcAuthFlags flags = g_value_get_flags (value);
+
+  if (0 != (flags & EPC_AUTH_PASSWORD_TEXT_NEEDED) &&
+      EPC_PROTOCOL_HTTPS != self->priv->protocol)
+    {
+      g_warning ("%s: Basic authentication not allowed for %s",
+                 G_STRFUNC, epc_protocol_to_string (self->priv->protocol));
+      flags &= ~EPC_AUTH_PASSWORD_TEXT_NEEDED;
+    }
+
+  if (self->priv->server)
+    epc_publisher_remove_handlers (self);
+
+#ifdef HAVE_LIBSOUP22
+  self->priv->server_auth.types =
+    flags & EPC_AUTH_PASSWORD_TEXT_NEEDED ?
+    SOUP_AUTH_TYPE_BASIC : SOUP_AUTH_TYPE_DIGEST;
+#else
+  self->priv->auth_flags = flags;
+#endif
+
+  if (self->priv->server)
+    epc_publisher_install_handlers (self);
+}
+
+/**
+ * epc_publisher_set_service_cookie:
+ * @publisher: a #EpcPublisher
+ * @cookie: the new service identifier, or %NULL
+ *
+ * Changes the unique identifier of the service.
+ * See #EpcPublisher:service-cookie for details.
+ *
+ * Since: 0.3.1
+ */
+void
+epc_publisher_set_service_cookie (EpcPublisher *self,
+                                  const gchar  *cookie)
+{
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+  g_object_set (self, "service-cookie", cookie, NULL);
+}
+
+/**
+ * epc_publisher_set_collision_handling:
+ * @publisher: a #EpcPublisher
+ * @method: the new strategy
+ *
+ * Changes the collision handling strategy the publisher uses.
+ * See #EpcPublisher:collision-handling for details.
+ *
+ * Since: 0.3.1
+ */
+void
+epc_publisher_set_collision_handling (EpcPublisher         *self,
+                                      EpcCollisionHandling  method)
+{
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+  g_object_set (self, "collision-handling", method, NULL);
+}
+
+static void
+epc_publisher_real_set_contents_path (EpcPublisher *self,
+                                      const GValue *value)
+{
+  const gchar *path = g_value_get_string (value);
+
+  g_return_if_fail (NULL != path);
+  g_return_if_fail ('/' == path[0]);
+  g_return_if_fail ('\0' != path[1]);
+
+  if (NULL == self->priv->contents_path ||
+      strcmp (self->priv->contents_path, path))
+    {
+      if (self->priv->server)
+        epc_publisher_remove_handlers (self);
+
+      g_free (self->priv->contents_path);
+      self->priv->contents_path = g_value_dup_string (value);
+
+      if (self->priv->server)
+        epc_publisher_install_handlers (self);
+    }
+}
+
+static void
+epc_publisher_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  EpcPublisher *self = EPC_PUBLISHER (object);
+
+  switch (prop_id)
+    {
+      case PROP_PROTOCOL:
+        g_return_if_fail (!epc_publisher_is_server_created (self));
+        g_return_if_fail (EPC_PROTOCOL_UNKNOWN != g_value_get_enum (value));
+        self->priv->protocol = g_value_get_enum (value);
+        break;
+
+      case PROP_APPLICATION:
+        g_return_if_fail (!epc_publisher_is_server_created (self));
+
+        g_free (self->priv->application);
+        self->priv->application = g_value_dup_string (value);
+        break;
+
+      case PROP_SERVICE_NAME:
+        epc_publisher_real_set_service_name (self, value);
+        break;
+
+      case PROP_SERVICE_DOMAIN:
+        g_return_if_fail (!epc_publisher_is_server_created (self));
+
+        g_free (self->priv->service_domain);
+        self->priv->service_domain = g_value_dup_string (value);
+        break;
+
+      case PROP_SERVICE_COOKIE:
+        g_free (self->priv->service_cookie);
+        self->priv->service_cookie = g_value_dup_string (value);
+
+        if (self->priv->dispatcher)
+          epc_dispatcher_set_cookie (self->priv->dispatcher,
+                                     self->priv->service_cookie);
+        break;
+
+      case PROP_COLLISION_HANDLING:
+        self->priv->collisions = g_value_get_enum (value);
+
+        if (self->priv->dispatcher)
+          epc_dispatcher_set_collision_handling (self->priv->dispatcher,
+                                                 self->priv->collisions);
+
+        break;
+
+      case PROP_CONTENTS_PATH:
+        epc_publisher_real_set_contents_path (self, value);
+        break;
+
+      case PROP_AUTH_FLAGS:
+        epc_publisher_real_set_auth_flags (self, value);
+        break;
+
+      case PROP_CERTIFICATE_FILE:
+        g_return_if_fail (!epc_publisher_is_server_created (self));
+
+        g_free (self->priv->certificate_file);
+        self->priv->certificate_file = g_value_dup_string (value);
+        break;
+
+      case PROP_PRIVATE_KEY_FILE:
+        g_return_if_fail (!epc_publisher_is_server_created (self));
+
+        g_free (self->priv->private_key_file);
+        self->priv->private_key_file = g_value_dup_string (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+epc_publisher_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  EpcPublisher *self = EPC_PUBLISHER (object);
+
+  switch (prop_id)
+    {
+      case PROP_PROTOCOL:
+        g_value_set_enum (value, self->priv->protocol);
+        break;
+
+      case PROP_APPLICATION:
+        g_value_set_string (value, self->priv->application);
+        break;
+
+      case PROP_SERVICE_NAME:
+        g_value_set_string (value, self->priv->service_name);
+        break;
+
+      case PROP_SERVICE_DOMAIN:
+        g_value_set_string (value, self->priv->service_domain);
+        break;
+
+      case PROP_SERVICE_COOKIE:
+        g_value_set_string (value, self->priv->service_cookie);
+        break;
+
+      case PROP_COLLISION_HANDLING:
+        g_value_set_enum (value, self->priv->collisions);
+        break;
+
+      case PROP_CONTENTS_PATH:
+        g_value_set_string (value, self->priv->contents_path);
+        break;
+
+      case PROP_AUTH_FLAGS:
+        g_value_set_flags (value, self->priv->auth_flags);
+        break;
+
+      case PROP_CERTIFICATE_FILE:
+        g_value_set_string (value, self->priv->certificate_file);
+        break;
+
+      case PROP_PRIVATE_KEY_FILE:
+        g_value_set_string (value, self->priv->private_key_file);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+epc_publisher_dispose (GObject *object)
+{
+  EpcPublisher *self = EPC_PUBLISHER (object);
+
+  epc_publisher_quit (self);
+
+  if (self->priv->clients)
+    {
+      g_hash_table_unref (self->priv->clients);
+      self->priv->clients = NULL;
+    }
+
+  if (self->priv->resources)
+    {
+      g_hash_table_unref (self->priv->resources);
+      self->priv->resources = NULL;
+    }
+
+  if (self->priv->default_resource)
+    {
+      epc_resource_free (self->priv->default_resource);
+      self->priv->default_resource = NULL;
+    }
+
+  g_free (self->priv->certificate_file);
+  self->priv->certificate_file = NULL;
+
+  g_free (self->priv->private_key_file);
+  self->priv->private_key_file = NULL;
+
+  g_free (self->priv->service_name);
+  self->priv->service_name = NULL;
+
+  g_free (self->priv->service_domain);
+  self->priv->service_domain = NULL;
+
+  g_free (self->priv->service_cookie);
+  self->priv->service_cookie = NULL;
+
+  g_free (self->priv->application);
+  self->priv->application = NULL;
+
+  g_free (self->priv->contents_path);
+  self->priv->contents_path = NULL;
+
+  g_free (self->priv->default_bookmark);
+  self->priv->default_bookmark = NULL;
+
+  G_OBJECT_CLASS (epc_publisher_parent_class)->dispose (object);
+}
+
+static void
+epc_publisher_class_init (EpcPublisherClass *cls)
+{
+  GObjectClass *oclass = G_OBJECT_CLASS (cls);
+
+  oclass->set_property = epc_publisher_set_property;
+  oclass->get_property = epc_publisher_get_property;
+  oclass->dispose = epc_publisher_dispose;
+
+  g_object_class_install_property (oclass, PROP_PROTOCOL,
+                                   g_param_spec_enum ("protocol", "Protocol",
+                                                      "The transport protocol the publisher uses",
+                                                      EPC_TYPE_PROTOCOL, EPC_PROTOCOL_HTTPS,
+                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                      G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_APPLICATION,
+                                   g_param_spec_string ("application", "Application",
+                                                        "Program name for deriving the service type",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_SERVICE_NAME,
+                                   g_param_spec_string ("service-name", "Service Name",
+                                                        "User friendly name for the service",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_SERVICE_DOMAIN,
+                                   g_param_spec_string ("service-domain", "Service Domain",
+                                                        "Internet domain for publishing the service",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  /**
+   * EpcPublisher:service-cookie:
+   *
+   * Unique identifier of the service. This cookie is used for implementing
+   * #EPC_COLLISIONS_UNIQUE_SERVICE, and usually is a UUID or the MD5/SHA1/...
+   * checksum of a central document. When passing %NULL, but using the
+   * #EPC_COLLISIONS_UNIQUE_SERVICE strategy a time based UUID is
+   * generated and used as service identifier.
+   *
+   * Since: 0.3.1
+   */
+  g_object_class_install_property (oclass, PROP_SERVICE_COOKIE,
+                                   g_param_spec_string ("service-cookie", "Service Cookie",
+                                                        "Unique identifier of the service",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  /**
+   * EpcPublisher:collision-handling:
+   *
+   * The collision handling strategy the publisher uses.
+   *
+   * Since: 0.3.1
+   */
+  g_object_class_install_property (oclass, PROP_COLLISION_HANDLING,
+                                   g_param_spec_enum ("collision-handling", "Collision Handling",
+                                                      "The collision handling strategy to use",
+                                                      EPC_TYPE_COLLISION_HANDLING,
+                                                      EPC_COLLISIONS_CHANGE_NAME,
+                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                      G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_CONTENTS_PATH,
+                                   g_param_spec_string ("contents-path", "Contents Path",
+                                                        "The built-in server path for publishing resources",
+                                                        "/contents",
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_AUTH_FLAGS,
+                                   g_param_spec_flags ("auth-flags", "Authentication Flags",
+                                                      "The authentication settings to use",
+                                                      EPC_TYPE_AUTH_FLAGS, EPC_AUTH_DEFAULT,
+                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                      G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_CERTIFICATE_FILE,
+                                   g_param_spec_string ("certificate-file", "Certificate File",
+                                                        "File name for the PEM encoded server certificate",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_PRIVATE_KEY_FILE,
+                                   g_param_spec_string ("private-key-file", "Private Key File",
+                                                        "File name for the PEM encoded private server key",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_type_class_add_private (cls, sizeof (EpcPublisherPrivate));
+  g_rec_mutex_init (&epc_publisher_lock);
+}
+
+/**
+ * epc_publisher_new:
+ * @name: the human friendly service name, or %NULL
+ * @application: application name used for DNS-SD service type, or %NULL
+ * @domain: the DNS domain for announcing the service, or %NULL
+ *
+ * Creates a new #EpcPublisher object. The publisher announces its service
+ * per DNS-SD to the DNS domain specified by @domain, using @name as service
+ * name. The service type is derived from @application. When %NULL is passed
+ * for @application the value returned by g_get_prgname() is used. See
+ * epc_service_type_new() for details.
+ *
+ * Returns: The newly created #EpcPublisher object.
+ */
+EpcPublisher*
+epc_publisher_new (const gchar *name,
+                   const gchar *application,
+                   const gchar *domain)
+{
+  return g_object_new (EPC_TYPE_PUBLISHER,
+                       "service-name", name,
+                       "service-domain", domain,
+                       "application", application,
+                       NULL);
+}
+
+/**
+ * epc_publisher_add:
+ * @publisher: a #EpcPublisher
+ * @key: the key for addressing the value
+ * @data: the value to publish
+ * @length: the length of @data in bytes, or -1 if @data is a null-terminated string.
+ *
+ * Publishes a new value on the #EpcPublisher using the unique @key for
+ * addressing. When -1 is passed for @length, @data is expected to be a
+ * null-terminated string and its length in bytes is determined automatically
+ * using <function>strlen</function>.
+ *
+ * <note><para>
+ *  Values published by the #EpcPublisher can be arbitrary data, possibly
+ *  including null characters in the middle. The kind of data associated
+ *  with a @key is chosen by the application providing values and should 
+ *  be specified separately.
+ *
+ *  However, when publishing plain text it is strongly recommended
+ *  to use UTF-8 encoding to avoid internationalization issues.
+ * </para></note>
+ */
+void
+epc_publisher_add (EpcPublisher  *self,
+                   const gchar   *key,
+                   gconstpointer  data,
+                   gssize         length)
+{
+  const gchar *type = NULL;
+
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+  g_return_if_fail (NULL != data);
+  g_return_if_fail (NULL != key);
+
+  if (-1 == length)
+    {
+      length = strlen (data);
+      type = "text/plain";
+    }
+
+  epc_publisher_add_handler (self, key,
+                             epc_publisher_handle_static,
+                             epc_contents_new_dup (type, data, length),
+                             (GDestroyNotify) epc_contents_unref);
+}
+
+/**
+ * epc_publisher_add_file:
+ * @publisher: a #EpcPublisher
+ * @key: the key for addressing the file
+ * @filename: the name of the file to publish
+ *
+ * Publishes a local file on the #EpcPublisher using the unique
+ * @key for addressing. The publisher delivers the current contents
+ * of the file at the time of access.
+ */
+void
+epc_publisher_add_file (EpcPublisher  *self,
+                        const gchar   *key,
+                        const gchar   *filename)
+{
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+  g_return_if_fail (NULL != filename);
+  g_return_if_fail (NULL != key);
+
+  epc_publisher_add_handler (self, key,
+                             epc_publisher_handle_file,
+                             g_strdup (filename), g_free);
+}
+
+/**
+ * epc_publisher_add_handler:
+ * @publisher: a #EpcPublisher
+ * @key: the key for addressing the contents
+ * @handler: the #EpcContentsHandler for handling this contents
+ * @user_data: data to pass on @handler calls
+ * @destroy_data: a function for releasing @user_data
+ *
+ * Publishes contents on the #EpcPublisher which are generated by a custom
+ * #EpcContentsHandler callback. This is the most flexible method for publishing
+ * information.
+ *
+ * The @handler is called on every request matching @key.
+ * When called, @publisher, @key and @user_data are passed to the @handler.
+ * When replacing or deleting the resource referenced by @key,
+ * or when the the Publisher is destroyed, the function
+ * described by @destroy_data is called with @user_data as argument.
+ */
+void
+epc_publisher_add_handler (EpcPublisher      *self,
+                           const gchar       *key,
+                           EpcContentsHandler handler,
+                           gpointer           user_data,
+                           GDestroyNotify     destroy_data)
+{
+  EpcResource *resource;
+
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+  g_return_if_fail (NULL != handler);
+  g_return_if_fail (NULL != key);
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+
+  resource = epc_resource_new (handler, user_data, destroy_data);
+  g_hash_table_insert (self->priv->resources, g_strdup (key), resource);
+
+  g_rec_mutex_unlock (&epc_publisher_lock);
+}
+
+/**
+ * epc_publisher_get_path:
+ * @publisher: a #EpcPublisher
+ * @key: the resource key to inspect, or %NULL.
+ *
+ * Queries the path component of the URI used to publish the resource
+ * associated with @key. This is useful when referencing keys in published
+ * resources. Passing %NULL as @key retrieve the path of the root context.
+ *
+ * Returns: The resource path for @key.
+ */
+gchar*
+epc_publisher_get_path (EpcPublisher *self,
+                        const gchar  *key)
+{
+  gchar *encoded_key = NULL;
+  gchar *path = NULL;
+
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), NULL);
+
+  if (key)
+    {
+      encoded_key = soup_uri_encode (key, NULL);
+      path = g_strconcat (self->priv->contents_path, "/", encoded_key, NULL);
+      g_free (encoded_key);
+    }
+  else
+    path = g_strdup ("/");
+
+  return path;
+}
+
+/**
+ * epc_publisher_get_uri:
+ * @publisher: a #EpcPublisher
+ * @key: the resource key to inspect, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Queries the URI used to publish the resource associated with @key.
+ * This is useful when referencing keys in published resources. When
+ * passing %NULL the publisher's base URI is returned.
+ *
+ * The function fails if the publisher's host name cannot be retrieved.
+ * In that case %NULL is returned and @error is set. The error domain is
+ * #EPC_AVAHI_ERROR. Possible error codes are those of the
+ * <citetitle>Avahi</citetitle> library.
+ *
+ * Returns: The fully qualified URI for @key, or %NULL on error.
+ */
+gchar*
+epc_publisher_get_uri (EpcPublisher  *self,
+                       const gchar   *key,
+                       GError       **error)
+{
+  gchar *path = NULL;
+  gchar *url = NULL;
+
+  const gchar *host;
+  gint port;
+
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), NULL);
+
+  host = epc_publisher_get_host (self, NULL, NULL);
+  port = epc_publisher_get_port (self);
+
+  if (!host)
+    host = epc_shell_get_host_name (error);
+  if (!host)
+    return NULL;
+
+  path = epc_publisher_get_path (self, key);
+  url = epc_protocol_build_uri (self->priv->protocol, host, port, path);
+  g_free (path);
+
+  return url;
+}
+
+/**
+ * epc_publisher_remove:
+ * @publisher: a #EpcPublisher
+ * @key: the key for addressing the contents
+ *
+ * Removes a key and its associated contents from a #EpcPublisher.
+ *
+ * Returns: %TRUE if the key was found and removed from the #EpcPublisher.
+ */
+gboolean
+epc_publisher_remove (EpcPublisher *self,
+                      const gchar  *key)
+{
+  gboolean success;
+
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), FALSE);
+  g_return_val_if_fail (NULL != key, FALSE);
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+
+  if (self->priv->default_bookmark &&
+      g_str_equal (key, self->priv->default_bookmark))
+    {
+      g_free (self->priv->default_bookmark);
+      self->priv->default_bookmark = NULL;
+
+      if (self->priv->server)
+        epc_publisher_announce (self);
+    }
+
+  success = g_hash_table_remove (self->priv->resources, key);
+  g_rec_mutex_unlock (&epc_publisher_lock);
+
+  return success;
+}
+
+/**
+ * epc_publisher_lookup:
+ * @publisher: a #EcpPublisher
+ * @key: the key for addressing contents
+ *
+ * Looks up the user_data passed to epc_publisher_add_handler() for @key.
+ * Returns %NULL if the specified @key doesn't exist or wasn't published
+ * with epc_publisher_add_handler().
+ *
+ * This function allows to use the publisher as local key/value store,
+ * which is useful for instance to prevent accidental key collisions.
+ *
+ * See also: epc_publisher_has_key.
+ *
+ * Returns: The user_data associated with @key, or %NULL.
+ */
+gpointer
+epc_publisher_lookup (EpcPublisher *self,
+                      const gchar  *key)
+{
+  EpcResource *resource;
+  gpointer data = NULL;
+
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), NULL);
+  g_return_val_if_fail (NULL != key, NULL);
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+
+  resource = g_hash_table_lookup (self->priv->resources, key);
+
+  if (resource)
+    data = resource->user_data;
+
+  g_rec_mutex_unlock (&epc_publisher_lock);
+
+  return data;
+}
+
+/**
+ * epc_publisher_has_key:
+ * @publisher: a #EcpPublisher
+ * @key: the key for addressing contents
+ *
+ * Checks if information is published for @key.
+ *
+ * This function allows to use the publisher as local key/value store,
+ * which is useful for instance to prevent accidental key collisions.
+ *
+ * See also: epc_publisher_lookup.
+ *
+ * Returns: %TRUE when the publisher has information for @key,
+ * and %FALSE otherwise.
+ */
+gboolean
+epc_publisher_has_key (EpcPublisher *self,
+                       const gchar  *key)
+{
+  EpcResource *resource;
+
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), FALSE);
+  g_return_val_if_fail (NULL != key, FALSE);
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+  resource = g_hash_table_lookup (self->priv->resources, key);
+  g_rec_mutex_unlock (&epc_publisher_lock);
+
+  return (NULL != resource);
+}
+
+static void
+epc_publisher_list_cb (gpointer key,
+                       gpointer value G_GNUC_UNUSED,
+                       gpointer data)
+{
+  EpcListContext *context = data;
+
+  if (NULL == context->pattern || g_pattern_match_string (context->pattern, key))
+    context->matches = g_list_prepend (context->matches, g_strdup (key));
+}
+
+/**
+ * epc_publisher_list:
+ * @publisher: a #EpcPublisher
+ * @pattern: a glob-style pattern, or %NULL
+ *
+ * Matches published keys against patterns containing '*' (wildcard) and '?'
+ * (joker). Passing %NULL as @pattern is equivalent to passing "*" and returns
+ * all published keys. This function is useful for generating dynamic resource
+ * listings in other formats than libepc's specific format. See #GPatternSpec
+ * for information about glob-style patterns.
+ *
+ * If the call was successful, a list of keys matching @pattern is returned.
+ * If the call was not successful, it returns %NULL.
+ *
+ * The returned list should be freed when no longer needed:
+ *
+ * <programlisting>
+ *  g_list_foreach (keys, (GFunc) g_free, NULL);
+ *  g_list_free (keys);
+ * </programlisting>
+ *
+ * See also epc_consumer_list() for builtin listing capabilities.
+ *
+ * Returns: A newly allocated list of keys, or %NULL when an error occurred.
+ */
+GList*
+epc_publisher_list (EpcPublisher *self,
+                    const gchar  *pattern)
+{
+  EpcListContext context;
+
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), NULL);
+
+  context.matches = NULL;
+  context.pattern = NULL;
+
+  if (pattern && *pattern)
+    context.pattern = g_pattern_spec_new (pattern);
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+
+  g_hash_table_foreach (self->priv->resources,
+                        epc_publisher_list_cb,
+                        &context);
+
+  g_rec_mutex_unlock (&epc_publisher_lock);
+
+  if (context.pattern)
+    g_pattern_spec_free (context.pattern);
+
+  return context.matches;
+}
+
+/**
+ * epc_publisher_set_auth_handler:
+ * @publisher: a #EpcPublisher
+ * @key: the key of the resource to protect, or %NULL
+ * @handler: the #EpcAuthHandler to connect
+ * @user_data: data to pass on @handler calls
+ * @destroy_data: a function for releasing @user_data
+ *
+ * Installs an authentication handler for the specified @key.
+ * Passing %NULL as @key installs a fallback handler for all resources.
+ *
+ * The @handler is called on every request matching @key. On this call
+ * a temporary #EpcAuthContext and @user_data are passed to the @handler.
+ * The #EpcAuthContext references the @publisher and @key passed here.
+ * When replacing or deleting the resource referenced by @key, or when
+ * the publisher is destroyed, the function
+ * described by @destroy_data is called with @user_data as argument.
+ *
+ * <note><para>
+ *  This should be called after adding the resource identified by @key,
+ *  not before. For instance, after calling epc_publisher_add().
+ * </para></note>
+ *
+ * See also epc_publisher_set_auth_flags().
+ */
+void
+epc_publisher_set_auth_handler (EpcPublisher   *self,
+                                const gchar    *key,
+                                EpcAuthHandler  handler,
+                                gpointer        user_data,
+                                GDestroyNotify  destroy_data)
+{
+  EpcResource *resource;
+
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+  g_return_if_fail (NULL != handler);
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+
+  resource = epc_publisher_find_resource (self, key);
+
+  if (resource)
+    epc_resource_set_auth_handler (resource, handler, user_data, destroy_data);
+  else
+    g_warning ("%s: No resource handler found for key `%s'", G_STRFUNC, key);
+
+  g_rec_mutex_unlock (&epc_publisher_lock);
+}
+
+/**
+ * epc_publisher_add_bookmark:
+ * @publisher: a #EpcResource
+ * @key: the key of the resource to publish, or %NULL
+ * @label: the bookmark's label, or %NULL
+ *
+ * Installs a dynamic HTTP (respectively HTTPS) bookmark for @key.
+ * This allows consumption of #EpcPublisher resources by foreign
+ * applications that support ZeroConf bookmarks, but not libepc.
+ * This is useful for instance for publishing media playlists.
+ *
+ * Passing %NULL as @key installs a bookmark for the root context of the
+ * builtin web server. When passing %NULL as @label the publisher's name
+ * is used as bookmark label.
+ *
+ * <note><para>
+ *  Dynamic bookmarks must be unique within the service domain.
+ *  Therefore the @label will get modified on name collisions.
+ * </para></note>
+ *
+ * <note><para>
+ *  This should be called after adding the resource identified by @key,
+ *  not before. For instance, after calling epc_publisher_add().
+ * </para></note>
+ */
+void
+epc_publisher_add_bookmark (EpcPublisher *self,
+                            const gchar  *key,
+                            const gchar  *description)
+{
+  EpcResource *resource;
+
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+
+  resource = epc_publisher_find_resource (self, key);
+
+  if (resource)
+    {
+      if (description)
+        epc_resource_announce (resource, description);
+      else
+        self->priv->default_bookmark = g_strdup (key);
+
+      if (self->priv->server)
+        epc_publisher_announce (self);
+    }
+  else
+    g_warning ("%s: No resource handler found for key `%s'", G_STRFUNC, key);
+
+  g_rec_mutex_unlock (&epc_publisher_lock);
+}
+
+/**
+ * epc_publisher_set_service_name:
+ * @publisher: a #EpcPublisher
+ * @name: the new name of this #EpcPublisher
+ *
+ * Changes the human friendly name this #EpcPublisher uses to announce its
+ * service. See #EpcPublisher:service-name for details.
+ */
+void
+epc_publisher_set_service_name (EpcPublisher *self,
+                                const gchar  *name)
+{
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+  g_object_set (self, "service-name", name, NULL);
+}
+
+/**
+ * epc_publisher_set_credentials:
+ * @publisher: a #EpcPublisher
+ * @certfile: file name of the server certificate
+ * @keyfile: file name of the private key
+ *
+ * Changes the file names of the PEM encoded TLS credentials the publisher use
+ * for its services, when the transport #EpcPublisher:protocol is
+ * #EPC_PROTOCOL_HTTPS.
+ *
+ * See #EpcPublisher:certificate-file and
+ * #EpcPublisher:private-key-file for details.
+ */
+void
+epc_publisher_set_credentials (EpcPublisher *self,
+                               const gchar  *certfile,
+                               const gchar  *keyfile)
+{
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+
+  g_object_set (self, "certificate-file", certfile,
+                      "private-key-file", keyfile,
+                      NULL);
+}
+
+/**
+ * epc_publisher_set_protocol:
+ * @publisher: a #EpcPublisher
+ * @protocol: the transport protocol
+ *
+ * Changes the transport protocol the publisher uses.
+ * See #EpcPublisher:protocol for details.
+ */
+void
+epc_publisher_set_protocol (EpcPublisher *self,
+                            EpcProtocol   protocol)
+{
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+  g_object_set (self, "protocol", protocol, NULL);
+}
+
+/**
+ * epc_publisher_set_contents_path:
+ * @publisher: a #EpcPublisher
+ * @path: the new contents path
+ *
+ * Changes the server path used for publishing contents.
+ * See #EpcPublisher:contents-path for details.
+ */
+void
+epc_publisher_set_contents_path (EpcPublisher *self,
+                                 const gchar  *path)
+{
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+  g_object_set (self, "contents-path", path, NULL);
+}
+
+/**
+ * epc_publisher_set_auth_flags:
+ * @publisher: a #EpcPublisher
+ * @flags: new authentication settings
+ *
+ * Changes the authentication settings the publisher uses 
+ * when epc_publisher_set_auth_handler() is used.
+ * See #EpcPublisher:auth-flags for details.
+ */
+void
+epc_publisher_set_auth_flags (EpcPublisher *self,
+                              EpcAuthFlags  flags)
+{
+  g_return_if_fail (EPC_IS_PUBLISHER (self));
+  g_object_set (self, "auth-flags", flags, NULL);
+}
+
+/**
+ * epc_publisher_get_service_name:
+ * @publisher: a #EpcPublisher
+ *
+ * Queries the human friendly name this #EpcPublisher uses
+ * to announce its service. See #EpcPublisher:name for details.
+ *
+ * Returns: The human friendly name of this #EpcPublisher.
+ */
+const gchar*
+epc_publisher_get_service_name (EpcPublisher *self)
+{
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), NULL);
+  return self->priv->service_name;
+}
+
+/**
+ * epc_publisher_get_service_domain:
+ * @publisher: a #EpcPublisher
+ *
+ * Queries the DNS domain for which this #EpcPublisher announces its service.
+ * See #EpcPublisher:domain for details.
+ *
+ * Returns: The DNS-SD domain of this #EpcPublisher, or %NULL.
+ */
+const gchar*
+epc_publisher_get_service_domain (EpcPublisher *self)
+{
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), NULL);
+  return self->priv->service_domain;
+}
+
+/**
+ * epc_publisher_get_certificate_file:
+ * @publisher: a #EpcPublisher
+ *
+ * Queries the file name of the PEM encoded server certificate.
+ * See #EpcPublisher:certificate-file for details.
+ *
+ * Returns: The certificate's file name, or %NULL.
+ */
+const gchar*
+epc_publisher_get_certificate_file (EpcPublisher *self)
+{
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), NULL);
+  return self->priv->certificate_file;
+}
+
+/**
+ * epc_publisher_get_private_key_file:
+ * @publisher: a #EpcPublisher
+ *
+ * Queries the file name of the PEM encoded private server key.
+ * See #EpcPublisher:private-key-file for details.
+ *
+ * Returns: The private key's file name, or %NULL.
+ */
+const gchar*
+epc_publisher_get_private_key_file (EpcPublisher *self)
+{
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), NULL);
+  return self->priv->private_key_file;
+}
+
+/**
+ * epc_publisher_get_protocol:
+ * @publisher: a #EpcPublisher
+ *
+ * Queries the transport protocol the publisher uses.
+ * See #EpcPublisher:protocol for details.
+ *
+ * Returns: The transport protocol the publisher uses,
+ * or #EPC_PROTOCOL_UNKNOWN on error.
+ */
+EpcProtocol
+epc_publisher_get_protocol (EpcPublisher *self)
+{
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), EPC_PROTOCOL_UNKNOWN);
+  return self->priv->protocol;
+}
+
+/**
+ * epc_publisher_get_contents_path:
+ * @publisher: a #EpcPublisher
+ *
+ * Queries the server path used for publishing contents.
+ * See #EpcPublisher:contents-path for details.
+ *
+ * Returns: The server's contents path.
+ */
+const gchar*
+epc_publisher_get_contents_path (EpcPublisher *self)
+{
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), NULL);
+  return self->priv->contents_path;
+}
+
+/**
+ * epc_publisher_get_auth_flags:
+ * @publisher: a #EpcPublisher
+ *
+ * Queries the current authentication settings of the publisher.
+ * See #EpcPublisher:auth-flags for details.
+ *
+ * Returns: The authentication settings of the publisher,
+ * or #EPC_AUTH_DEFAULT on error.
+ */
+EpcAuthFlags
+epc_publisher_get_auth_flags (EpcPublisher *self)
+{
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), EPC_AUTH_DEFAULT);
+  return self->priv->auth_flags;
+}
+
+/**
+ * epc_publisher_get_service_cookie:
+ * @publisher: a #EpcPublisher
+ *
+ * Queries the unique identifier of the service.
+ * See #EpcPublisher:service-cookie for details.
+ *
+ * Returns: The unique identifier of the service, or %NULL on error.
+ * Since: 0.3.1
+ */
+const gchar*
+epc_publisher_get_service_cookie (EpcPublisher *self)
+{
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), NULL);
+  return self->priv->service_cookie;
+}
+
+/**
+ * epc_publisher_get_collision_handling:
+ * @publisher: a #EpcPublisher
+ *
+ * Queries the collision handling strategy the publisher uses.
+ * See #EpcPublisher:collision-handling for details.
+ *
+ * Returns: The publisher's collision handling strategy,
+ * or #EPC_COLLISIONS_IGNORE on error.
+ * Since: 0.3.1
+ */
+EpcCollisionHandling
+epc_publisher_get_collision_handling (EpcPublisher *self)
+{
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), EPC_COLLISIONS_IGNORE);
+  return self->priv->collisions;
+}
+
+/**
+ * epc_publisher_run:
+ * @publisher: a #EpcPublisher
+ * @error: return location for a #GError, or %NULL
+ *
+ * Starts the server component of the #EpcPublisher and blocks until it is
+ * shutdown using epc_publisher_quit(). If the server could not be started, the
+ * function returns %FALSE and sets @error. The error domain is
+ * #EPC_AVAHI_ERROR. Possible error codes are those of the
+ * <citetitle>Avahi</citetitle> library.
+ *
+ * When starting the publisher in HTTPS mode for the first time self-signed
+ * keys must be generated. Generating secure keys needs some time,
+ * so it is recommended to call epc_progress_window_install(), or
+ * epc_shell_set_progress_hooks() to provide visual feedback during that
+ * operation. Key generation takes place in a separate background thread and
+ * the calling thread waits in a GMainLoop. Therefore the UI can remain
+ * responsive when generating keys.
+ *
+ * To start the server without blocking call epc_publisher_run_async().
+ *
+ * Returns: %TRUE when the publisher was successfully started,
+ * %FALSE if an error occurred.
+ */
+gboolean
+epc_publisher_run (EpcPublisher  *self,
+                   GError       **error)
+{
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), FALSE);
+
+  if (!epc_publisher_run_async (self, error))
+    return FALSE;
+
+  if (NULL == self->priv->server_loop)
+    {
+      self->priv->server_loop = g_main_loop_new (NULL, FALSE);
+
+      g_main_loop_run (self->priv->server_loop);
+
+      g_main_loop_unref (self->priv->server_loop);
+      self->priv->server_loop = NULL;
+    }
+
+  return TRUE;
+}
+
+/**
+ * epc_publisher_run_async:
+ * @publisher: a #EpcPublisher
+ * @error: return location for a #GError, or %NULL
+ *
+ * Starts the server component of the #EpcPublisher without blocking. If the
+ * server could not be started then the function returns %FALSE and sets @error. The
+ * error domain is #EPC_AVAHI_ERROR. Possible error codes are those of the
+ * <citetitle>Avahi</citetitle> library.
+ *
+ * To stop the server component call epc_publisher_quit().
+ * See epc_publisher_run() for additional information.
+ *
+ * Returns: %TRUE when the publisher was successfully started,
+ * %FALSE if an error occurred.
+ */
+gboolean
+epc_publisher_run_async (EpcPublisher  *self,
+                         GError       **error)
+{
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), FALSE);
+
+  if (!epc_publisher_is_server_created (self) &&
+      !epc_publisher_create_server (self, error))
+    return FALSE;
+
+  if (!self->priv->server_started)
+    {
+      soup_server_run_async (self->priv->server);
+#ifdef HAVE_LIBSOUP22
+      g_object_unref (self->priv->server); /* work arround bug #494128 */
+#endif
+      self->priv->server_started = TRUE;
+    }
+
+  return TRUE;
+}
+
+static void
+epc_publisher_disconnect_idle_cb (gpointer key,
+                                  gpointer value,
+                                  gpointer data)
+{
+  SoupSocket *socket = key;
+  GSList **clients = data;
+
+  if (1 >= GPOINTER_TO_INT (value))
+    {
+      if (EPC_DEBUG_LEVEL (1))
+        epc_publisher_trace_client (G_STRFUNC, "idle client", socket);
+
+      *clients = g_slist_prepend (*clients, socket);
+    }
+}
+
+/**
+ * epc_publisher_quit:
+ * @publisher: a #EpcPublisher
+ *
+ * Stops the server component of the #EpcPublisher started with
+ * epc_publisher_run() or #epc_publisher_run_async. The functions
+ * returns %TRUE when the built-in server was running and had to
+ * be stopped. If the server wasn't running the function returns
+ * %FALSE.
+ *
+ * Returns: %TRUE when the server had to be stopped, and %FALSE otherwise.
+ */
+gboolean
+epc_publisher_quit (EpcPublisher *self)
+{
+  GSList *idle_clients = NULL;
+  gboolean was_running;
+
+  g_return_val_if_fail (EPC_IS_PUBLISHER (self), FALSE);
+
+  was_running = self->priv->server_started;
+
+  /* prevent new requests, and also cleanup auth handlers (#510435) */
+  epc_publisher_remove_handlers (self);
+
+  if (self->priv->server_loop)
+    g_main_loop_quit (self->priv->server_loop);
+
+  g_rec_mutex_lock (&epc_publisher_lock);
+
+  if (self->priv->clients)
+    g_hash_table_foreach (self->priv->clients,
+                          epc_publisher_disconnect_idle_cb,
+                          &idle_clients);
+
+  g_slist_foreach (idle_clients, (GFunc) soup_socket_disconnect, NULL);
+  g_slist_free (idle_clients);
+
+  g_rec_mutex_unlock (&epc_publisher_lock);
+
+  if (self->priv->dispatcher)
+    {
+      g_object_unref (self->priv->dispatcher);
+      self->priv->dispatcher = NULL;
+    }
+
+  if (self->priv->server)
+    {
+      g_object_unref (self->priv->server);
+      self->priv->server = NULL;
+    }
+
+  self->priv->server_started = FALSE;
+
+  return was_running;
+}
+
+static gchar*
+epc_utf8_strtitle (const gchar *str,
+                   gssize       len)
+{
+    gunichar     first_chr;
+    gchar        first_str[7];
+    gint         first_len;
+
+    const gchar *tail_str;
+    gsize        tail_len;
+
+    gchar       *lower_str;
+    gsize        lower_len;
+
+    gchar       *title_str;
+
+    g_return_val_if_fail (NULL != str, NULL);
+
+    if (-1 == len)
+      len = strlen (str);
+
+    first_chr = g_utf8_get_char_validated (str, len);
+
+    if ((gint) first_chr < 0)
+      return NULL;
+
+    first_chr = g_unichar_totitle (first_chr);
+    first_len = g_unichar_to_utf8 (first_chr, first_str);
+
+    tail_str = g_utf8_next_char (str);
+    tail_len = len - (tail_str - str);
+
+    lower_str = g_utf8_strdown (tail_str, tail_len);
+    lower_len = strlen (lower_str);
+
+    len = first_len + lower_len;
+    title_str = g_new (gchar, len + 1);
+    title_str[len] = '\0';
+
+    memcpy (title_str, first_str, first_len);
+    memcpy (title_str + first_len, lower_str, lower_len);
+
+    g_free (lower_str);
+
+    return title_str;
+}
+
+/**
+ * epc_publisher_expand_name:
+ * @name: a service name with placeholders
+ * @error: return location for a #GError, or %NULL
+ *
+ * Expands all known placeholders in @name. Supported placeholders are:
+ *
+ * <itemizedlist>
+ *  <listitem>%%a: the program name as returned by g_get_application_name()</listitem>
+ *  <listitem>%%h: the machine's host name in title case</listitem>
+ *  <listitem>%%u: the user's login name in title case</listitem>
+ *  <listitem>%%U: the user's real name</listitem>
+ *  <listitem>%%: the percent sign</listitem>
+ * </itemizedlist>
+ *
+ * The function fails when the host name cannot looked up. In that case %NULL
+ * is returned and @error is set. The error domain is #EPC_AVAHI_ERROR.
+ * Possible error codes are those of the <citetitle>Avahi</citetitle> library.
+ *
+ * Returns: The @name with all known placeholders expanded, or %NULL on error.
+ */
+gchar*
+epc_publisher_expand_name (const gchar  *name,
+                           GError      **error)
+{
+  gchar *tcase_host = NULL;
+  const gchar *host = NULL;
+  const gchar *tail = NULL;
+
+  GString *expand = NULL;
+
+  if (NULL == name)
+    name = _("%a of %u on %h");
+
+  host = epc_shell_get_host_name (error);
+
+  if (NULL == host)
+    return NULL;
+
+  expand = g_string_new (NULL);
+
+  while (NULL != (tail = strchr (name, '%')))
+    {
+      const gchar *subst = NULL;
+      gchar *temp_str1 = NULL;
+      gchar *temp_str2 = NULL;
+      gsize  temp_len;
+
+      g_string_append_len (expand, name, tail - name);
+
+      switch (tail[1])
+        {
+          case 'u':
+            temp_str1 = g_filename_to_utf8 (g_get_user_name (), -1, NULL, &temp_len, NULL);
+            temp_str2 = epc_utf8_strtitle (temp_str1, temp_len);
+            subst = temp_str2;
+            break;
+
+          case 'U':
+            temp_str1 = g_filename_to_utf8 (g_get_real_name (), -1, NULL, NULL, NULL);
+            subst = temp_str1;
+            break;
+            break;
+
+          case 'a':
+            subst = g_get_application_name ();
+            break;
+
+          case 'h':
+            if (!tcase_host)
+              tcase_host = epc_utf8_strtitle (host, -1);
+
+            subst = tcase_host;
+            break;
+
+          case '%':
+            subst = "%";
+            break;
+
+          default:
+            g_warning ("%s: Unexpected character.", G_STRFUNC);
+            break;
+        }
+
+      if (subst)
+        {
+          g_string_append (expand, subst);
+          name = tail + 2;
+        }
+      else
+        {
+          g_string_append_c (expand, *tail);
+          name = tail + 1;
+        }
+
+      g_free (temp_str2);
+      g_free (temp_str1);
+    }
+
+  g_string_append (expand, name);
+  g_free (tcase_host);
+
+  return g_string_free (expand, FALSE);
+}
+
+/**
+ * epc_auth_context_get_publisher:
+ * @context: a #EpcAuthContext
+ *
+ * Queries the #EpcPublisher owning the authentication @context.
+ *
+ * Returns: The owning #EpcPublisher.
+ */
+EpcPublisher*
+epc_auth_context_get_publisher (const EpcAuthContext *context)
+{
+  g_return_val_if_fail (NULL != context, NULL);
+  return context->publisher;
+}
+
+/**
+ * epc_auth_context_get_key:
+ * @context: a #EpcAuthContext
+ *
+ * Queries the resource key associated with the authentication @context.
+ *
+ * Returns: The resource key.
+ */
+const gchar*
+epc_auth_context_get_key (const EpcAuthContext *context)
+{
+  g_return_val_if_fail (NULL != context, NULL);
+  return context->key;
+}
+
+/**
+ * epc_auth_context_get_password:
+ * @context: a #EpcAuthContext
+ *
+ * Queries the password sent for the authentication @context when Basic
+ * authentication was allowed for the @context, and %NULL otherwise.
+ *
+ * See also: #EPC_AUTH_PASSWORD_TEXT_NEEDED
+ *
+ * Returns: The password sent, or %NULL.
+ */
+const gchar*
+epc_auth_context_get_password (const EpcAuthContext *context)
+{
+  g_return_val_if_fail (NULL != context, NULL);
+
+#ifdef HAVE_LIBSOUP22
+
+  if (NULL != context->auth &&
+      SOUP_AUTH_TYPE_BASIC == context->auth->type)
+    return context->auth->basic.passwd;
+
+  return NULL;
+
+#else
+
+  return context->password;
+
+#endif
+}
+
+/**
+ * epc_auth_context_check_password:
+ * @context: a #EpcAuthContext
+ * @password: the expected password
+ *
+ * Verifies that the password supplied with the network request matches
+ * the @password the application expects. There is no way to retrieve the
+ * password from the #EpcAuthContext, as the network protocol transfers
+ * just a hash code, not the actual password.
+ *
+ * Returns: %TRUE when the sent password matches, or %FALSE otherwise.
+ */
+gboolean
+epc_auth_context_check_password (const EpcAuthContext *context,
+                                 const gchar          *password)
+{
+  g_return_val_if_fail (NULL != context, FALSE);
+  g_return_val_if_fail (NULL != password, FALSE);
+
+#ifdef HAVE_LIBSOUP22
+
+  return
+    NULL != context->auth &&
+    soup_server_auth_check_passwd (context->auth, (gchar*) password);
+
+#else
+
+  return soup_auth_domain_check_password (context->publisher->priv->server_auth,
+                                          context->message, context->username,
+                                          password);
+
+#endif
+}
+
+/* vim: set sw=2 sta et spl=en spell: */
diff --git a/glom/libglom/libepc/publisher.h b/glom/libglom/libepc/publisher.h
new file mode 100644
index 0000000..c087133
--- /dev/null
+++ b/glom/libglom/libepc/publisher.h
@@ -0,0 +1,224 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+#ifndef __EPC_PUBLISHER_H__
+#define __EPC_PUBLISHER_H__
+
+#include <libepc/contents.h>
+#include <libepc/dispatcher.h>
+#include <libepc/service-type.h>
+
+G_BEGIN_DECLS
+
+#define EPC_TYPE_PUBLISHER           (epc_publisher_get_type())
+#define EPC_PUBLISHER(obj)           (G_TYPE_CHECK_INSTANCE_CAST(obj, EPC_TYPE_PUBLISHER, EpcPublisher))
+#define EPC_PUBLISHER_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST(cls, EPC_TYPE_PUBLISHER, EpcPublisherClass))
+#define EPC_IS_PUBLISHER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE(obj, EPC_TYPE_PUBLISHER))
+#define EPC_IS_PUBLISHER_CLASS(obj)  (G_TYPE_CHECK_CLASS_TYPE(obj, EPC_TYPE_PUBLISHER))
+#define EPC_PUBLISHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPC_TYPE_PUBLISHER, 
EpcPublisherClass))
+
+typedef struct _EpcAuthContext                  EpcAuthContext;
+typedef struct _EpcPublisher                    EpcPublisher;
+typedef struct _EpcPublisherClass               EpcPublisherClass;
+typedef struct _EpcPublisherPrivate             EpcPublisherPrivate;
+
+/**
+ * EpcContentsHandler:
+ * @publisher: the #EpcPublisher
+ * @key: the unique key
+ * @user_data: user data set when the signal handler was installed
+ *
+ * This callback is used to generate custom contents published with the
+ * #epc_publisher_add_handler function. The arguments passed are the same as
+ * passed to #epc_publisher_add_handler. The #EpcPublisher will decrease the
+ * reference count of the returned buffer after deliving it. It's valid to
+ * return %NULL in situations were no contents can be generated.
+ *
+ * Returns: The #EpcContents buffer for this publication, or %NULL.
+ */
+typedef EpcContents* (*EpcContentsHandler) (EpcPublisher   *publisher,
+                                            const gchar    *key,
+                                            gpointer        user_data);
+
+/**
+ * EpcAuthHandler:
+ * @context: the #EpcAuthContext
+ * @username: the username provided for authentication, or %NULL
+ * @user_data: user data set when the signal handler was installed
+ *
+ * Functions implementing this callback shall return %TRUE when the
+ * credentials provided by the authentication request grant access
+ * to the resource described by @context.
+ *
+ * The @username is %NULL when no creditials were passed, and anonymous access
+ * is tried.
+ * 
+ * See also #epc_publisher_set_auth_flags. When EPC_AUTH_DEFAULT is used, 
+ * you should call #epc_auth_context_check_password
+ * to verify that the password passed in the request matches the known password
+ * for that user. In this case there is no way to retrieve the password from 
+ * the #EpcAuthContext because the network protocol transfers just a hash code, 
+ * not the actual password.
+ *
+ * However, when EPC_AUTH_PASSWORD_TEXT_NEEDED is used, you should call 
+ * epc_auth_context_get_password() and then do your own authentication check. 
+ * For instance, you might need to delegate the authentication to some other 
+ * code or server, such as a database server.
+ *
+ * Returns: %TRUE when access is granted, and %FALSE otherwise.
+ */
+typedef gboolean    (*EpcAuthHandler)    (EpcAuthContext *context,
+                                          const gchar    *username,
+                                          gpointer        user_data);
+
+/**
+ * EpcAuthFlags:
+ * @EPC_AUTH_DEFAULT: The default authentication settings.
+ * @EPC_AUTH_PASSWORD_TEXT_NEEDED: Set this flag when your #EpcAuthFlags
+ * needs the supplied password in plain text - for instance to pass it to a
+ * database server used by your application. This flag replaces the secure Digest
+ * authentication scheme with the insecure Basic authentication scheme.
+ * Therefore this setting is valid only when the publisher's transport
+ * protocol is #EPC_PROTOCOL_HTTPS (secure http).
+ *
+ * These flags specify the authentication behaviour of an #EpcPublisher.
+ */
+
+typedef enum /*< flags >*/
+{
+  EPC_AUTH_DEFAULT =                     0,
+  EPC_AUTH_PASSWORD_TEXT_NEEDED =       (1 << 0)
+} EpcAuthFlags;
+
+/**
+ * EpcPublisher:
+ *
+ * Public fields of the #EpcPublisher class.
+ */
+struct _EpcPublisher
+{
+  /*< private >*/
+  GObject parent_instance;
+  EpcPublisherPrivate *priv;
+
+  /*< public >*/
+};
+
+/**
+ * EpcPublisherClass:
+ *
+ * Virtual methods of the #EpcPublisher class.
+ */
+struct _EpcPublisherClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+};
+
+GType                 epc_publisher_get_type               (void) G_GNUC_CONST;
+
+EpcPublisher*         epc_publisher_new                    (const gchar           *name,
+                                                            const gchar           *application,
+                                                            const gchar           *domain);
+
+void                  epc_publisher_set_service_name       (EpcPublisher          *publisher,
+                                                            const gchar           *name);
+void                  epc_publisher_set_credentials        (EpcPublisher          *publisher,
+                                                            const gchar           *certfile,
+                                                            const gchar           *keyfile);
+void                  epc_publisher_set_protocol           (EpcPublisher          *publisher,
+                                                            EpcProtocol            protocol);
+void                  epc_publisher_set_contents_path      (EpcPublisher          *publisher,
+                                                            const gchar           *path);
+void                  epc_publisher_set_auth_flags         (EpcPublisher          *publisher,
+                                                            EpcAuthFlags           flags);
+void                  epc_publisher_set_collision_handling (EpcPublisher          *publisher,
+                                                            EpcCollisionHandling   method);
+void                  epc_publisher_set_service_cookie     (EpcPublisher          *publisher,
+                                                            const gchar           *cookie);
+
+const gchar* epc_publisher_get_service_name       (EpcPublisher          *publisher);
+const gchar* epc_publisher_get_service_domain     (EpcPublisher          *publisher);
+const gchar* epc_publisher_get_certificate_file   (EpcPublisher          *publisher);
+const gchar* epc_publisher_get_private_key_file   (EpcPublisher          *publisher);
+EpcProtocol           epc_publisher_get_protocol           (EpcPublisher          *publisher);
+const gchar* epc_publisher_get_contents_path      (EpcPublisher          *publisher);
+EpcAuthFlags          epc_publisher_get_auth_flags         (EpcPublisher          *publisher);
+EpcCollisionHandling  epc_publisher_get_collision_handling (EpcPublisher          *publisher);
+const gchar* epc_publisher_get_service_cookie     (EpcPublisher          *publisher);
+
+void                  epc_publisher_add                    (EpcPublisher          *publisher,
+                                                            const gchar           *key,
+                                                            gconstpointer          data,
+                                                            gssize                 length);
+void                  epc_publisher_add_file               (EpcPublisher          *publisher,
+                                                            const gchar           *key,
+                                                            const gchar           *filename);
+void                  epc_publisher_add_handler            (EpcPublisher          *publisher,
+                                                            const gchar           *key,
+                                                            EpcContentsHandler     handler,
+                                                            gpointer               user_data,
+                                                            GDestroyNotify         destroy_data);
+
+void                  epc_publisher_set_auth_handler       (EpcPublisher          *publisher,
+                                                            const gchar           *key,
+                                                            EpcAuthHandler         handler,
+                                                            gpointer               user_data,
+                                                            GDestroyNotify         destroy_data);
+
+void                  epc_publisher_add_bookmark           (EpcPublisher          *publisher,
+                                                            const gchar           *key,
+                                                            const gchar           *label);
+
+gchar*                epc_publisher_get_path               (EpcPublisher          *publisher,
+                                                            const gchar           *key);
+gchar*                epc_publisher_get_uri                (EpcPublisher          *publisher,
+                                                            const gchar           *key,
+                                                            GError               **error);
+
+gboolean              epc_publisher_remove                 (EpcPublisher          *publisher,
+                                                            const gchar           *key);
+gpointer              epc_publisher_lookup                 (EpcPublisher          *publisher,
+                                                            const gchar           *key);
+gboolean              epc_publisher_has_key                (EpcPublisher          *publisher,
+                                                            const gchar           *key);
+GList*                epc_publisher_list                   (EpcPublisher          *publisher,
+                                                            const gchar           *pattern);
+
+gboolean              epc_publisher_run                    (EpcPublisher          *publisher,
+                                                            GError               **error);
+gboolean              epc_publisher_run_async              (EpcPublisher          *publisher,
+                                                            GError               **error);
+gboolean              epc_publisher_quit                   (EpcPublisher          *publisher);
+
+gchar*                epc_publisher_expand_name            (const gchar           *name,
+                                                            GError               **error);
+
+EpcPublisher*         epc_auth_context_get_publisher       (const EpcAuthContext  *context);
+const gchar* epc_auth_context_get_key             (const EpcAuthContext  *context);
+const gchar* epc_auth_context_get_password        (const EpcAuthContext  *context);
+gboolean              epc_auth_context_check_password      (const EpcAuthContext  *context,
+                                                            const gchar           *password);
+
+G_END_DECLS
+
+#endif /* __EPC_PUBLISHER_H__ */
diff --git a/glom/libglom/libepc/service-info.c b/glom/libglom/libepc/service-info.c
new file mode 100644
index 0000000..914d220
--- /dev/null
+++ b/glom/libglom/libepc/service-info.c
@@ -0,0 +1,319 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#include <libglom/libepc/service-info.h>
+
+#include <string.h>
+
+/**
+ * SECTION:service-info
+ * @short_description: DNS-SD service descriptions
+ * @include: libepc/service-info.h
+ * @stability: Unstable
+ *
+ * The #EpcServiceInfo object describes DNS-SD services.
+ */
+
+/**
+ * EpcServiceInfo:
+ *
+ * Description of a network service.
+ * See also: epc_service_monitor_new().
+ */
+struct _EpcServiceInfo
+{
+  volatile gint    ref_count;
+
+  gchar           *type;
+  gchar           *host;
+  guint            port;
+
+  AvahiStringList *details;
+
+  AvahiAddress    *address;
+  gchar           *ifname;
+};
+
+GType
+epc_service_info_get_type (void)
+{
+  static GType type = G_TYPE_INVALID;
+
+  if (G_UNLIKELY (!type))
+    type = g_boxed_type_register_static ("EpcServiceInfo",
+                                         (GBoxedCopyFunc) epc_service_info_ref,
+                                         (GBoxedFreeFunc) epc_service_info_unref);
+
+  return type;
+}
+
+/**
+ * epc_service_info_new_full:
+ * @type: the DNS-SD service type
+ * @host: the DNS hostname
+ * @port: the TCP/IP port
+ * @details: list of key-value pairs, or %NULL
+ * @address: IP address of the service, or %NULL
+ * @ifname: network interface for contacting the service, or %NULL
+ *
+ * Creates a new service description using the information provided. The
+ * @details list usually is retrieved from the TXT record the dynamic naming
+ * system (DNS) provides for the service. When using Avahi's service chooser
+ * aui_service_dialog_get_txt_data() can be used for getting a @details list.
+ * To create an ad-hoc list use avahi_string_list_new() and related functions.
+ *
+ * Returns: The newly created service description, or %NULL on error.
+ */
+EpcServiceInfo*
+epc_service_info_new_full (const gchar           *type,
+                           const gchar           *host,
+                           guint                  port,
+                           const AvahiStringList *details,
+                           const AvahiAddress    *address,
+                           const gchar           *ifname)
+{
+  EpcServiceInfo *self;
+
+  g_return_val_if_fail (NULL != type, NULL);
+  g_return_val_if_fail (NULL != host, NULL);
+  g_return_val_if_fail (port != 0,    NULL);
+
+  self = g_slice_new0 (EpcServiceInfo);
+
+  self->ref_count = 1;
+  self->type = g_strdup (type);
+  self->host = g_strdup (host);
+  self->port = port;
+
+  if (details)
+    self->details = avahi_string_list_copy (details);
+
+  if (address)
+    self->address = g_memdup (address, sizeof *address);
+  if (ifname)
+    self->ifname = g_strdup (ifname);
+
+  return self;
+}
+
+/**
+ * epc_service_info_new:
+ * @type: the DNS-SD service type
+ * @host: the DNS hostname
+ * @port: the TCP/IP port
+ * @details: list of key-value pairs, or %NULL
+ *
+ * Creates a new service description using the information provided. The
+ * @details list usually is retrieved from the TXT record the dynamic naming
+ * system (DNS) provides for the service. When using Avahi's service chooser
+ * aui_service_dialog_get_txt_data() can be used for getting a @details list.
+ * To create an ad-hoc list use avahi_string_list_new() and related functions.
+ *
+ * Returns: The newly created service description, or %NULL on error.
+ */
+EpcServiceInfo*
+epc_service_info_new (const gchar           *type,
+                      const gchar           *host,
+                      guint                  port,
+                      const AvahiStringList *details)
+{
+  return epc_service_info_new_full (type, host, port, details, NULL, NULL);
+}
+
+/**
+ * epc_service_info_ref:
+ * @info: a #EpcServiceInfo
+ *
+ * Increases the reference count of @info by one.
+ * See also: epc_service_info_unref()
+ *
+ * Returns: The same @info object.
+ */
+EpcServiceInfo*
+epc_service_info_ref (EpcServiceInfo *self)
+{
+  g_return_val_if_fail (EPC_IS_SERVICE_INFO (self), NULL);
+  g_atomic_int_inc (&self->ref_count);
+  return self;
+}
+
+/**
+ * epc_service_info_unref:
+ * @info: a #EpcServiceInfo
+ *
+ * Decreases the reference count of @info by one. When its reference count
+ * drops to 0, the object is finalized (i.e. its memory is freed).
+ *
+ * See also: epc_service_info_ref()
+ */
+void
+epc_service_info_unref (EpcServiceInfo *self)
+{
+  g_return_if_fail (EPC_IS_SERVICE_INFO (self));
+
+  if (g_atomic_int_dec_and_test (&self->ref_count))
+    {
+      g_free (self->address);
+      g_free (self->ifname);
+      g_free (self->type);
+      g_free (self->host);
+
+      if (self->details)
+        avahi_string_list_free (self->details);
+
+      g_slice_free (EpcServiceInfo, self);
+    }
+}
+
+/**
+ * epc_service_info_get_service_type:
+ * @info: a #EpcServiceInfo
+ *
+ * Retrieves the DNS-SD service type associated with @info.
+ *
+ * Returns: A DNS-SD service type.
+ */
+const gchar*
+epc_service_info_get_service_type (const EpcServiceInfo *self)
+{
+  g_return_val_if_fail (NULL != self, NULL);
+  return self->type;
+}
+
+/**
+ * epc_service_info_get_host:
+ * @info: a #EpcServiceInfo
+ *
+ * Retrieves the DNS host name associated with @info.
+ *
+ * Returns: A DNS host name.
+ */
+const gchar*
+epc_service_info_get_host (const EpcServiceInfo *self)
+{
+  g_return_val_if_fail (NULL != self, NULL);
+  return self->host;
+}
+
+/**
+ * epc_service_info_get_port:
+ * @info: a #EpcServiceInfo
+ *
+ * Retrieves the TCP/IP port associated with @info.
+ *
+ * Returns: A TCP/IP port.
+ */
+guint
+epc_service_info_get_port (const EpcServiceInfo *self)
+{
+  g_return_val_if_fail (NULL != self, 0);
+  return self->port;
+}
+
+/**
+ * epc_service_info_get_detail:
+ * @info: a #EpcServiceInfo
+ * @name: the detail's name
+ *
+ * Retrieves a detail stored in the service's TXT record.
+ * Returns %NULL when the requested information is not available.
+ *
+ * Returns: The requested service detail, or %NULL.
+ */
+const gchar*
+epc_service_info_get_detail (const EpcServiceInfo *self,
+                             const gchar          *name)
+{
+  AvahiStringList *match = NULL;
+  const gchar *detail = NULL;
+
+  g_return_val_if_fail (NULL != self, NULL);
+  g_return_val_if_fail (NULL != name, NULL);
+
+  if (self->details)
+    match = avahi_string_list_find (self->details, name);
+
+  if (match)
+    {
+      gsize len = strlen (name);
+
+      g_assert (!memcmp (match->text, name, len));
+
+      if ('=' == match->text[len])
+        detail = (gchar*) &match->text[len + 1];
+    }
+
+  return detail;
+}
+
+/**
+ * epc_service_info_get_interface:
+ * @info: a #EpcServiceInfo
+ *
+ * Retrieves the name of the network interface which must be used for
+ * contacting the service, or %NULL when that information is not available.
+ *
+ * Returns: A network interface name, or %NULL.
+ */
+const gchar*
+epc_service_info_get_interface (const EpcServiceInfo *self)
+{
+  g_return_val_if_fail (NULL != self, NULL);
+  return self->ifname;
+}
+
+/**
+ * epc_service_info_get_address_family:
+ * @info: a #EpcServiceInfo
+ *
+ * Retrieves the address family for contacting the service,
+ * or #EPC_ADDRESS_UNSPEC when that information is not available.
+ *
+ * Returns: A #EpcAddressFamily.
+ */
+EpcAddressFamily
+epc_service_info_get_address_family (const EpcServiceInfo *self)
+{
+  g_return_val_if_fail (NULL != self, EPC_ADDRESS_UNSPEC);
+
+  if (self->address)
+    return avahi_proto_to_af (self->address->proto);
+
+  return EPC_ADDRESS_UNSPEC;
+}
+
+/**
+ * epc_service_info_get_address:
+ * @info: a #EpcServiceInfo
+ *
+ * Retrieves the IP address for contacting the service,
+ * or %NULL when that information is not available.
+ *
+ * Returns: A IP address, or %NULL.
+ */
+const AvahiAddress*
+epc_service_info_get_address (const EpcServiceInfo *self)
+{
+  g_return_val_if_fail (NULL != self, NULL);
+  return self->address;
+}
+
+
diff --git a/glom/libglom/libepc/service-info.h b/glom/libglom/libepc/service-info.h
new file mode 100644
index 0000000..76fa122
--- /dev/null
+++ b/glom/libglom/libepc/service-info.h
@@ -0,0 +1,81 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#ifndef __EPC_SERVICE_INFO_H__
+#define __EPC_SERVICE_INFO_H__
+
+#include <avahi-common/address.h>
+#include <avahi-common/strlst.h>
+#include <glib-object.h>
+#include <sys/socket.h>
+
+G_BEGIN_DECLS
+
+#define EPC_TYPE_SERVICE_INFO    (epc_service_info_get_type())
+#define EPC_IS_SERVICE_INFO(obj) (NULL != (obj))
+
+typedef struct _EpcServiceInfo EpcServiceInfo;
+
+/**
+ * EpcAddressFamily:
+ * @EPC_ADDRESS_UNSPEC: No preferences exist. Use all address families supported.
+ * @EPC_ADDRESS_IPV4: Exclusively use IPv4 for addressing network services.
+ * @EPC_ADDRESS_IPV6: Exclusively use IPv6 for addressing network services.
+ *
+ * The address family to use for contacting network services.
+ */
+typedef enum
+{
+  EPC_ADDRESS_UNSPEC = AF_UNSPEC,
+  EPC_ADDRESS_IPV4 = AF_INET,
+  EPC_ADDRESS_IPV6 = AF_INET6
+}
+EpcAddressFamily;
+
+GType                        epc_service_info_get_type           (void) G_GNUC_CONST;
+
+EpcServiceInfo*              epc_service_info_new                (const gchar           *type,
+                                                                  const gchar           *host,
+                                                                  guint                  port,
+                                                                  const AvahiStringList *details);
+EpcServiceInfo*              epc_service_info_new_full           (const gchar           *type,
+                                                                  const gchar           *host,
+                                                                  guint                  port,
+                                                                  const AvahiStringList *details,
+                                                                  const AvahiAddress    *address,
+                                                                  const gchar           *ifname);
+
+EpcServiceInfo*              epc_service_info_ref                (EpcServiceInfo        *info);
+void                         epc_service_info_unref              (EpcServiceInfo        *info);
+
+const gchar*        epc_service_info_get_service_type   (const EpcServiceInfo  *info);
+const gchar*        epc_service_info_get_host           (const EpcServiceInfo  *info);
+guint                        epc_service_info_get_port           (const EpcServiceInfo  *info);
+const gchar*        epc_service_info_get_detail         (const EpcServiceInfo  *info,
+                                                                  const gchar           *name);
+
+const gchar*        epc_service_info_get_interface      (const EpcServiceInfo  *info);
+EpcAddressFamily             epc_service_info_get_address_family (const EpcServiceInfo  *info);
+const AvahiAddress* epc_service_info_get_address        (const EpcServiceInfo  *info);
+
+G_END_DECLS
+
+#endif /* __EPC_SERVICE_INFO_H__ */ 
diff --git a/glom/libglom/libepc/service-monitor.c b/glom/libglom/libepc/service-monitor.c
new file mode 100644
index 0000000..fc9adf9
--- /dev/null
+++ b/glom/libglom/libepc/service-monitor.c
@@ -0,0 +1,592 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#include <libglom/libepc/service-monitor.h>
+
+#include <libglom/libepc/marshal.h>
+#include <libglom/libepc/service-type.h>
+#include <libglom/libepc/shell.h>
+
+#include <avahi-common/error.h>
+#include <net/if.h>
+
+/**
+ * SECTION:service-monitor
+ * @short_description: find DNS-SD services
+ * @include: libepc/service-monitor.h
+ * @stability: Unstable
+ *
+ * The #EpcServiceMonitor object provides an easy method for finding DNS-SD
+ * services. It hides all the boring state and callback handling Avahi
+ * requires, and just exposes three simple GObject signals:
+ * #EpcServiceMonitor:service-found, #EpcServiceMonitor:service-removed
+ * and #EpcServiceMonitor:scanning-done.
+ *
+ * <example id="find-services">
+ *  <title>Find an Easy-Publish service</title>
+ *  <programlisting>
+ *   monitor = epc_service_monitor_new ("glom", NULL, EPC_PROTOCOL_UNKNOWN);
+ *
+ *   g_signal_connect (monitor, "service-found", service_found_cb, self);
+ *   g_signal_connect (monitor, "service-removed", service_removed_cb, self);
+ *
+ *   g_main_loop_run (loop);
+ *  </programlisting>
+ * </example>
+ */
+
+enum
+{
+  PROP_NONE,
+  PROP_SERVICE_TYPES,
+  PROP_APPLICATION,
+  PROP_DOMAIN,
+  PROP_SKIP_OUR_OWN
+};
+
+enum
+{
+  SIGNAL_SERVICE_FOUND,
+  SIGNAL_SERVICE_REMOVED,
+  SIGNAL_SCANNING_DONE,
+  SIGNAL_LAST
+};
+
+/**
+ * EpcServiceMonitorPrivate:
+ *
+ * Private fields of the #EpcServiceMonitor class.
+ */
+struct _EpcServiceMonitorPrivate
+{
+  GSList  *browsers;
+  gchar   *application;
+  gchar   *domain;
+  gchar  **types;
+  gboolean skip_our_own;
+};
+
+static guint signals[SIGNAL_LAST];
+
+G_DEFINE_TYPE (EpcServiceMonitor, epc_service_monitor, G_TYPE_OBJECT);
+
+static void
+epc_service_monitor_init (EpcServiceMonitor *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                            EPC_TYPE_SERVICE_MONITOR,
+                                            EpcServiceMonitorPrivate);
+}
+
+static void
+epc_service_monitor_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  EpcServiceMonitor *self = EPC_SERVICE_MONITOR (object);
+
+  switch (prop_id)
+    {
+      case PROP_SERVICE_TYPES:
+        g_assert (NULL == self->priv->browsers);
+        self->priv->types = g_value_dup_boxed (value);
+        break;
+
+      case PROP_APPLICATION:
+        g_assert (NULL == self->priv->browsers);
+        self->priv->application = g_value_dup_string (value);
+        break;
+
+      case PROP_DOMAIN:
+        g_assert (NULL == self->priv->browsers);
+        self->priv->domain = g_value_dup_string (value);
+        break;
+
+      case PROP_SKIP_OUR_OWN:
+        self->priv->skip_our_own = g_value_get_boolean (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+epc_service_monitor_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  EpcServiceMonitor *self = EPC_SERVICE_MONITOR (object);
+
+  switch (prop_id)
+    {
+      case PROP_SERVICE_TYPES:
+        g_value_set_boxed (value, self->priv->types);
+        break;
+
+      case PROP_APPLICATION:
+        g_value_set_string (value, self->priv->application);
+        break;
+
+      case PROP_DOMAIN:
+        g_value_set_string (value, self->priv->domain);
+        break;
+
+      case PROP_SKIP_OUR_OWN:
+        g_value_set_boolean (value, self->priv->skip_our_own);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+epc_service_monitor_resolver_cb (AvahiServiceResolver   *resolver,
+                                 AvahiIfIndex            ifindex,
+                                 AvahiProtocol           protocol,
+                                 AvahiResolverEvent      event,
+                                 const char             *name,
+                                 const char             *type,
+                                 const char             *domain G_GNUC_UNUSED,
+                                 const char             *hostname,
+                                 const AvahiAddress     *address,
+                                 uint16_t                port,
+                                 AvahiStringList        *txt,
+                                 AvahiLookupResultFlags  flags G_GNUC_UNUSED,
+                                 void                   *data)
+{
+  EpcServiceMonitor *self = EPC_SERVICE_MONITOR (data);
+  gchar ifname[IFNAMSIZ];
+  EpcServiceInfo *info;
+  gint error;
+
+  switch (event)
+    {
+      case AVAHI_RESOLVER_FOUND:
+        if (EPC_DEBUG_LEVEL (1))
+          g_debug ("%s: Service resolved: type='%s', hostname='%s', port=%d, protocol=%s",
+                   G_STRLOC, type, hostname, port, avahi_proto_to_string (protocol));
+
+        g_assert (protocol == address->proto);
+
+        info = epc_service_info_new_full (type, hostname, port, txt, address,
+                                          if_indextoname (ifindex, ifname));
+        g_signal_emit (self, signals[SIGNAL_SERVICE_FOUND], 0, name, info);
+        epc_service_info_unref (info);
+        break;
+
+      case AVAHI_RESOLVER_FAILURE:
+        error = avahi_client_errno (avahi_service_resolver_get_client (resolver));
+        g_warning ("%s: %s (%d)", G_STRFUNC, avahi_strerror (error), error);
+        break;
+    }
+
+  avahi_service_resolver_free (resolver);
+}
+
+static void
+epc_service_monitor_browser_cb (AvahiServiceBrowser    *browser,
+                                AvahiIfIndex            interface,
+                                AvahiProtocol           protocol,
+                                AvahiBrowserEvent       event,
+                                const char             *name,
+                                const char             *type,
+                                const char             *domain,
+                                AvahiLookupResultFlags  flags,
+                                void                   *data)
+{
+  AvahiClient *client = avahi_service_browser_get_client (browser);
+  EpcServiceMonitor *self = EPC_SERVICE_MONITOR (data);
+  gint error;
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: event=%d, name=`%s', type=`%s', domain=`%s', our-own=%d",
+             G_STRLOC, event, name, type, domain,
+             flags & AVAHI_LOOKUP_RESULT_OUR_OWN);
+
+  switch (event)
+    {
+      case AVAHI_BROWSER_NEW:
+        if (!self->priv->skip_our_own || !(flags & AVAHI_LOOKUP_RESULT_OUR_OWN))
+          avahi_service_resolver_new (client, interface, protocol, name, type, domain,
+                                      protocol, 0, epc_service_monitor_resolver_cb, self);
+
+        break;
+
+      case AVAHI_BROWSER_REMOVE:
+        g_signal_emit (self, signals[SIGNAL_SERVICE_REMOVED], 0, name, type);
+        break;
+
+      case AVAHI_BROWSER_CACHE_EXHAUSTED:
+        break;
+
+      case AVAHI_BROWSER_ALL_FOR_NOW:
+        g_signal_emit (self, signals[SIGNAL_SCANNING_DONE], 0, type);
+        break;
+
+      case AVAHI_BROWSER_FAILURE:
+        error = avahi_client_errno (client);
+
+        g_warning ("%s: %s (%d)", G_STRFUNC,
+                   avahi_strerror (error), error);
+
+        break;
+    }
+}
+
+static void
+epc_service_monitor_constructed (GObject *object)
+{
+  EpcServiceMonitor *self = EPC_SERVICE_MONITOR (object);
+  gchar **service_types = self->priv->types;
+  gchar **iter;
+
+  if (G_OBJECT_CLASS (epc_service_monitor_parent_class)->constructed)
+    G_OBJECT_CLASS (epc_service_monitor_parent_class)->constructed (object);
+
+  if (NULL == service_types || NULL == *service_types)
+    service_types = epc_service_type_list_supported (self->priv->application);
+
+  for (iter = service_types; *iter; ++iter)
+    {
+      AvahiServiceBrowser *browser;
+      GError *error = NULL;
+
+      browser = epc_shell_create_service_browser (AVAHI_IF_UNSPEC,
+                                                  AVAHI_PROTO_UNSPEC,
+                                                  *iter, self->priv->domain, 0,
+                                                  epc_service_monitor_browser_cb,
+                                                  self, &error);
+
+      if (error)
+        {
+          g_warning ("%s: Cannot create service type browser for '%s': %s (%d)",
+                     G_STRFUNC, *iter, error->message, error->code);
+          g_clear_error (&error);
+
+          continue;
+      }
+
+      if (G_UNLIKELY (!browser))
+        continue;
+
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: watching %s", G_STRLOC, *iter);
+
+      self->priv->browsers = g_slist_prepend (self->priv->browsers, browser);
+    }
+
+  if (service_types != self->priv->types)
+    g_strfreev (service_types);
+}
+
+static void
+epc_service_monitor_dispose (GObject *object)
+{
+  EpcServiceMonitor *self = EPC_SERVICE_MONITOR (object);
+
+  while (self->priv->browsers)
+    {
+      avahi_service_browser_free (self->priv->browsers->data);
+      self->priv->browsers = g_slist_delete_link (self->priv->browsers, self->priv->browsers);
+    }
+
+  if (self->priv->types)
+    {
+      g_strfreev (self->priv->types);
+      self->priv->types = NULL;
+    }
+
+  if (self->priv->domain)
+    {
+      g_free (self->priv->domain);
+      self->priv->domain = NULL;
+    }
+
+  if (self->priv->application)
+    {
+      g_free (self->priv->application);
+      self->priv->application = NULL;
+    }
+
+  G_OBJECT_CLASS (epc_service_monitor_parent_class)->dispose (object);
+}
+
+static void
+epc_service_monitor_class_init (EpcServiceMonitorClass *cls)
+{
+  GObjectClass *oclass = G_OBJECT_CLASS (cls);
+
+  oclass->set_property = epc_service_monitor_set_property;
+  oclass->get_property = epc_service_monitor_get_property;
+  oclass->constructed = epc_service_monitor_constructed;
+  oclass->dispose = epc_service_monitor_dispose;
+
+  g_object_class_install_property (oclass, PROP_DOMAIN,
+                                   g_param_spec_string ("domain", "Domain",
+                                                        "The DNS domain to monitor",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_APPLICATION,
+                                   g_param_spec_string ("application", "Application",
+                                                        "The application to monitor",
+                                                        NULL,
+                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                        G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_SERVICE_TYPES,
+                                   g_param_spec_boxed ("service-types", "Service Types",
+                                                       "The DNS-SD services types to watch",
+                                                      G_TYPE_STRV,
+                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+                                                      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                      G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property (oclass, PROP_SKIP_OUR_OWN,
+                                   g_param_spec_boolean ("skip-our-own", "Skip our Own",
+                                                         "Skip services of the current application",
+                                                         FALSE,
+                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+                                                         G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                                                         G_PARAM_STATIC_BLURB));
+
+  /**
+   * EpcServiceMonitor::service-found:
+   * @monitor: a #EpcServiceMonitor
+   * @name: name of the service removed
+   * @info: a description of the service found
+   *
+   * This signal is emitted when a new service was found.
+   */
+  signals[SIGNAL_SERVICE_FOUND] = g_signal_new ("service-found",
+                                                EPC_TYPE_SERVICE_MONITOR, G_SIGNAL_RUN_FIRST,
+                                                G_STRUCT_OFFSET (EpcServiceMonitorClass, service_found),
+                                                NULL, NULL, _epc_marshal_VOID__STRING_BOXED,
+                                                G_TYPE_NONE, 2, G_TYPE_STRING, EPC_TYPE_SERVICE_INFO);
+
+  /**
+   * EpcServiceMonitor::service-removed:
+   * @monitor: a #EpcServiceMonitor
+   * @name: name of the service removed
+   * @type: the service type watched
+   *
+   * This signal is emitted when a previously known service disappears.
+   */
+  signals[SIGNAL_SERVICE_REMOVED] = g_signal_new ("service-removed",
+                                                  EPC_TYPE_SERVICE_MONITOR, G_SIGNAL_RUN_FIRST,
+                                                  G_STRUCT_OFFSET (EpcServiceMonitorClass, service_removed),
+                                                  NULL, NULL, _epc_marshal_VOID__STRING_STRING,
+                                                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
+
+  /**
+   * EpcServiceMonitor::scanning-done:
+   * @monitor: a #EpcServiceMonitor
+   * @type: the service type watched
+   *
+   * This signal is emitted when active scanning as finished and most certainly
+   * no new services will be detected for some time. Can be used for instance
+   * to hide an progress indicator.
+   */
+  signals[SIGNAL_SCANNING_DONE] = g_signal_new ("scanning-done",
+                                                EPC_TYPE_SERVICE_MONITOR, G_SIGNAL_RUN_FIRST,
+                                                G_STRUCT_OFFSET (EpcServiceMonitorClass, scanning_done),
+                                                NULL, NULL, g_cclosure_marshal_VOID__STRING,
+                                                G_TYPE_NONE, 1, G_TYPE_STRING);
+
+  g_type_class_add_private (cls, sizeof (EpcServiceMonitorPrivate));
+}
+
+/**
+ * epc_service_monitor_new_for_types_strv:
+ * @domain: the DNS domain to monitor, or %NULL
+ * @types: a %NULL terminated list of service types to monitor
+ *
+ * Creates a new service monitor watching the specified @domain for the
+ * service-types listed. Passing %NULL for @domain monitors the local network.
+ * Passing an empty service list requests monitoring of all service-types
+ * supported by the library (see epc_service_type_list_supported()).
+ *
+ * See also: epc_service_monitor_new_for_types()
+ *
+ * Returns: The newly created service monitor.
+ */
+EpcServiceMonitor*
+epc_service_monitor_new_for_types_strv (const gchar  *domain,
+                                        gchar       **types)
+{
+  g_return_val_if_fail (NULL != types, NULL);
+
+  return g_object_new (EPC_TYPE_SERVICE_MONITOR,
+                       "service-types", types,
+                       "domain", domain,
+                       NULL);
+}
+
+/**
+ * epc_service_monitor_new_for_types:
+ * @domain: the DNS domain to monitor, or %NULL
+ * @...: a %NULL terminated list of service types to monitor
+ *
+ * Creates a new service monitor watching the specified @domain for the
+ * service-types listed. Passing %NULL for @domain monitors the local network.
+ * Passing an empty service list requests monitoring of all service-types
+ * supported by the library (see epc_service_type_list_supported()).
+ *
+ * See also: epc_service_monitor_new_for_types_strv()
+ *
+ * Returns: The newly created service monitor.
+ * Since: 0.3.1
+ */
+EpcServiceMonitor*
+epc_service_monitor_new_for_types (const gchar *domain,
+                                                ...)
+{
+  EpcServiceMonitor *self = NULL;
+  gchar **types = NULL;
+  va_list args;
+  gint i;
+
+  for (i = 0; i < 2; ++i)
+    {
+      const gchar *type;
+      gint tail = 0;
+
+      va_start (args, domain);
+
+      while (NULL != (type = va_arg (args, const gchar*)))
+        {
+          if (types)
+            types[tail] = g_strdup (type);
+
+          tail += 1;
+        }
+
+      va_end (args);
+
+      if (NULL == types)
+        types = g_new0 (gchar*, tail + 1);
+    }
+
+  self = g_object_new (EPC_TYPE_SERVICE_MONITOR,
+                       "service-types", types,
+                       "domain", domain, NULL);
+
+  g_strfreev (types);
+  return self;
+}
+
+/**
+ * epc_service_monitor_new:
+ * @application: name of the application to monitor, or %NULL
+ * @domain: the DNS domain to monitor, or %NULL
+ * @first_protocol: the first protocol to monitor
+ * @...: a list of additional protocols, terminated by #EPC_PROTOCOL_UNKNOWN
+ *
+ * Creates a new service monitor watching the specified @domain for a
+ * certain @application using one of the protocols listed. Passing %NULL for
+ * @application lists all libepc based applications. Passing %NULL for @domain
+ * monitors the local network. Passing an empty protocol list requests
+ * monitoring of all service-types supported by the library (see
+ * epc_service_type_list_supported()).
+ *
+ * Returns: The newly created service monitor.
+ */
+EpcServiceMonitor*
+epc_service_monitor_new (const gchar *application,
+                         const gchar *domain,
+                         EpcProtocol  first_protocol,
+                                      ...)
+{
+  EpcServiceMonitor* self = NULL;
+  gchar **types = NULL;
+  va_list args;
+  gint i;
+
+  for (i = 0; i < 2; ++i)
+    {
+      EpcProtocol protocol = first_protocol;
+      gint tail = 0;
+
+      va_start (args, first_protocol);
+
+      while (((gint) protocol) > EPC_PROTOCOL_UNKNOWN)
+        {
+          if (types)
+            types[tail] = epc_service_type_new (protocol, application);
+
+          protocol = va_arg (args, EpcProtocol);
+          tail += 1;
+        }
+
+      va_end (args);
+
+      if (NULL == types)
+        types = g_new0 (gchar*, tail + 1);
+    }
+
+  self = g_object_new (EPC_TYPE_SERVICE_MONITOR,
+                       "application", application,
+                       "service-types", types,
+                       "domain", domain,
+                       NULL);
+
+  g_strfreev (types);
+  return self;
+}
+
+/**
+ * epc_service_monitor_set_skip_our_own:
+ * @monitor: a #EpcServiceMonitor
+ * @setting: the new setting
+ *
+ * Updates the #EpcServiceMonitor::skip-our-own property.
+ */
+void
+epc_service_monitor_set_skip_our_own (EpcServiceMonitor *self,
+                                      gboolean           setting)
+{
+  g_return_if_fail (EPC_IS_SERVICE_MONITOR (self));
+  g_object_set (self, "skip-our-own", setting, NULL);
+}
+
+/**
+ * epc_service_monitor_get_skip_our_own:
+ * @monitor: a #EpcServiceMonitor
+ *
+ * Queries the current value of the #EpcServiceMonitor::skip-our-own property.
+ *
+ * Returns: The current value of the #EpcServiceMonitor::skip-our-own property
+ */
+gboolean
+epc_service_monitor_get_skip_our_own (EpcServiceMonitor *self)
+{
+  g_return_val_if_fail (EPC_IS_SERVICE_MONITOR (self), FALSE);
+  return self->priv->skip_our_own;
+}
diff --git a/glom/libglom/libepc/service-monitor.h b/glom/libglom/libepc/service-monitor.h
new file mode 100644
index 0000000..cd244b2
--- /dev/null
+++ b/glom/libglom/libepc/service-monitor.h
@@ -0,0 +1,97 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#ifndef __EPC_SERVICE_MONITOR_H__
+#define __EPC_SERVICE_MONITOR_H__
+
+#include <libglom/libepc/protocol.h>
+#include <libglom/libepc/service-info.h>
+
+G_BEGIN_DECLS
+
+#define EPC_TYPE_SERVICE_MONITOR           (epc_service_monitor_get_type())
+#define EPC_SERVICE_MONITOR(obj)           (G_TYPE_CHECK_INSTANCE_CAST(obj, EPC_TYPE_SERVICE_MONITOR, 
EpcServiceMonitor))
+#define EPC_SERVICE_MONITOR_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST(cls, EPC_TYPE_SERVICE_MONITOR, 
EpcServiceMonitorClass))
+#define EPC_IS_SERVICE_MONITOR(obj)        (G_TYPE_CHECK_INSTANCE_TYPE(obj, EPC_TYPE_SERVICE_MONITOR))
+#define EPC_IS_SERVICE_MONITOR_CLASS(obj)  (G_TYPE_CHECK_CLASS_TYPE(obj, EPC_TYPE_SERVICE_MONITOR))
+#define EPC_SERVICE_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPC_TYPE_SERVICE_MONITOR, 
EpcServiceMonitorClass))
+
+typedef struct _EpcServiceMonitor        EpcServiceMonitor;
+typedef struct _EpcServiceMonitorClass   EpcServiceMonitorClass;
+typedef struct _EpcServiceMonitorPrivate EpcServiceMonitorPrivate;
+
+/**
+ * EpcServiceMonitor:
+ *
+ * Public fields of the #EpcServiceMonitor class.
+ */
+struct _EpcServiceMonitor
+{
+  /*< private >*/
+  GObject parent_instance;
+  EpcServiceMonitorPrivate *priv;
+
+  /*< public >*/
+};
+
+/**
+ * EpcServiceMonitorClass:
+ * @service_found: virtual method of the #EpcServiceMonitor::service-found signal
+ * @service_removed: virtual method of the #EpcServiceMonitor::service-removed signal
+ * @scanning_done: virtual method of the #EpcServiceMonitor::scanning-done signal
+ *
+ * Virtual methods of the #EpcServiceMonitor class.
+ */
+struct _EpcServiceMonitorClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+  void (*service_found)   (EpcServiceMonitor  *monitor,
+                           const gchar        *name,
+                           EpcServiceInfo     *info);
+  void (*service_removed) (EpcServiceMonitor  *monitor,
+                           const gchar        *name,
+                           const gchar        *type);
+  void (*scanning_done)   (EpcServiceMonitor  *monitor,
+                           const gchar        *type);
+};
+
+GType              epc_service_monitor_get_type           (void) G_GNUC_CONST;
+
+EpcServiceMonitor* epc_service_monitor_new                (const gchar       *application,
+                                                           const gchar       *domain,
+                                                           EpcProtocol        first_protocol,
+                                                                              ...);
+EpcServiceMonitor* epc_service_monitor_new_for_types      (const gchar       *domain,
+                                                                              ...)
+                                                           G_GNUC_NULL_TERMINATED;
+EpcServiceMonitor* epc_service_monitor_new_for_types_strv (const gchar       *domain,
+                                                           gchar            **types);
+
+void               epc_service_monitor_set_skip_our_own   (EpcServiceMonitor *monitor,
+                                                           gboolean           setting);
+gboolean           epc_service_monitor_get_skip_our_own   (EpcServiceMonitor *monitor);
+
+G_END_DECLS
+
+#endif /* __EPC_SERVICE_MONITOR_H__ */ 
diff --git a/glom/libglom/libepc/service-type.c b/glom/libglom/libepc/service-type.c
new file mode 100644
index 0000000..cf0ffff
--- /dev/null
+++ b/glom/libglom/libepc/service-type.c
@@ -0,0 +1,233 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#include <libglom/libepc/service-type.h>
+#include <libglom/libepc/enums.h>
+
+#include <string.h>
+
+/**
+ * SECTION:service-type
+ * @short_description: service type details
+ * @see_also: #EpcConsumer, #EpcPublisher
+ * @include: libepc/service-type.h
+ * @stability: Unstable
+ *
+ * DNS-SD uses well-known services types to discover service providers.
+ * The following macros describe the service types uses by this library.
+ *
+ * <example id="find-publisher">
+ *  <title>Find an Easy-Publish server</title>
+ *  <programlisting>
+ *   dialog = aui_service_dialog_new ("Choose an Easy Publish Server", NULL,
+ *                                    GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ *                                    GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT,
+ *                                    NULL);
+ *   aui_service_dialog_set_browse_service_types (AUI_SERVICE_DIALOG (dialog),
+ *                                                EPC_SERVICE_TYPE_HTTPS,
+ *                                                EPC_SERVICE_TYPE_HTTP,
+ *                                                NULL);
+ *
+ *   if (GTK_RESPONSE_ACCEPT == gtk_dialog_run (GTK_DIALOG (dialog)))
+ *     {
+ *       const gint port = aui_service_dialog_get_port (AUI_SERVICE_DIALOG (dialog));
+ *       const gchar *host = aui_service_dialog_get_host_name (AUI_SERVICE_DIALOG (dialog));
+ *       const gchar *type = aui_service_dialog_get_service_type (AUI_SERVICE_DIALOG (dialog));
+ *       EpcProtocol protocol = epc_service_type_get_protocol (type);
+ *       ...
+ *     }
+ *  </programlisting>
+ * </example>
+ */
+
+static gchar*
+epc_service_type_normalize_name (const gchar *name,
+                                 gssize       length)
+{
+  GError *error = NULL;
+  gchar *normalized, *s;
+
+  g_return_val_if_fail (NULL != name, NULL);
+
+  normalized = g_convert (name, length,
+                          "ASCII//TRANSLIT", "UTF-8",
+                          NULL, NULL, &error);
+
+  if (error)
+    {
+      g_warning ("%s: %s", G_STRLOC, error->message);
+      g_error_free (error);
+    }
+
+  if (normalized)
+    {
+      for (s = normalized; *s; ++s)
+        if (!g_ascii_isalnum (*s))
+          *s = '-';
+    }
+
+  return normalized;
+}
+
+/**
+ * epc_service_type_new:
+ * @protocol: a #EpcProtocol
+ * @application: the application name, or %NULL
+ *
+ * Builds the DNS-SD service type for the given transport @protocol and
+ * application. When @application is %NULL, the application name is retrieved by
+ * calling g_get_prgname(). %NULL is returned in that case if g_get_prgname() returns %NULL.
+ *
+ * The string returned should be released when no longer needed.
+ *
+ * Returns: A newly allocated string holding the requested service-type,
+ * or %NULL when @application is %NULL and g_get_prgname() fails.
+ */
+gchar*
+epc_service_type_new (EpcProtocol  protocol,
+                      const gchar *application)
+{
+  const gchar *transport = NULL;
+  gchar *service_type = NULL;
+  gchar *normalized = NULL;
+
+  transport = epc_protocol_get_service_type (protocol);
+  g_return_val_if_fail (NULL != transport, NULL);
+
+  if (!application)
+    application = g_get_prgname ();
+
+  if (!application)
+    {
+      g_warning ("%s: Cannot derive the DNS-SD service type, as no "
+                 "application name was specified and g_get_prgname() "
+                 "returns NULL. Consider calling g_set_prgname().",
+                 G_STRFUNC);
+
+      return NULL;
+    }
+
+  normalized = epc_service_type_normalize_name (application, -1);
+
+  if (normalized)
+    {
+      service_type = g_strconcat ("_", normalized, "._sub.", transport, NULL);
+      g_free (normalized);
+    }
+
+  return service_type;
+}
+
+/**
+ * epc_service_type_get_base:
+ * @type: a DNS-SD service-type
+ *
+ * Extracts the base service-type of a DNS-SD service-type.
+ *
+ * DNS-SD service types may contain a sub service type, for instance the
+ * service-type "_anon._sub._ftp._tcp" contains the base-type "_ftp._tcp"
+ * and the sub-type "_anon". This function extracts extracts the base-type.
+ * The service type is returned unmodifed if it doesn't contain a sub-type.
+ *
+ * Returns: The base-service-type.
+ */
+const gchar*
+epc_service_type_get_base (const gchar *type)
+{
+  const gchar *base;
+
+  g_return_val_if_fail (NULL != type, NULL);
+  base = type + strlen (type);
+
+  while (base > type && '.' != *(--base));
+  while (base > type && '.' != *(--base));
+
+
+  if (base > type)
+    base += 1;
+
+  return base;
+}
+
+/**
+ * epc_service_type_get_protocol:
+ * @service_type: a DNS-SD service type
+ *
+ * Queries the #EpcProtocol associated with a DNS-SD service type.
+ * See #EPC_SERVICE_TYPE_HTTP, #EPC_SERVICE_TYPE_HTTPS.
+ *
+ * Returns: Returns the #EpcProtocol associated with @service_type,
+ * or #EPC_PROTOCOL_UNKNOWN for unrecognized or unsupported service types.
+ */
+EpcProtocol
+epc_service_type_get_protocol (const gchar *service_type)
+{
+  g_return_val_if_fail (NULL != service_type, EPC_PROTOCOL_UNKNOWN);
+
+  service_type = epc_service_type_get_base (service_type);
+  g_assert (NULL != service_type);
+
+  if (g_str_equal (service_type, EPC_SERVICE_TYPE_HTTPS))
+    return EPC_PROTOCOL_HTTPS;
+  if (g_str_equal (service_type, EPC_SERVICE_TYPE_HTTP))
+    return EPC_PROTOCOL_HTTP;
+
+  return EPC_PROTOCOL_UNKNOWN;
+}
+
+/**
+ * epc_service_type_list_supported:
+ * @application: an application name, or %NULL
+ *
+ * Lists all service types supported by the library. When @application is %NULL
+ * just the generic types, otherwise the service-subtypes for that application
+ * are returned. The returned list is terminated by %NULL and must be released
+ * by the caller with g_strfreev().
+ *
+ * See also: epc_service_type_new().
+ *
+ * Returns: The %NULL terminated list of all supported service types.
+ */
+gchar**
+epc_service_type_list_supported (const gchar *application)
+{
+  GEnumClass *protocol_class = epc_protocol_get_class ();
+  gchar **types = NULL;
+  guint vi, ti;
+
+  types = g_new0 (gchar*, protocol_class->n_values);
+
+  for (vi = 0, ti = 0; vi < protocol_class->n_values; ++vi)
+    {
+      const EpcProtocol protocol = protocol_class->values[vi].value;
+
+      if (EPC_PROTOCOL_UNKNOWN == protocol)
+        continue;
+
+      types[ti++] = application ?
+        epc_service_type_new (protocol, application) :
+        g_strdup (epc_protocol_get_service_type (protocol));
+    }
+
+  return types;
+}
+
+
diff --git a/glom/libglom/libepc/service-type.h b/glom/libglom/libepc/service-type.h
new file mode 100644
index 0000000..73b0020
--- /dev/null
+++ b/glom/libglom/libepc/service-type.h
@@ -0,0 +1,52 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+#ifndef __EPC_SERVICE_TYPE_H__
+#define __EPC_SERVICE_TYPE_H__
+
+#include <libepc/protocol.h>
+
+/**
+ * EPC_SERVICE_TYPE_HTTP:
+ *
+ * The well-known DNS-SD service type for #EpcPublisher
+ * servers providing unencrypted HTTP access.
+ */
+#define EPC_SERVICE_TYPE_HTTP   "_easy-publish-http._tcp"
+
+/**
+ * EPC_SERVICE_TYPE_HTTPS:
+ *
+ * The well-known DNS-SD service type for #EpcPublisher
+ * servers providing encrypted HTTPS access.
+ */
+#define EPC_SERVICE_TYPE_HTTPS  "_easy-publish-https._tcp"
+
+G_BEGIN_DECLS
+
+gchar*                epc_service_type_new            (EpcProtocol  protocol,
+                                                       const gchar *application);
+const gchar* epc_service_type_get_base       (const gchar *type) G_GNUC_PURE;
+EpcProtocol           epc_service_type_get_protocol   (const gchar *service_type) G_GNUC_PURE;
+gchar**               epc_service_type_list_supported (const gchar *application);
+
+G_END_DECLS
+
+#endif /* __EPC_SERVICE_TYPE_H__ */
diff --git a/glom/libglom/libepc/shell.c b/glom/libglom/libepc/shell.c
new file mode 100644
index 0000000..d8dca49
--- /dev/null
+++ b/glom/libglom/libepc/shell.c
@@ -0,0 +1,616 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libglom/libepc/shell.h>
+
+#include <avahi-common/error.h>
+#include <avahi-glib/glib-malloc.h>
+#include <avahi-glib/glib-watch.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib-object.h>
+#include <gmodule.h>
+
+#include <gnutls/gnutls.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * SECTION:shell
+ * @short_description: library management functions
+ * @include: libepc/shell.h
+ * @stability: Private
+ *
+ * The methods of the #EpcShell singleton are used to manage library resources.
+ */
+
+typedef struct _EpcShellWatch EpcShellWatch;
+
+struct _EpcShellWatch
+{
+  guint          id;
+  GCallback      callback;
+  gpointer       user_data;
+  GDestroyNotify destroy_data;
+};
+
+static AvahiGLibPoll *epc_shell_avahi_poll = NULL;
+static AvahiClient   *epc_shell_avahi_client = NULL;
+static gboolean       epc_shell_restart_avahi_client_allowed = TRUE;
+static GArray        *epc_shell_watches = NULL;
+
+static void (*epc_shell_threads_enter)(void) = NULL;
+static void (*epc_shell_threads_leave)(void) = NULL;
+
+static const EpcShellProgressHooks  *epc_shell_progress_hooks = NULL;
+static gpointer                      epc_shell_progress_user_data = NULL;
+static GDestroyNotify                epc_shell_progress_destroy_data = NULL;
+
+/**
+ * epc_shell_get_debug_level:
+ *
+ * Query the library's debug level. The debug level can be modified by setting
+ * the external <varname>EPC_DEBUG</varname> environment variable. In most
+ * cases the #EPC_DEBUG_LEVEL macro should be used, instead of calling this
+ * checking the return value of this function.
+ *
+ * Returns: The library's current debugging level.
+ */
+guint
+epc_shell_get_debug_level (void)
+{
+  static gint level = -1;
+
+  if (G_UNLIKELY (-1 == level))
+    {
+      const gchar *text = g_getenv ("EPC_DEBUG");
+      level = text ? MAX (0, atoi (text)) : 0;
+    }
+
+  return level;
+}
+
+static void
+epc_shell_exit (void)
+{
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: releasing libepc resources", G_STRLOC);
+
+  if (NULL != epc_shell_avahi_client)
+    {
+      avahi_client_free (epc_shell_avahi_client);
+      epc_shell_avahi_client = NULL;
+    }
+
+  if (epc_shell_avahi_poll)
+    {
+      avahi_glib_poll_free (epc_shell_avahi_poll);
+      epc_shell_avahi_poll = NULL;
+    }
+
+  epc_shell_threads_enter = NULL;
+  epc_shell_threads_leave = NULL;
+}
+
+static void
+epc_shell_init (void)
+{
+  if (G_UNLIKELY (NULL == epc_shell_avahi_poll))
+    {
+      gnutls_global_init ();
+      avahi_set_allocator (avahi_glib_allocator ());
+      g_atexit (epc_shell_exit);
+
+      epc_shell_avahi_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT);
+      g_assert (NULL != epc_shell_avahi_poll);
+
+      bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+      bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+    }
+}
+
+static guint
+epc_shell_watches_length (void)
+{
+  if (epc_shell_watches)
+    return epc_shell_watches->len;
+
+  return 0;
+}
+
+static EpcShellWatch*
+epc_shell_watches_get (guint index)
+{
+  g_return_val_if_fail (index < epc_shell_watches_length (), NULL);
+  return &g_array_index (epc_shell_watches, EpcShellWatch, index);
+}
+
+static EpcShellWatch*
+epc_shell_watches_last (void)
+{
+  if (epc_shell_watches && epc_shell_watches->len)
+    return epc_shell_watches_get (epc_shell_watches->len - 1);
+
+  return NULL;
+}
+
+static guint
+epc_shell_watch_add (GCallback      callback,
+                     gpointer       user_data,
+                     GDestroyNotify destroy_data)
+{
+  EpcShellWatch *last, *self;
+
+  if (NULL == epc_shell_watches)
+    epc_shell_watches = g_array_sized_new (TRUE, TRUE, sizeof (EpcShellWatch), 4);
+
+  g_return_val_if_fail (NULL != epc_shell_watches, 0);
+
+  last = epc_shell_watches_last ();
+
+  g_array_set_size (epc_shell_watches, epc_shell_watches->len + 1);
+
+  self = epc_shell_watches_last ();
+
+  self->id = (last ? last->id + 1 : 1);
+  self->callback = callback;
+  self->user_data = user_data;
+  self->destroy_data = destroy_data;
+
+  return self->id;
+}
+
+/**
+ * epc_shell_watch_remove:
+ * @id: identifier of an watching callback
+ *
+ * Removes the watching callback identified by @id.
+ *
+ * See also: epc_shell_watch_avahi_client()
+ */
+void
+epc_shell_watch_remove (guint id)
+{
+  guint idx, len;
+
+  g_return_if_fail (id > 0);
+
+  if (!epc_shell_watches)
+    return;
+
+  len = epc_shell_watches_length ();
+
+  for (idx = MIN (id, len) - 1; idx < len; ++idx)
+    if (epc_shell_watches_get (idx)->id == id)
+      break;
+
+  if (idx < len)
+    g_array_remove_index (epc_shell_watches, idx);
+}
+
+static void
+epc_shell_avahi_client_cb (AvahiClient      *client,
+                           AvahiClientState  state,
+                           gpointer          user_data G_GNUC_UNUSED)
+{
+  guint i;
+
+  if (epc_shell_avahi_client)
+    g_assert (client == epc_shell_avahi_client);
+  else
+    epc_shell_avahi_client = client;
+
+  if (epc_shell_watches)
+    {
+      epc_shell_restart_avahi_client_allowed = FALSE;
+
+      for (i = 0; i < epc_shell_watches->len; ++i)
+        {
+          EpcShellWatch *watch = &g_array_index (epc_shell_watches, EpcShellWatch, i);
+          ((AvahiClientCallback) watch->callback) (client, state, watch->user_data);
+        }
+
+      epc_shell_restart_avahi_client_allowed = TRUE;
+    }
+
+  if (AVAHI_CLIENT_FAILURE == state)
+    {
+      gint error_code = avahi_client_errno (client);
+
+      g_warning ("%s: Avahi client failed: %s.",
+                 G_STRFUNC, avahi_strerror (error_code));
+
+      /* Restart the client __after__ notifying all watchers to give them
+       * a chance to react. Restarting the client before notification would
+       * risk that watchers deal with stale AvahiClient references. */
+      epc_shell_restart_avahi_client (G_STRLOC);
+    }
+}
+
+static AvahiClient*
+epc_shell_get_avahi_client (GError **error)
+{
+  g_return_val_if_fail (NULL != epc_shell_avahi_client ||
+                        NULL != error, NULL);
+
+  if (G_UNLIKELY (NULL == epc_shell_avahi_client))
+    {
+      gint error_code = AVAHI_OK;
+
+      epc_shell_init ();
+
+      epc_shell_avahi_client =
+        avahi_client_new (avahi_glib_poll_get (epc_shell_avahi_poll),
+                          AVAHI_CLIENT_NO_FAIL, epc_shell_avahi_client_cb,
+                          NULL, &error_code);
+
+      if (NULL == epc_shell_avahi_client)
+        g_set_error (error, EPC_AVAHI_ERROR, error_code,
+                     _("Cannot create Avahi client: %s"),
+                     avahi_strerror (error_code));
+    }
+
+  return epc_shell_avahi_client;
+}
+
+/**
+ * epc_shell_watch_avahi_client_state:
+ * @callback: a callback function
+ * @user_data: data to pass to @callback
+ * @destroy_data: a function for freeing @user_data when removing the watch
+ * @error: return location for a #GError, or %NULL
+ *
+ * Registers a function to watch state changes of the library's #AvahiClient.
+ * On success the identifier of the newly created watch is returned. Pass it
+ * to epc_shell_watch_remove() to remove the watch. On failure it returns 0 and
+ * sets @error. The error domain is #EPC_AVAHI_ERROR. Possible error codes are
+ * those of the <citetitle>Avahi</citetitle> library.
+ *
+ * <note><para>
+ *  Usually there is no need in handling the #AVAHI_CLIENT_FAILURE state
+ *  for @callback, as the callback dispatcher takes care already. This
+ *  state is dispatched mostly for notification purposes.
+ * </para></note>
+ *
+ * Returns: The identifier of the newly created watch, or 0 on error.
+ */
+guint
+epc_shell_watch_avahi_client_state (AvahiClientCallback callback,
+                                    gpointer            user_data,
+                                    GDestroyNotify      destroy_data,
+                                    GError            **error)
+{
+  AvahiClient *client = epc_shell_get_avahi_client (error);
+  guint id = 0;
+
+  g_return_val_if_fail (NULL != callback, 0);
+
+  if (NULL != client)
+    {
+      id = epc_shell_watch_add (G_CALLBACK (callback), user_data, destroy_data);
+      callback (client, avahi_client_get_state (client), user_data);
+    }
+
+  return id;
+}
+
+/**
+ * epc_shell_create_avahi_entry_group:
+ * @callback: a callback function
+ * @user_data: data to pass to @callback
+ *
+ * Creates a new #AvahiEntryGroup for the library's shared #AvahiClient.
+ * On success the newly created #AvahiEntryGroup is returned,
+ * and %NULL on failure.
+ *
+ * Returns: The newly created #AvahiEntryGroup, or %NULL on error.
+ */
+AvahiEntryGroup*
+epc_shell_create_avahi_entry_group (AvahiEntryGroupCallback callback,
+                                    gpointer                user_data)
+{
+  AvahiClient *client = epc_shell_get_avahi_client (NULL);
+  AvahiEntryGroup *group = NULL;
+
+  if (NULL != client)
+    group = avahi_entry_group_new (client, callback, user_data);
+
+  return group;
+}
+
+/**
+ * epc_shell_create_service_browser:
+ * @interface: the index of the network interface to watch
+ * @protocol: the protocol of the services to watch
+ * @type: the DNS-SD service type to watch
+ * @domain: the DNS domain to watch, or %NULL
+ * @flags: flags for creating the service browser
+ * @callback: a callback function
+ * @user_data: data to pass to @callback
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new #AvahiServiceBrowser for the library's shared #AvahiClient.
+ * On success the newly created #AvahiEntryGroup is returned. On failure
+ * %NULL is returned and @error is set. The error domain is #EPC_AVAHI_ERROR.
+ * Possible error codes are those of the <citetitle>Avahi</citetitle> library.
+ *
+ * Returns: The newly created #AvahiServiceBrowser, or %NULL on error.
+ */
+AvahiServiceBrowser*
+epc_shell_create_service_browser (AvahiIfIndex                interface,
+                                  AvahiProtocol               protocol,
+                                  const gchar                *type,
+                                  const gchar                *domain,
+                                  AvahiLookupFlags            flags,
+                                  AvahiServiceBrowserCallback callback,
+                                  gpointer                    user_data,
+                                  GError                    **error)
+{
+  AvahiClient *client = epc_shell_get_avahi_client (error);
+  AvahiServiceBrowser *browser = NULL;
+
+  if (error && *error)
+    return NULL;
+
+  if (NULL != client)
+    browser = avahi_service_browser_new (client, interface, protocol, type,
+                                         domain, flags, callback, user_data);
+
+  if (G_UNLIKELY (!browser))
+    g_set_error (error, EPC_AVAHI_ERROR, AVAHI_ERR_FAILURE,
+                 _("Cannot create Avahi service browser."));
+
+  return browser;
+}
+
+/**
+ * epc_shell_restart_avahi_client:
+ * @strloc: code location of the callee (#G_STRLOC)
+ *
+ * Requests restart of the builtin Avahi client. Use this function to react on
+ * critical failures (like #AVAHI_ENTRY_GROUP_FAILURE) reported to your Avahi
+ * callbacks. The @strloc argument is used to prevent endless restart cycles.
+ *
+ * <note><para>
+ *  Do not call this function to react on #AVAHI_CLIENT_FAILURE in a
+ *  #AvahiClientCallback. The builtin callback dispatcher deals with
+ *  that situation.
+ * </para></note>
+ */
+void
+epc_shell_restart_avahi_client (const gchar *strloc G_GNUC_UNUSED)
+{
+  GError *error = NULL;
+
+  g_return_if_fail (epc_shell_restart_avahi_client_allowed);
+  g_warning ("%s: Restarting Avahi client.", G_STRFUNC);
+
+  /* TODO: Use strloc to prevent endless restart cycles. */
+
+  if (epc_shell_avahi_client)
+    {
+      avahi_client_free (epc_shell_avahi_client);
+      epc_shell_avahi_client = NULL;
+    }
+
+  if (!epc_shell_get_avahi_client (&error))
+    {
+      g_warning ("%s: %s", G_STRFUNC, error->message);
+      g_clear_error (&error);
+    }
+}
+
+/**
+ * epc_shell_get_host_name:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Retrieves the official host name of this machine. On failure the function
+ * returns %NULL and sets @error. The error domain is #EPC_AVAHI_ERROR.
+ * Possible error codes are those of the <citetitle>Avahi</citetitle> library.
+ *
+ * Returns: The official host name, or %NULL on error.
+ */
+const gchar*
+epc_shell_get_host_name (GError **error)
+{
+  AvahiClient *client = epc_shell_get_avahi_client (error);
+
+  if (client)
+    return avahi_client_get_host_name (client);
+
+  return NULL;
+}
+
+static void
+epc_shell_progress_begin_default (const gchar *title,
+                                  gpointer     user_data)
+{
+  gchar **context = user_data;
+  g_assert (NULL != context);
+
+  if (title)
+    *context = g_strdup (title);
+}
+
+static void
+epc_shell_progress_update_default (gdouble      progress,
+                                   const gchar *message,
+                                   gpointer     user_data)
+{
+  const gchar **context = user_data;
+  const gchar *title;
+
+  g_assert (NULL != context);
+  title = *context;
+
+  if (NULL == title)
+  {
+    // Translators: This is just a generic default message for a progress bar.
+    title = _("Operation in Progress");
+  }
+
+  if (NULL == message)
+    message = _("No details known");
+
+  if (progress >= 0 && progress <= 1)
+    g_message ("%s: %s (%.1f %%)", title, message, progress * 100);
+  else
+    g_message ("%s: %s", title, message);
+}
+
+static void
+epc_shell_progress_end_default (gpointer     user_data)
+{
+  gchar **context = user_data;
+  g_assert (NULL != context);
+  g_free (*context);
+}
+
+
+/**
+ * epc_shell_set_progress_hooks:
+ * @hooks: the function table to install, or %NULL
+ * @user_data: custom data which shall be passed to the start hook
+ * @destroy_data: function to call on @user_data when the hooks are replaced
+ *
+ * Installs functions which are called during processing, such as generating 
+ * server keys. This allows the application to indicate progress and generally 
+ * keep its UI responsive. If no progress callbacks are provided,
+ * or when %NULL is passed for @hooks, progress messages are written to the
+ * console.
+ *
+ * See also: #EpcEntropyProgress
+ */
+void
+epc_shell_set_progress_hooks (const EpcShellProgressHooks *hooks,
+                              gpointer                     user_data,
+                              GDestroyNotify               destroy_data)
+{
+  if (epc_shell_progress_destroy_data)
+    epc_shell_progress_destroy_data (epc_shell_progress_user_data);
+
+  if (NULL == hooks)
+    {
+      static EpcShellProgressHooks default_hooks =
+      {
+        begin: epc_shell_progress_begin_default,
+        update: epc_shell_progress_update_default,
+        end: epc_shell_progress_end_default,
+      };
+
+      g_return_if_fail (NULL == user_data);
+      g_return_if_fail (NULL == destroy_data);
+
+      hooks = &default_hooks;
+      user_data = g_new0 (gchar*, 1);
+      destroy_data = g_free;
+    }
+
+  epc_shell_progress_hooks = hooks;
+  epc_shell_progress_user_data = user_data;
+  epc_shell_progress_destroy_data = destroy_data;
+}
+
+/**
+ * epc_shell_progress_begin:
+ * @title: the title of the lengthy operation
+ * @message: description of the lengthy operation
+ *
+ * Call this function before starting a lengthy operation to allow the
+ * application tp provide some visual feedback during the operation,
+ * and to generally keep its UI responsive.
+ *
+ * This function calls your #EpcShellProgressHooks::begin hook with @title
+ * as argument and #EpcShellProgressHooks::update with @message.
+ *
+ * See also: epc_shell_set_progress_hooks(), #epc_progress_window_install,
+ * epc_shell_progress_update(), #epc_shell_progress_end
+ */
+void
+epc_shell_progress_begin (const gchar *title,
+                          const gchar *message)
+{
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: %s", G_STRFUNC, title);
+
+  if (!epc_shell_progress_hooks)
+    epc_shell_set_progress_hooks (NULL, NULL, NULL);
+  if (epc_shell_progress_hooks->begin)
+    epc_shell_progress_hooks->begin (title, epc_shell_progress_user_data);
+
+  epc_shell_progress_update (-1, message);
+}
+
+/**
+ * epc_shell_progress_update:
+ * @percentage: current progress of the operation, or -1
+ * @message: a description of the current progress
+ *
+ * Called this function to inform about progress of a lengthy operation.
+ * The progress is expressed as @percentage in the range [0..1], or -1 if the
+ * progress cannot be estimated.
+ *
+ * See also: epc_shell_set_progress_hooks(), #epc_progress_window_install,
+ * epc_shell_progress_begin, epc_shell_progress_end()
+ */
+void
+epc_shell_progress_update (gdouble      percentage,
+                           const gchar *message)
+{
+  g_assert (NULL != epc_shell_progress_hooks);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: %s", G_STRFUNC, message);
+
+  if (epc_shell_progress_hooks->update)
+    epc_shell_progress_hooks->update (percentage, message, epc_shell_progress_user_data);
+}
+
+/**
+ * epc_shell_progress_end:
+ *
+ * Call this function when your lengthy operation has finished.
+ *
+ * See also: epc_shell_set_progress_hooks(), #epc_progress_window_install,
+ * epc_shell_progress_begin(), #epc_shell_progress_update
+ */
+void
+epc_shell_progress_end (void)
+{
+  g_assert (NULL != epc_shell_progress_hooks);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s", G_STRFUNC);
+
+  if (epc_shell_progress_hooks->end)
+    epc_shell_progress_hooks->end (epc_shell_progress_user_data);
+}
+
+GQuark
+epc_avahi_error_quark (void)
+{
+  return g_quark_from_static_string ("epc-avahi-error-quark");
+}
diff --git a/glom/libglom/libepc/shell.h b/glom/libglom/libepc/shell.h
new file mode 100644
index 0000000..5a2bbfa
--- /dev/null
+++ b/glom/libglom/libepc/shell.h
@@ -0,0 +1,119 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+#ifndef __EPC_SHELL_H__
+#define __EPC_SHELL_H__
+
+#include <avahi-client/client.h>
+#include <avahi-client/lookup.h>
+#include <avahi-client/publish.h>
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/**
+ * EPC_DEBUG_LEVEL:
+ * @level: the minimum level to check for
+ *
+ * Checks if the library's debug level, which can be modified by setting the
+ * <varname>EPC_DEBUG</varname> environment variable, is above the value
+ * sepecified by @level.
+ *
+ * Returns: %TRUE if the library's debug level is above the threshold specified
+ * by @level, and %FALSE otherwise.
+ */
+#define EPC_DEBUG_LEVEL(level) G_UNLIKELY(epc_shell_get_debug_level () >= (level))
+
+/**
+ * EPC_AVAHI_ERROR:
+ *
+ * Error domain for <citetitle>Avahi</citetitle> operations. Errors in this
+ * domain will be <citetitle>Avahi</citetitle> error codes. See GError
+ * for information on error domains.
+ */
+#define EPC_AVAHI_ERROR (epc_avahi_error_quark ())
+
+typedef struct _EpcShellProgressHooks EpcShellProgressHooks;
+
+/**
+ * EpcShellProgressHooks:
+ * @begin: the function called by #epc_shell_progress_begin
+ * @update: the function called by #epc_shell_progress_update
+ * @end: the function called by #epc_shell_progress_end
+ *
+ * This table is used by #epc_shell_set_progress_hooks to install functions
+ * allowing the library to provide feedback during processing.
+ *
+ * See also: #epc_progress_window_install
+ */
+struct _EpcShellProgressHooks
+{
+  /*< public >*/
+  void (*begin)  (const gchar *title,
+                  gpointer     user_data);
+  void (*update) (gdouble      percentage,
+                  const gchar *message,
+                  gpointer     user_data);
+  void (*end)    (gpointer     user_data);
+
+  /*< private >*/
+  gpointer reserved1;
+  gpointer reserved2;
+  gpointer reserved3;
+  gpointer reserved4;
+};
+
+guint                 epc_shell_get_debug_level          (void) G_GNUC_CONST;
+
+void                  epc_shell_watch_remove             (guint id);
+
+guint                 epc_shell_watch_avahi_client_state (AvahiClientCallback          callback,
+                                                          gpointer                     user_data,
+                                                          GDestroyNotify               destroy_data,
+                                                          GError                     **error);
+AvahiEntryGroup*      epc_shell_create_avahi_entry_group (AvahiEntryGroupCallback      callback,
+                                                          gpointer                     user_data);
+AvahiServiceBrowser*  epc_shell_create_service_browser   (AvahiIfIndex                 interface,
+                                                          AvahiProtocol                protocol,
+                                                          const gchar                 *type,
+                                                          const gchar                 *domain,
+                                                          AvahiLookupFlags             flags,
+                                                          AvahiServiceBrowserCallback  callback,
+                                                          gpointer                     user_data,
+                                                          GError                     **error);
+void                  epc_shell_restart_avahi_client     (const gchar                 *strloc);
+
+const gchar* epc_shell_get_host_name            (GError                     **error);
+
+void                  epc_shell_set_progress_hooks       (const EpcShellProgressHooks *hooks,
+                                                          gpointer                     user_data,
+                                                          GDestroyNotify               destroy_data);
+void                  epc_shell_progress_begin           (const gchar                 *title,
+                                                          const gchar                 *message);
+void                  epc_shell_progress_update          (gdouble                      percentage,
+                                                          const gchar                 *message);
+void                  epc_shell_progress_end             (void);
+
+GQuark                epc_avahi_error_quark              (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __EPC_SHELL_H__ */
diff --git a/glom/libglom/libepc/tls.c b/glom/libglom/libepc/tls.c
new file mode 100644
index 0000000..97cd788
--- /dev/null
+++ b/glom/libglom/libepc/tls.c
@@ -0,0 +1,665 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors:
+ *      Mathias Hasselmann
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libglom/libepc/tls.h>
+#include <libglom/libepc/shell.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <uuid/uuid.h>
+
+/**
+ * SECTION:tls
+ * @short_description: TLS support
+ * @include: libepc/shell.h
+ * @stability: Private
+ *
+ * Functions for handling TLS (X.509) certificates and keys.
+ */
+
+#define epc_tls_check(result) G_STMT_START{     \
+  if (GNUTLS_E_SUCCESS != (result))             \
+    goto out;                                   \
+}G_STMT_END
+
+typedef struct _EcpTlsKeyContext EcpTlsKeyContext;
+
+struct _EcpTlsKeyContext
+{
+  gnutls_x509_privkey_t key;
+  GMainLoop *loop;
+  gint rc;
+};
+
+GQuark
+epc_tls_error_quark (void)
+{
+  return g_quark_from_static_string ("epc-tls-error-quark");
+}
+
+static gchar*
+epc_tls_get_filename (const gchar *hostname,
+                      const gchar *extension)
+{
+  const gchar *progname = g_get_prgname ();
+  gchar *filename = NULL;
+  gchar *basename = NULL;
+
+  g_return_val_if_fail (NULL != hostname, NULL);
+  g_return_val_if_fail (NULL != extension, NULL);
+
+  if (NULL == progname)
+    {
+      g_warning ("%s: No program name set. "
+                 "Consider calling g_set_prgname().",
+                 G_STRLOC);
+
+      progname = "";
+    }
+
+  basename = g_strconcat (hostname, extension, NULL);
+  filename = g_build_filename (g_get_user_config_dir (),
+                               "libepc", progname,
+                               basename, NULL);
+
+  g_free (basename);
+  return filename;
+}
+
+/**
+ * epc_tls_get_private_key_filename:
+ * @hostname: the server's host name
+ *
+ * Queries the preferred location for storing private X.509 keys for the server
+ * identified by @hostname. This file is located in the current user's XDG
+ * configuration folder (g_get_user_config_dir()). The filename also contains
+ * program's name when specified by g_set_prgname().
+ *
+ * Returns: The preferred private key location for @hostname.
+ */
+gchar*
+epc_tls_get_private_key_filename (const gchar *hostname)
+{
+  return epc_tls_get_filename (hostname, ".key");
+}
+
+/**
+ * epc_tls_get_certificate_filename:
+ * @hostname: the server's host name
+ *
+ * Queries the preferred location for storing X.509 certificates for the server
+ * identified by @hostname. This file is located in the current user's XDG
+ * configuration folder (g_get_user_config_dir()). The filename also contains
+ * program's name when specified by g_set_prgname().
+ *
+ * Returns: The preferred certificate location for @hostname.
+ */
+gchar*
+epc_tls_get_certificate_filename (const gchar *hostname)
+{
+  return epc_tls_get_filename (hostname, ".crt");
+}
+
+static gpointer
+epc_tls_private_key_thread (gpointer data)
+{
+  EcpTlsKeyContext *context = data;
+
+  context->rc = gnutls_x509_privkey_generate (context->key, GNUTLS_PK_RSA, 1024, 0);
+  g_main_loop_quit (context->loop);
+
+  return NULL;
+}
+
+/**
+ * epc_tls_private_key_new:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a self private X.509 key. Generating secure keys needs quite
+ * some time. Call epc_tls_set_private_key_hooks() to install hooks providing
+ * some feedback to your users. Key generation takes place in a separate
+ * background thread, whilst the calling thread waits in a GMainLoop. So
+ * for instance the GTK+ widget system remains responsible during that
+ * phase.
+ *
+ * If the call was successful, the newly created key is returned. This
+ * certificate can be used with functions of the <citetitle>GNU TLS</citetitle>
+ * library. If the call was not successful, it returns %NULL and sets @error.
+ * The error domain is #EPC_TLS_ERROR. Error codes are taken from the
+ * <citetitle>GNU TLS</citetitle> library.
+ *
+ * See also: #EpcEntropyProgress
+ *
+ * Returns: The newly created private key object, or %NULL.
+ */
+gnutls_x509_privkey_t
+epc_tls_private_key_new (GError **error)
+{
+  EcpTlsKeyContext context = { NULL, NULL, GNUTLS_E_SUCCESS };
+
+  epc_shell_progress_begin (_("Generating Server Key"),
+                            _("This may take some time. Type on the "
+                              "keyboard, move your mouse, or browse "
+                              "the web to generate some entropy."));
+
+  context.rc = gnutls_x509_privkey_init (&context.key);
+  epc_tls_check (context.rc);
+
+  context.loop = g_main_loop_new (NULL, FALSE);
+  g_thread_new (NULL, epc_tls_private_key_thread, &context);
+  g_main_loop_run (context.loop);
+  g_main_loop_unref (context.loop);
+
+  epc_tls_check (context.rc);
+
+out:
+  epc_shell_progress_end ();
+
+  if (GNUTLS_E_SUCCESS != context.rc)
+    {
+      g_set_error (error, EPC_TLS_ERROR, context.rc,
+                   _("Cannot create private server key: %s"),
+                   gnutls_strerror (context.rc));
+
+      if (context.key)
+        gnutls_x509_privkey_deinit (context.key);
+
+      context.key = NULL;
+    }
+
+  return context.key;
+}
+
+/**
+ * epc_tls_private_key_load:
+ * @filename: name of a file to read the key from, in the GLib file name encoding
+ * @error: return location for a #GError, or %NULL
+ *
+ * Reads a PEM encoded private X.509 key.
+ *
+ * If the call was successful, the newly created private key object is
+ * returned. This key can be used with functions of the <citetitle>GNU
+ * TLS</citetitle> library. If the call was not successful, it returns %NULL
+ * and sets @error. The error domain is #EPC_TLS_ERROR. Error codes are taken
+ * from the <citetitle>GNU TLS</citetitle> library.
+ *
+ * Returns: The newly created key object, or %NULL.
+ */
+gnutls_x509_privkey_t
+epc_tls_private_key_load (const gchar *filename,
+                          GError     **error)
+{
+  gnutls_x509_privkey_t key = NULL;
+  gint rc = GNUTLS_E_SUCCESS;
+  gchar *contents = NULL;
+  gnutls_datum_t buffer;
+
+  g_return_val_if_fail (NULL != filename, NULL);
+
+  if (g_file_get_contents (filename, &contents, (gsize*) &buffer.size, error))
+    {
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: Loading private key `%s'", G_STRLOC, filename);
+
+      buffer.data = (guchar*) contents;
+
+      epc_tls_check (rc = gnutls_x509_privkey_init (&key));
+      epc_tls_check (rc = gnutls_x509_privkey_import (key, &buffer, GNUTLS_X509_FMT_PEM));
+    }
+
+out:
+  if (GNUTLS_E_SUCCESS != rc)
+    {
+      g_set_error (error, EPC_TLS_ERROR, rc,
+                   _("Cannot import private server key '%s': %s"),
+                   filename, gnutls_strerror (rc));
+
+      if (key)
+        gnutls_x509_privkey_deinit (key);
+
+      key = NULL;
+    }
+
+  g_free (contents);
+
+  return key;
+}
+
+/**
+ * epc_tls_private_key_save:
+ * @key: a private X.509 key
+ * @filename: name of a file to write the private key to, in the GLib file name encoding
+ * @error: return location for a #GError, or %NULL
+ *
+ * Writes a PEM encoded private X.509 key.
+ *
+ * If the call was successful, it returns %TRUE. If the call was not
+ * successful, it returns %FALSE and sets @error. The error domain is
+ * #EPC_TLS_ERROR. Error codes are taken from the <citetitle>GNU
+ * TLS</citetitle> library.
+ *
+ * Returns: %TRUE on successful, %FALSE if an error occured
+ */
+gboolean
+epc_tls_private_key_save (gnutls_x509_privkey_t  key,
+                          const gchar           *filename,
+                          GError               **error)
+{
+  gint rc = GNUTLS_E_SUCCESS;
+  gchar *display_name = NULL;
+  guchar *contents = NULL;
+  gchar *dirname = NULL;
+  gsize length = 0;
+  gint fd = -1;
+
+  g_return_val_if_fail (NULL != key, FALSE);
+  g_return_val_if_fail (NULL != filename, FALSE);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: Writing server key `%s'", G_STRLOC, filename);
+
+  rc = gnutls_x509_privkey_export (key, GNUTLS_X509_FMT_PEM, NULL, &length);
+  g_assert (GNUTLS_E_SHORT_MEMORY_BUFFER == rc);
+
+  contents = g_malloc (length);
+
+  if (GNUTLS_E_SUCCESS != (rc =
+      gnutls_x509_privkey_export (key, GNUTLS_X509_FMT_PEM, contents, &length)))
+    {
+      g_set_error (error, EPC_TLS_ERROR, rc,
+                   _("Cannot export private key to PEM format: %s"),
+                   gnutls_strerror (rc));
+
+      goto out;
+    }
+
+  if (g_mkdir_with_parents (dirname = g_path_get_dirname (filename), 0700))
+    {
+      display_name = g_filename_display_name (dirname);
+      rc = GNUTLS_E_FILE_ERROR;
+
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   g_file_error_from_errno (errno),
+                   _("Failed to create private key folder '%s': %s"),
+                   display_name, g_strerror (errno));
+
+      goto out;
+    }
+
+  fd = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+
+  if (-1 == fd)
+    {
+      display_name = g_filename_display_name (filename);
+      rc = GNUTLS_E_FILE_ERROR;
+
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   g_file_error_from_errno (errno),
+                   _("Failed to create private key file '%s': %s"),
+                   display_name, g_strerror (errno));
+
+      goto out;
+    }
+
+  if (write (fd, contents, length) < (gssize)length)
+    {
+      display_name = g_filename_display_name (filename);
+      rc = GNUTLS_E_FILE_ERROR;
+
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   g_file_error_from_errno (errno),
+                   _("Failed to write private key file '%s': %s"),
+                   display_name, g_strerror (errno));
+
+      goto out;
+    }
+
+out:
+  if (-1 != fd)
+    close (fd);
+
+  if (GNUTLS_E_SUCCESS != rc)
+    g_unlink (filename);
+
+  g_free (display_name);
+  g_free (contents);
+  g_free (dirname);
+
+  return (GNUTLS_E_SUCCESS == rc);
+}
+
+/**
+ * epc_tls_certificate_new:
+ * @hostname: the name of the host that will use this certificate
+ * @validity: the number of days the certificate will remain valid
+ * @key: the private key for signing the certificate
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a self signed X.509 certificate. The certificate will mention
+ * @hostname as common name and as DNS host name, and it will be valid
+ * for @validity days.
+ *
+ * If the call was successful, the newly created certificate is returned. This
+ * certificate can be used with functions of the <citetitle>GNU TLS</citetitle>
+ * library. If the call was not successful, it returns %NULL and sets @error.
+ * The error domain is #EPC_TLS_ERROR. Error codes are taken from the
+ * <citetitle>GNU TLS</citetitle> library.
+ *
+ * Returns: The newly created certificate object, or %NULL.
+ */
+gnutls_x509_crt_t
+epc_tls_certificate_new (const gchar            *hostname,
+                         guint                   validity,
+                         gnutls_x509_privkey_t   key,
+                         GError                **error)
+{
+  gint rc = GNUTLS_E_SUCCESS;
+  gnutls_x509_crt_t crt = NULL;
+  time_t now = time (NULL);
+  uuid_t serial;
+
+  g_return_val_if_fail (NULL != key, NULL);
+  g_return_val_if_fail (NULL != hostname, NULL);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: Generating self signed server certificate for `%s'", G_STRLOC, hostname);
+
+  uuid_generate_time (serial);
+
+  epc_tls_check (rc = gnutls_x509_crt_init (&crt));
+  epc_tls_check (rc = gnutls_x509_crt_set_version (crt, 1));
+  epc_tls_check (rc = gnutls_x509_crt_set_key (crt, key));
+  epc_tls_check (rc = gnutls_x509_crt_set_serial (crt, serial, sizeof serial));
+  epc_tls_check (rc = gnutls_x509_crt_set_activation_time (crt, now));
+  epc_tls_check (rc = gnutls_x509_crt_set_expiration_time (crt, now + validity));
+  epc_tls_check (rc = gnutls_x509_crt_set_subject_alternative_name (crt, GNUTLS_SAN_DNSNAME, hostname));
+  epc_tls_check (rc = gnutls_x509_crt_set_dn_by_oid (crt, GNUTLS_OID_X520_COMMON_NAME, 0, hostname, strlen 
(hostname)));
+  epc_tls_check (rc = gnutls_x509_crt_sign (crt, crt, key));
+
+out:
+  if (GNUTLS_E_SUCCESS != rc)
+    {
+      g_set_error (error, EPC_TLS_ERROR, rc,
+                   _("Cannot create self signed server key for '%s': %s"),
+                   hostname, gnutls_strerror (rc));
+
+      if (crt)
+        gnutls_x509_crt_deinit (crt);
+
+      crt = NULL;
+    }
+
+  return crt;
+}
+
+/**
+ * epc_tls_certificate_load:
+ * @filename: name of a file to read the certificate from, in the GLib file name encoding
+ * @error: return location for a #GError, or %NULL
+ *
+ * Reads a PEM encoded X.509 certificate.
+ *
+ * If the call was successful, the newly created certificate object is
+ * returned. This key can be used with functions of the <citetitle>GNU
+ * TLS</citetitle> library. If the call was not successful, it returns %NULL
+ * and sets @error. The error domain is #EPC_TLS_ERROR. Error codes are taken
+ * from the <citetitle>GNU TLS</citetitle> library.
+ *
+ * Returns: The newly created certificate object, or %NULL.
+ */
+gnutls_x509_crt_t
+epc_tls_certificate_load (const gchar *filename,
+                          GError     **error)
+{
+  gint rc = GNUTLS_E_SUCCESS;
+  gchar *contents = NULL;
+
+  gnutls_x509_crt_t crt = NULL;
+  gnutls_datum_t buffer;
+
+  g_return_val_if_fail (NULL != filename, NULL);
+
+  if (g_file_get_contents (filename, &contents, (gsize*) &buffer.size, error))
+    {
+      if (EPC_DEBUG_LEVEL (1))
+        g_debug ("%s: Loading server certificate `%s'", G_STRLOC, filename);
+
+      buffer.data = (guchar*) contents;
+
+      epc_tls_check (rc = gnutls_x509_crt_init (&crt));
+      epc_tls_check (rc = gnutls_x509_crt_import (crt, &buffer, GNUTLS_X509_FMT_PEM));
+    }
+
+out:
+  if (GNUTLS_E_SUCCESS != rc)
+    {
+      g_set_error (error, EPC_TLS_ERROR, rc,
+                   _("Cannot import server certificate '%s': %s"),
+                   filename, gnutls_strerror (rc));
+
+      if (crt)
+        gnutls_x509_crt_deinit (crt);
+
+      crt = NULL;
+    }
+
+  g_free (contents);
+
+  return crt;
+}
+
+/**
+ * epc_tls_certificate_save:
+ * @certificate: a X.509 certificate
+ * @filename: name of a file to write the certificate to, in the GLib file name encoding
+ * @error: return location for a #GError, or %NULL
+ *
+ * Writes a PEM encoded X.509 certificate.
+ *
+ * If the call was successful, it returns %TRUE. If the call was not
+ * successful, it returns %FALSE and sets @error. The error domain is
+ * #EPC_TLS_ERROR. Error codes are taken from the <citetitle>GNU
+ * TLS</citetitle> library.
+ *
+ * Returns: %TRUE on successful, %FALSE if an error occurred
+ */
+gboolean
+epc_tls_certificate_save (gnutls_x509_crt_t  certificate,
+                          const gchar       *filename,
+                          GError           **error)
+{
+  gint rc = GNUTLS_E_SUCCESS;
+  gchar *display_name = NULL;
+  guchar *contents = NULL;
+  gchar *dirname = NULL;
+  gsize length = 0;
+
+  g_return_val_if_fail (NULL != certificate, FALSE);
+  g_return_val_if_fail (NULL != filename, FALSE);
+
+  if (EPC_DEBUG_LEVEL (1))
+    g_debug ("%s: Writing server certificate `%s'", G_STRLOC, filename);
+
+  rc = gnutls_x509_crt_export (certificate, GNUTLS_X509_FMT_PEM, NULL, &length);
+  g_assert (GNUTLS_E_SHORT_MEMORY_BUFFER == rc);
+
+  contents = g_malloc (length);
+
+  if (GNUTLS_E_SUCCESS != (rc =
+      gnutls_x509_crt_export (certificate, GNUTLS_X509_FMT_PEM, contents, &length)))
+    {
+      g_set_error (error, EPC_TLS_ERROR, rc,
+                   _("Cannot export server certificate to PEM format: %s"),
+                   gnutls_strerror (rc));
+
+      goto out;
+    }
+
+  if (g_mkdir_with_parents (dirname = g_path_get_dirname (filename), 0700))
+    {
+      display_name = g_filename_display_name (dirname);
+      rc = GNUTLS_E_FILE_ERROR;
+
+      g_set_error (error,
+                   G_FILE_ERROR,
+                   g_file_error_from_errno (errno),
+                   _("Failed to create server certificate folder '%s': %s"),
+                   display_name, g_strerror (errno));
+
+      goto out;
+    }
+
+  if (!g_file_set_contents (filename, (gchar*)contents, length, error))
+    rc = GNUTLS_E_FILE_ERROR;
+
+out:
+  g_free (display_name);
+  g_free (contents);
+  g_free (dirname);
+
+  return (GNUTLS_E_SUCCESS == rc);
+}
+
+/**
+ * epc_tls_get_server_credentials:
+ * @hostname: the server's host name
+ * @crtfile: location for storing the certificate's filename in GLib filename encoding
+ * @keyfile: location for storing the private key's filename in GLib filename encoding
+ * @error: return location for a #GError, or %NULL
+ *
+ * Searches or creates X.509 certificate and key for the server identified
+ * by @hostname. This function uses epc_tls_get_certificate_filename() and
+ * epc_tls_get_private_key_filename() to locate existing certificates and
+ * keys. New certificates and keys are generated, when the files cannot
+ * be found, or the existing files contain invalid or expired information.
+ *
+ * If the call was successful, it returns %TRUE. If the call was not
+ * successful, it returns %FALSE and sets @error. The error domain is
+ * #EPC_TLS_ERROR. Error codes are taken from the <citetitle>GNU
+ * TLS</citetitle> library.
+  *
+ * Returns: %TRUE on successful, %FALSE if an error occurred
+*/
+gboolean
+epc_tls_get_server_credentials (const gchar  *hostname,
+                                gchar       **crtfile,
+                                gchar       **keyfile,
+                                GError      **error)
+{
+  gboolean success = FALSE;
+
+  struct stat keyinfo, crtinfo;
+  gnutls_x509_privkey_t key = NULL;
+  gnutls_x509_crt_t crt = NULL;
+
+  gchar *_keyfile = NULL;
+  gchar *_crtfile = NULL;
+
+  g_return_val_if_fail (NULL != hostname, FALSE);
+
+  g_return_val_if_fail (NULL != crtfile, FALSE);
+  g_return_val_if_fail (NULL != keyfile, FALSE);
+
+  g_return_val_if_fail (NULL == *crtfile, FALSE);
+  g_return_val_if_fail (NULL == *keyfile, FALSE);
+
+  _crtfile = epc_tls_get_certificate_filename (hostname);
+  _keyfile = epc_tls_get_private_key_filename (hostname);
+
+  if (NULL == (key = epc_tls_private_key_load (_keyfile, NULL)))
+    {
+      if (!(key = epc_tls_private_key_new (error)) ||
+          !(epc_tls_private_key_save (key, _keyfile, error)))
+        goto out;
+    }
+
+  if (0 == g_stat (_keyfile, &keyinfo) &&
+      0 == g_stat (_crtfile, &crtinfo) &&
+      keyinfo.st_mtime <= crtinfo.st_mtime)
+    crt = epc_tls_certificate_load (_crtfile, NULL);
+
+  if (NULL != crt)
+    {
+      time_t now = time (NULL);
+      gboolean invalid = TRUE;
+
+      if (!gnutls_x509_crt_check_hostname (crt, hostname))
+        g_warning ("%s: The certificate's owner doesn't match hostname '%s'.", G_STRLOC, hostname);
+      else if (gnutls_x509_crt_get_activation_time (crt) > now)
+        g_warning ("%s: The certificate is not yet activated.", G_STRLOC);
+      else if (gnutls_x509_crt_get_expiration_time (crt) < now)
+        g_warning ("%s: The certificate has expired.", G_STRLOC);
+      else
+        invalid = FALSE;
+
+      if (invalid)
+        {
+          g_warning ("%s: Discarding invalid server certificate.", G_STRLOC);
+          gnutls_x509_crt_deinit (crt);
+          crt = NULL;
+        }
+    }
+
+  if (NULL == crt)
+    {
+      if (!(crt = epc_tls_certificate_new (hostname, 365 * EPC_TLS_SECONDS_PER_DAY, key, error)) ||
+          !(epc_tls_certificate_save (crt, _crtfile, error)))
+        goto out;
+    }
+
+  success = TRUE;
+
+out:
+  if (!success)
+    {
+      g_free (_keyfile);
+      g_free (_crtfile);
+
+      _keyfile = NULL;
+      _crtfile = NULL;
+    }
+
+  if (key)
+    gnutls_x509_privkey_deinit (key);
+  if (crt)
+    gnutls_x509_crt_deinit (crt);
+
+  *keyfile = _keyfile;
+  *crtfile = _crtfile;
+
+  return success;
+}
+
diff --git a/glom/libglom/libepc/tls.h b/glom/libglom/libepc/tls.h
new file mode 100644
index 0000000..e24904e
--- /dev/null
+++ b/glom/libglom/libepc/tls.h
@@ -0,0 +1,86 @@
+/* Easy Publish and Consume Library
+ * Copyright (C) 2007, 2008  Openismus GmbH
+ *
+ * This library 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 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Mathias Hasselmann
+ */
+#ifndef __EPC_TLS_H__
+#define __EPC_TLS_H__
+
+#include <glib-object.h>
+#include <gnutls/x509.h>
+
+G_BEGIN_DECLS
+
+/**
+ * EPC_TLS_ERROR:
+ *
+ * Error domain for TLS operations. Errors in this domain will
+ * be <citetitle>GNU TLS</citetitle> error codes. See GError
+ * for information on error domains.
+ */
+#define EPC_TLS_ERROR (epc_tls_error_quark ())
+
+/**
+ * EPC_TLS_SECONDS_PER_MINUTE:
+ *
+ * The number of seconds per minute.
+ */
+#define EPC_TLS_SECONDS_PER_MINUTE  (60)
+
+/**
+ * EPC_TLS_SECONDS_PER_HOUR:
+ *
+ * The number of seconds per hour.
+ */
+#define EPC_TLS_SECONDS_PER_HOUR    (60 * EPC_TLS_SECONDS_PER_MINUTE)
+
+/**
+ * EPC_TLS_SECONDS_PER_DAY:
+ *
+ * The number of seconds per day.
+ */
+#define EPC_TLS_SECONDS_PER_DAY     (24 * EPC_TLS_SECONDS_PER_HOUR)
+
+GQuark                epc_tls_error_quark              (void) G_GNUC_CONST;
+
+gnutls_x509_crt_t     epc_tls_certificate_new          (const gchar            *hostname,
+                                                        guint                   validity,
+                                                        gnutls_x509_privkey_t   key,
+                                                        GError                **error);
+gnutls_x509_crt_t     epc_tls_certificate_load         (const gchar            *filename,
+                                                        GError                **error);
+gboolean              epc_tls_certificate_save         (gnutls_x509_crt_t       certificate,
+                                                        const gchar            *filename,
+                                                        GError                **error);
+
+gnutls_x509_privkey_t epc_tls_private_key_new          (GError                **error);
+gnutls_x509_privkey_t epc_tls_private_key_load         (const gchar            *filename,
+                                                        GError                **error);
+gboolean              epc_tls_private_key_save         (gnutls_x509_privkey_t   key,
+                                                        const gchar            *filename,
+                                                        GError                **error);
+
+gchar*                epc_tls_get_certificate_filename (const gchar            *hostname);
+gchar*                epc_tls_get_private_key_filename (const gchar            *hostname);
+gboolean              epc_tls_get_server_credentials   (const gchar            *hostname,
+                                                        gchar                 **crtfile,
+                                                        gchar                 **keyfile,
+                                                        GError                **error);
+
+G_END_DECLS
+
+#endif /* __EPC_TLS_H__ */


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