[glom/gtkmm4v4] libglom: Copy in the libepc source code.
- From: Murray Cumming <murrayc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glom/gtkmm4v4] libglom: Copy in the libepc source code.
- Date: Wed, 1 Nov 2017 19:06:39 +0000 (UTC)
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]