diff options
author | Alexander Larsson <alexl@redhat.com> | 2008-12-01 09:24:00 +0000 |
---|---|---|
committer | Alexander Larsson <alexl@src.gnome.org> | 2008-12-01 09:24:00 +0000 |
commit | f8875b2649a0f85624e0da7df96fa6d47840c4a9 (patch) | |
tree | df7f32b8849652f03b597aead9f1b56d697d806a | |
parent | 6e89574f1656ff71f5aba339ff38e8e0417b172b (diff) | |
download | gvfs-f8875b2649a0f85624e0da7df96fa6d47840c4a9.tar.gz |
Added. Added. Added. Added.
2008-12-01 Alexander Larsson <alexl@redhat.com>
* common/Makefile.am:
* common/gvfsdnssdresolver.c: Added.
* common/gvfsdnssdresolver.h: Added.
* common/gvfsdnssdutils.c: Added.
* common/gvfsdnssdutils.h: Added.
* daemon/Makefile.am:
* daemon/dav+sd.mount.in: Added.
* daemon/dav.mount.in:
* daemon/gvfsbackenddav.c:
* daemon/gvfsbackenddnssd.c:
* daemon/gvfsbackendnetwork.c:
For references to dns-sd dav services, use a
dav+sd: uri, since this is stable over e.g.
port changes and as such work better in e.g.
bookmarks.
Patch from David Zeuthen (#555436)
svn path=/trunk/; revision=2112
-rw-r--r-- | ChangeLog | 20 | ||||
-rw-r--r-- | common/Makefile.am | 17 | ||||
-rw-r--r-- | common/gvfsdnssdresolver.c | 1253 | ||||
-rw-r--r-- | common/gvfsdnssdresolver.h | 80 | ||||
-rw-r--r-- | common/gvfsdnssdutils.c | 323 | ||||
-rw-r--r-- | common/gvfsdnssdutils.h | 50 | ||||
-rw-r--r-- | daemon/Makefile.am | 18 | ||||
-rw-r--r-- | daemon/dav+sd.mount.in | 4 | ||||
-rw-r--r-- | daemon/dav.mount.in | 2 | ||||
-rw-r--r-- | daemon/gvfsbackenddav.c | 152 | ||||
-rw-r--r-- | daemon/gvfsbackenddnssd.c | 218 | ||||
-rw-r--r-- | daemon/gvfsbackendnetwork.c | 2 |
12 files changed, 2050 insertions, 89 deletions
@@ -1,3 +1,23 @@ +2008-12-01 Alexander Larsson <alexl@redhat.com> + + * common/Makefile.am: + * common/gvfsdnssdresolver.c: Added. + * common/gvfsdnssdresolver.h: Added. + * common/gvfsdnssdutils.c: Added. + * common/gvfsdnssdutils.h: Added. + * daemon/Makefile.am: + * daemon/dav+sd.mount.in: Added. + * daemon/dav.mount.in: + * daemon/gvfsbackenddav.c: + * daemon/gvfsbackenddnssd.c: + * daemon/gvfsbackendnetwork.c: + For references to dns-sd dav services, use a + dav+sd: uri, since this is stable over e.g. + port changes and as such work better in e.g. + bookmarks. + + Patch from David Zeuthen (#555436) + 2008-11-18 Alexander Larsson <alexl@redhat.com> * daemon/gvfsbackendsftp.c: diff --git a/common/Makefile.am b/common/Makefile.am index 12dd9e83..4e10cf8b 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -22,3 +22,20 @@ libgvfscommon_la_SOURCES = \ libgvfscommon_la_LIBADD = \ $(DBUS_LIBS) \ $(GLIB_LIBS) + +if HAVE_AVAHI +lib_LTLIBRARIES += libgvfscommon-dnssd.la + +libgvfscommon_dnssd_la_SOURCES = \ + gvfsdnssdutils.c gvfsdnssdutils.h \ + gvfsdnssdresolver.c gvfsdnssdresolver.h \ + $(NULL) + +libgvfscommon_dnssd_la_CFLAGS = \ + $(AVAHI_CFLAGS) + +libgvfscommon_dnssd_la_LIBADD = \ + $(top_builddir)/common/libgvfscommon.la \ + $(AVAHI_LIBS) +endif + diff --git a/common/gvfsdnssdresolver.c b/common/gvfsdnssdresolver.c new file mode 100644 index 00000000..38327502 --- /dev/null +++ b/common/gvfsdnssdresolver.c @@ -0,0 +1,1253 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen <davidz@redhat.com> + */ + +/* + * TODO: - locking + * - cancellation + */ + +#include <config.h> +#include <string.h> +#include <glib/gi18n-lib.h> + +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> +#include <avahi-common/error.h> +#include <avahi-common/timeval.h> +#include <avahi-glib/glib-watch.h> +#include <avahi-glib/glib-malloc.h> + +#include "gvfsdnssdutils.h" +#include "gvfsdnssdresolver.h" + +enum +{ + PROP_0, + PROP_ENCODED_TRIPLE, + PROP_REQUIRED_TXT_KEYS, + PROP_SERVICE_NAME, + PROP_SERVICE_TYPE, + PROP_DOMAIN, + PROP_TIMEOUT_MSEC, + + PROP_IS_RESOLVED, + PROP_ADDRESS, + PROP_PORT, + PROP_TXT_RECORDS, +}; + +enum { + CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +struct _GVfsDnsSdResolver +{ + GObject parent_instance; + + char *encoded_triple; + char *service_name; + char *service_type; + char *domain; + char *required_txt_keys; + char **required_txt_keys_broken_out; + guint timeout_msec; + + gboolean is_resolved; + char *address; + guint port; + char **txt_records; + + AvahiServiceResolver *avahi_resolver; +}; + + +struct _GVfsDnsSdResolverClass +{ + GObjectClass parent_class; + + /* signals */ + void (*changed) (GVfsDnsSdResolver *resolver); +}; + +G_DEFINE_TYPE (GVfsDnsSdResolver, g_vfs_dns_sd_resolver, G_TYPE_OBJECT); + +static gboolean resolver_supports_mdns = FALSE; +static AvahiClient *global_client = NULL; +static gboolean avahi_initialized = FALSE; +static void free_global_avahi_client (void); +static AvahiClient *get_global_avahi_client (GError **error); + +static gboolean ensure_avahi_resolver (GVfsDnsSdResolver *resolver, + GError **error); + +static void service_resolver_cb (AvahiServiceResolver *resolver, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, + const char *type, + const char *domain, + const char *host_name, + const AvahiAddress *a, + uint16_t port, + AvahiStringList *txt, + AvahiLookupResultFlags flags, + void *user_data); + + + +static GList *resolvers = NULL; + +static void +clear_avahi_data (GVfsDnsSdResolver *resolver); + +static void +remove_client_from_resolver (GVfsDnsSdResolver *resolver) +{ + if (resolver->avahi_resolver != NULL) + { + avahi_service_resolver_free (resolver->avahi_resolver); + resolver->avahi_resolver = NULL; + } + + clear_avahi_data (resolver); +} + +static void +add_client_to_resolver (GVfsDnsSdResolver *resolver) +{ + ensure_avahi_resolver (resolver, NULL); +} + +/* Callback for state changes on the Client */ +static void +avahi_client_callback (AvahiClient *client, AvahiClientState state, void *userdata) +{ + if (global_client == NULL) + global_client = client; + + if (state == AVAHI_CLIENT_FAILURE) + { + if (avahi_client_errno (client) == AVAHI_ERR_DISCONNECTED) + { + free_global_avahi_client (); + + /* Attempt to reconnect */ + get_global_avahi_client (NULL); + } + } + else if (state == AVAHI_CLIENT_S_RUNNING) + { + /* Start resolving again */ + g_list_foreach (resolvers, (GFunc) add_client_to_resolver, NULL); + } +} + +static void +free_global_avahi_client (void) +{ + /* Remove current resolvers */ + g_list_foreach (resolvers, (GFunc) remove_client_from_resolver, NULL); + + /* Destroy client */ + avahi_client_free (global_client); + global_client = NULL; + avahi_initialized = FALSE; +} + +static AvahiClient * +get_global_avahi_client (GError **error) +{ + static AvahiGLibPoll *glib_poll = NULL; + int avahi_error; + + if (!avahi_initialized) + { + avahi_initialized = TRUE; + + if (glib_poll == NULL) + { + avahi_set_allocator (avahi_glib_allocator ()); + glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT); + } + + /* Create a new AvahiClient instance */ + global_client = avahi_client_new (avahi_glib_poll_get (glib_poll), + AVAHI_CLIENT_NO_FAIL, + avahi_client_callback, + glib_poll, + &avahi_error); + + if (global_client == NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Error initializing Avahi: %s"), + avahi_strerror (avahi_error)); + goto out; + } + } + + out: + + return global_client; +} + + +static gboolean +ensure_avahi_resolver (GVfsDnsSdResolver *resolver, + GError **error) +{ + AvahiClient *avahi_client; + gboolean ret; + + ret = FALSE; + + if (resolver->avahi_resolver != NULL) + { + ret = TRUE; + goto out; + } + + avahi_client = get_global_avahi_client (error); + if (avahi_client == NULL) + goto out; + + resolver->avahi_resolver = avahi_service_resolver_new (avahi_client, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + resolver->service_name, + resolver->service_type, + resolver->domain, + AVAHI_PROTO_UNSPEC, + 0, /* AvahiLookupFlags */ + service_resolver_cb, + resolver); + if (resolver->avahi_resolver == NULL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Error creating Avahi resolver: %s"), + avahi_strerror (avahi_client_errno (avahi_client))); + goto out; + } + + ret = TRUE; + +out: + return ret; +} + +static void +g_vfs_dns_sd_resolver_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GVfsDnsSdResolver *resolver = G_VFS_DNS_SD_RESOLVER (object); + + switch (prop_id) + { + case PROP_ENCODED_TRIPLE: + g_value_set_string (value, resolver->encoded_triple); + break; + + case PROP_REQUIRED_TXT_KEYS: + g_value_set_string (value, resolver->required_txt_keys); + break; + + case PROP_SERVICE_NAME: + g_value_set_string (value, resolver->service_name); + break; + + case PROP_SERVICE_TYPE: + g_value_set_string (value, resolver->service_type); + break; + + case PROP_DOMAIN: + g_value_set_string (value, resolver->domain); + break; + + case PROP_TIMEOUT_MSEC: + g_value_set_uint (value, resolver->timeout_msec); + break; + + case PROP_IS_RESOLVED: + g_value_set_boolean (value, resolver->is_resolved); + break; + + case PROP_ADDRESS: + g_value_set_string (value, resolver->address); + break; + + case PROP_PORT: + g_value_set_uint (value, resolver->port); + break; + + case PROP_TXT_RECORDS: + g_value_set_boxed (value, resolver->txt_records); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +g_vfs_dns_sd_resolver_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GVfsDnsSdResolver *resolver = G_VFS_DNS_SD_RESOLVER (object); + + switch (prop_id) + { + case PROP_ENCODED_TRIPLE: + resolver->encoded_triple = g_strdup (g_value_get_string (value)); + break; + + case PROP_REQUIRED_TXT_KEYS: + resolver->required_txt_keys = g_strdup (g_value_get_string (value)); + if (resolver->required_txt_keys != NULL) + { + /* TODO: maybe support escaping ',' */ + resolver->required_txt_keys_broken_out = g_strsplit (resolver->required_txt_keys, ",", 0); + } + break; + + case PROP_SERVICE_NAME: + resolver->service_name = g_strdup (g_value_get_string (value)); + break; + + case PROP_SERVICE_TYPE: + resolver->service_type = g_strdup (g_value_get_string (value)); + break; + + case PROP_DOMAIN: + resolver->domain = g_strdup (g_value_get_string (value)); + break; + + case PROP_TIMEOUT_MSEC: + resolver->timeout_msec = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +g_vfs_dns_sd_resolver_finalize (GObject *object) +{ + GVfsDnsSdResolver *resolver; + + resolver = G_VFS_DNS_SD_RESOLVER (object); + + g_free (resolver->encoded_triple); + g_free (resolver->service_name); + g_free (resolver->service_type); + g_free (resolver->domain); + g_free (resolver->required_txt_keys); + g_strfreev (resolver->required_txt_keys_broken_out); + + g_free (resolver->address); + g_strfreev (resolver->txt_records); + + if (resolver->avahi_resolver != NULL) + avahi_service_resolver_free (resolver->avahi_resolver); + + + resolvers = g_list_remove (resolvers, resolver); + + /* free the global avahi client for the last resolver */ + if (resolvers == NULL) + { + free_global_avahi_client (); + } + + G_OBJECT_CLASS (g_vfs_dns_sd_resolver_parent_class)->finalize (object); +} + +static void +g_vfs_dns_sd_resolver_constructed (GObject *object) +{ + GVfsDnsSdResolver *resolver; + + resolver = G_VFS_DNS_SD_RESOLVER (object); + + if (resolver->encoded_triple != NULL) + { + GError *error; + + if (resolver->service_name != NULL) + { + g_warning ("Ignoring service-name since encoded-triple is already set"); + g_free (resolver->service_name); + resolver->service_name = NULL; + } + + if (resolver->service_type != NULL) + { + g_warning ("Ignoring service-type since encoded-triple is already set"); + g_free (resolver->service_type); + resolver->service_type = NULL; + } + + if (resolver->domain != NULL) + { + g_warning ("Ignoring domain since encoded-triple is already set"); + g_free (resolver->domain); + resolver->domain = NULL; + } + + + error = NULL; + if (!g_vfs_decode_dns_sd_triple (resolver->encoded_triple, + &(resolver->service_name), + &(resolver->service_type), + &(resolver->domain), + &error)) + { + /* GObject construction can't fail. So whine if the triple isn't valid. */ + g_warning ("Malformed construction data passed: %s", error->message); + g_error_free (error); + + g_free (resolver->encoded_triple); + g_free (resolver->service_name); + g_free (resolver->service_type); + g_free (resolver->domain); + resolver->encoded_triple = NULL; + resolver->service_name = NULL; + resolver->service_type = NULL; + resolver->domain = NULL; + goto out; + } + } + + /* Always set encoded triple to what we encode; this is because we can decode + * an encoded triple that isn't 100% properly URI encoded, e.g. + * + * "davidz's public files on quad.fubar.dk._webdav._tcp.local" + * + * will be properly decoded. But we want to return a properly URI encoded triple + * + * "davidz%27s%20public%20files%20on%20quad%2efubar%2edk._webdav._tcp.local" + * + * for e.g. setting the GMountSpec. This is useful because the use can + * put the former into the pathbar in a file manager and then it will + * be properly rewritten on mount. + */ + g_free (resolver->encoded_triple); + resolver->encoded_triple = g_vfs_encode_dns_sd_triple (resolver->service_name, + resolver->service_type, + resolver->domain); + + /* start resolving immediately */ + ensure_avahi_resolver (resolver, NULL); + + resolvers = g_list_prepend (resolvers, resolver); + + out: + + if (G_OBJECT_CLASS (g_vfs_dns_sd_resolver_parent_class)->constructed != NULL) + G_OBJECT_CLASS (g_vfs_dns_sd_resolver_parent_class)->constructed (object); +} + +static void +g_vfs_dns_sd_resolver_class_init (GVfsDnsSdResolverClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + resolver_supports_mdns = (avahi_nss_support () > 0); + + gobject_class->get_property = g_vfs_dns_sd_resolver_get_property; + gobject_class->set_property = g_vfs_dns_sd_resolver_set_property; + gobject_class->finalize = g_vfs_dns_sd_resolver_finalize; + gobject_class->constructed = g_vfs_dns_sd_resolver_constructed; + + /** + * GVfsDnsSdResolver::changed: + * @resolver: The resolver emitting the signal. + * + * Emitted when resolved data changes. + */ + signals[CHANGED] = g_signal_new ("changed", + G_VFS_TYPE_DNS_SD_RESOLVER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GVfsDnsSdResolverClass, changed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + + /** + * GVfsDnsSdResolver:encoded-triple: + * + * The encoded DNS-SD triple for the resolver. + */ + g_object_class_install_property (gobject_class, + PROP_ENCODED_TRIPLE, + g_param_spec_string ("encoded-triple", + "Encoded triple", + "Encoded triple", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GVfsDnsSdResolver:required-txt-keys: + * + * A comma separated list of keys that must appear in the TXT + * records in order to consider the service being resolved. + */ + g_object_class_install_property (gobject_class, + PROP_REQUIRED_TXT_KEYS, + g_param_spec_string ("required-txt-keys", + "Required TXT keys", + "Required TXT keys", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GVfsDnsSdResolver:service-name: + * + * The name of the service for the resolver. + */ + g_object_class_install_property (gobject_class, + PROP_SERVICE_NAME, + g_param_spec_string ("service-name", + "Service Name", + "Service Name", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GVfsDnsSdResolver:service-type: + * + * The type of the service for the resolver. + */ + g_object_class_install_property (gobject_class, + PROP_SERVICE_TYPE, + g_param_spec_string ("service-type", + "Service Type", + "Service Type", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GVfsDnsSdResolver:domain: + * + * The domain for the resolver. + */ + g_object_class_install_property (gobject_class, + PROP_DOMAIN, + g_param_spec_string ("domain", + "Domain", + "Domain", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GVfsDnsSdResolver:timeout-msec: + * + * Timeout in milliseconds to use when resolving. + */ + g_object_class_install_property (gobject_class, + PROP_TIMEOUT_MSEC, + g_param_spec_uint ("timeout-msec", + "Timeout in milliseconds", + "Timeout in milliseconds", + 0, + G_MAXUINT, + 5000, + G_PARAM_CONSTRUCT | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GVfsDnsSdResolver:is-resolved: + * + * Whether the service is resolved. + */ + g_object_class_install_property (gobject_class, + PROP_IS_RESOLVED, + g_param_spec_boolean ("is-resolved", + "Is resolved", + "Is resolved", + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GVfsDnsSdResolver:address: + * + * The resolved address. + */ + g_object_class_install_property (gobject_class, + PROP_DOMAIN, + g_param_spec_string ("address", + "Address", + "Address", + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GVfsDnsSdResolver:port: + * + * The resolved port. + */ + g_object_class_install_property (gobject_class, + PROP_PORT, + g_param_spec_uint ("port", + "Port", + "Port", + 0, + 65536, + 0, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); + + /** + * GVfsDnsSdResolver:txt-records: + * + * The resolved TXT records. + */ + g_object_class_install_property (gobject_class, + PROP_TXT_RECORDS, + g_param_spec_boxed ("txt-records", + "TXT Records", + "TXT Records", + G_TYPE_STRV, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB | + G_PARAM_STATIC_NICK)); +} + +static void +g_vfs_dns_sd_resolver_init (GVfsDnsSdResolver *resolver) +{ +} + +gboolean +g_vfs_dns_sd_resolver_is_resolved (GVfsDnsSdResolver *resolver) +{ + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), FALSE); + return resolver->is_resolved; +} + +const gchar * +g_vfs_dns_sd_resolver_get_encoded_triple (GVfsDnsSdResolver *resolver) +{ + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL); + return resolver->encoded_triple; +} + +const gchar * +g_vfs_dns_sd_resolver_get_required_txt_keys (GVfsDnsSdResolver *resolver) +{ + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL); + return resolver->required_txt_keys; +} + +const gchar * +g_vfs_dns_sd_resolver_get_service_name (GVfsDnsSdResolver *resolver) +{ + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL); + return resolver->service_name; +} + +const gchar * +g_vfs_dns_sd_resolver_get_service_type (GVfsDnsSdResolver *resolver) +{ + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL); + return resolver->service_type; +} + +const gchar * +g_vfs_dns_sd_resolver_get_domain (GVfsDnsSdResolver *resolver) +{ + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL); + return resolver->domain; +} + +gchar * +g_vfs_dns_sd_resolver_get_address (GVfsDnsSdResolver *resolver) +{ + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL); + return g_strdup (resolver->address); +} + +guint +g_vfs_dns_sd_resolver_get_port (GVfsDnsSdResolver *resolver) +{ + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), (guint) -1); + return resolver->port; +} + +gchar ** +g_vfs_dns_sd_resolver_get_txt_records (GVfsDnsSdResolver *resolver) +{ + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL); + return g_strdupv (resolver->txt_records); +} + +gchar * +g_vfs_dns_sd_resolver_lookup_txt_record (GVfsDnsSdResolver *resolver, + const gchar *key) +{ + gint n; + gchar *result; + gsize key_len; + + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL); + g_return_val_if_fail (key != NULL, NULL); + + result = NULL; + + + if (resolver->txt_records == NULL) + goto out; + + key_len = strlen (key); + + for (n = 0; resolver->txt_records[n] != NULL; n++) + { + const gchar *s = resolver->txt_records[n]; + const gchar *p; + + p = strchr (s, '='); + if (p != NULL && (p - s) == key_len) + { + if (g_ascii_strncasecmp (s, + key, + p - s) == 0) + { + result = g_strdup (p + 1); + goto out; + } + } + } + + out: + return result; +} + +GVfsDnsSdResolver * +g_vfs_dns_sd_resolver_new_for_encoded_triple (const gchar *encoded_triple, + const gchar *required_txt_keys) + +{ + g_return_val_if_fail (encoded_triple != NULL, NULL); + + return G_VFS_DNS_SD_RESOLVER (g_object_new (G_VFS_TYPE_DNS_SD_RESOLVER, + "encoded-triple", encoded_triple, + "required-txt-keys", required_txt_keys, + NULL)); +} + +GVfsDnsSdResolver * +g_vfs_dns_sd_resolver_new_for_service (const gchar *service_name, + const gchar *service_type, + const gchar *domain, + const gchar *required_txt_keys) +{ + g_return_val_if_fail (service_name != NULL, NULL); + g_return_val_if_fail (service_type != NULL, NULL); + g_return_val_if_fail (domain != NULL, NULL); + + return G_VFS_DNS_SD_RESOLVER (g_object_new (G_VFS_TYPE_DNS_SD_RESOLVER, + "service-name", service_name, + "service-type", service_type, + "domain", domain, + "required-txt-keys", required_txt_keys, + NULL)); +} + +static int +safe_strcmp (const char *a, const char *b) +{ + if (a == NULL) + a = ""; + if (b == NULL) + b = ""; + return strcmp (a, b); +} + +static gboolean +strv_equal (char **a, char **b) +{ + static char *dummy[1] = {NULL}; + int n; + gboolean ret; + + if (a == NULL) + a = dummy; + if (b == NULL) + b = dummy; + + ret = FALSE; + + if (g_strv_length (a) != g_strv_length (b)) + goto out; + + for (n = 0; a[n] != NULL && b[n] != NULL; n++) + { + if (strcmp (a[n], b[n]) != 0) + goto out; + } + + ret = TRUE; + + out: + return ret; + +} + +static gboolean +has_required_txt_keys (GVfsDnsSdResolver *resolver) +{ + gboolean ret; + int n; + char *value; + + ret = FALSE; + + if (resolver->required_txt_keys_broken_out != NULL) + { + for (n = 0; resolver->required_txt_keys_broken_out[n] != NULL; n++) + { + value = g_vfs_dns_sd_resolver_lookup_txt_record (resolver, + resolver->required_txt_keys_broken_out[n]); + if (value == NULL) + { + /* key is missing */ + goto out; + } + g_free (value); + } + } + + ret = TRUE; + + out: + return ret; +} + +static void +set_avahi_data (GVfsDnsSdResolver *resolver, + const char *host_name, + AvahiProtocol protocol, + const AvahiAddress *a, + uint16_t port, + AvahiStringList *txt) +{ + char *address; + gboolean changed; + AvahiStringList *l; + GPtrArray *p; + char **txt_records; + gboolean is_resolved; + + changed = FALSE; + + if (resolver_supports_mdns) + { + address = g_strdup (host_name); + } + else + { + char aa[128]; + + avahi_address_snprint (aa, sizeof(aa), a); + if (protocol == AVAHI_PROTO_INET6) + { + /* an ipv6 address, follow RFC 2732 */ + address = g_strdup_printf ("[%s]", aa); + } + else + { + address = g_strdup (aa); + } + } + + if (safe_strcmp (resolver->address, address) != 0) + { + g_free (resolver->address); + resolver->address = g_strdup (address); + g_object_notify (G_OBJECT (resolver), "address"); + changed = TRUE; + } + + g_free (address); + + if (resolver->port != port) + { + resolver->port = port; + g_object_notify (G_OBJECT (resolver), "port"); + changed = TRUE; + } + + p = g_ptr_array_new (); + for (l = txt; l != NULL; l = avahi_string_list_get_next (l)) + { + g_ptr_array_add (p, g_strdup ((const char *) l->text)); + } + g_ptr_array_add (p, NULL); + txt_records = (char **) g_ptr_array_free (p, FALSE); + + if (strv_equal (resolver->txt_records, txt_records)) + { + g_strfreev (txt_records); + } + else + { + g_strfreev (resolver->txt_records); + resolver->txt_records = txt_records; + g_object_notify (G_OBJECT (resolver), "txt-records"); + changed = TRUE; + } + + is_resolved = has_required_txt_keys (resolver); + + if (is_resolved != resolver->is_resolved) + { + resolver->is_resolved = is_resolved; + g_object_notify (G_OBJECT (resolver), "is-resolved"); + changed = TRUE; + } + + if (changed) + g_signal_emit (resolver, signals[CHANGED], 0); +} + +static void +clear_avahi_data (GVfsDnsSdResolver *resolver) +{ + gboolean changed; + + changed = FALSE; + + if (resolver->is_resolved) + { + resolver->is_resolved = FALSE; + g_object_notify (G_OBJECT (resolver), "is-resolved"); + changed = TRUE; + } + + if (resolver->address != NULL) + { + g_free (resolver->address); + resolver->address = NULL; + g_object_notify (G_OBJECT (resolver), "address"); + changed = TRUE; + } + + if (resolver->port != 0) + { + resolver->port = 0; + g_object_notify (G_OBJECT (resolver), "port"); + changed = TRUE; + } + + if (resolver->txt_records != NULL) + { + resolver->txt_records = NULL; + g_object_notify (G_OBJECT (resolver), "txt-records"); + changed = TRUE; + } + + if (changed) + g_signal_emit (resolver, signals[CHANGED], 0); +} + +static void +service_resolver_cb (AvahiServiceResolver *avahi_resolver, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, + const char *type, + const char *domain, + const char *host_name, + const AvahiAddress *a, + uint16_t port, + AvahiStringList *txt, + AvahiLookupResultFlags flags, + void *user_data) +{ + GVfsDnsSdResolver *resolver = G_VFS_DNS_SD_RESOLVER (user_data); + + switch (event) + { + case AVAHI_RESOLVER_FOUND: + set_avahi_data (resolver, + host_name, + protocol, + a, + port, + txt); + break; + + case AVAHI_RESOLVER_FAILURE: + clear_avahi_data (resolver); + break; + } +} + + +typedef struct +{ + GVfsDnsSdResolver *resolver; + GSimpleAsyncResult *simple; + guint timeout_id; +} ResolveData; + +static void service_resolver_changed (GVfsDnsSdResolver *resolver, ResolveData *data); + +static void +resolve_data_free (ResolveData *data) +{ + if (data->timeout_id > 0) + g_source_remove (data->timeout_id); + g_signal_handlers_disconnect_by_func (data->resolver, service_resolver_changed, data); + g_object_unref (data->simple); + g_free (data); +} + +static void +service_resolver_changed (GVfsDnsSdResolver *resolver, + ResolveData *data) +{ + if (resolver->is_resolved) + { + g_simple_async_result_set_op_res_gboolean (data->simple, TRUE); + g_simple_async_result_complete (data->simple); + resolve_data_free (data); + } + else + { + if (data->resolver->address != NULL) + { + /* keep going until timeout if we're missing TXT records */ + } + else + { + g_simple_async_result_set_error (data->simple, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Error resolving \"%s\" service \"%s\" on domain \"%s\""), + data->resolver->service_type, + data->resolver->service_name, + data->resolver->domain); + g_simple_async_result_complete (data->simple); + resolve_data_free (data); + } + } +} + +static gboolean +service_resolver_timed_out (ResolveData *data) +{ + + if (data->resolver->address != NULL) + { + /* special case if one of the required TXT records are missing */ + g_simple_async_result_set_error (data->simple, + G_IO_ERROR, + G_IO_ERROR_FAILED, + _("Error resolving \"%s\" service \"%s\" on domain \"%s\". " + "One or more TXT records are missing. Keys required: \"%s\"."), + data->resolver->service_type, + data->resolver->service_name, + data->resolver->domain, + data->resolver->required_txt_keys); + } + else + { + g_simple_async_result_set_error (data->simple, + G_IO_ERROR, + G_IO_ERROR_TIMED_OUT, + _("Timed out resolving \"%s\" service \"%s\" on domain \"%s\""), + data->resolver->service_type, + data->resolver->service_name, + data->resolver->domain); + } + + g_simple_async_result_complete (data->simple); + data->timeout_id = 0; + resolve_data_free (data); + return FALSE; +} + +gboolean +g_vfs_dns_sd_resolver_resolve_finish (GVfsDnsSdResolver *resolver, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); + + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), FALSE); + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_vfs_dns_sd_resolver_resolve); + g_simple_async_result_propagate_error (simple, error); + + return g_simple_async_result_get_op_res_gboolean (simple); +} + +void +g_vfs_dns_sd_resolver_resolve (GVfsDnsSdResolver *resolver, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ResolveData *data; + GSimpleAsyncResult *simple; + GError *error; + + g_return_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver)); + + simple = g_simple_async_result_new (G_OBJECT (resolver), + callback, + user_data, + g_vfs_dns_sd_resolver_resolve); + + + if (resolver->is_resolved) + { + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + g_simple_async_result_complete (simple); + g_object_unref (simple); + goto out; + } + + error = NULL; + if (!ensure_avahi_resolver (resolver, &error)) + { + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); + g_object_unref (simple); + g_error_free (error); + goto out; + } + + data = g_new0 (ResolveData, 1); + data->resolver = resolver; + data->simple = simple; + data->timeout_id = g_timeout_add (resolver->timeout_msec, + (GSourceFunc) service_resolver_timed_out, + data); + + g_signal_connect (resolver, + "changed", + (GCallback) service_resolver_changed, + data); + + out: + ; +} + + +typedef struct +{ + GMainLoop *loop; + GError *error; + gboolean ret; +} ResolveDataSync; + +static void +resolve_sync_cb (GVfsDnsSdResolver *resolver, + GAsyncResult *res, + ResolveDataSync *data) +{ + data->ret = g_vfs_dns_sd_resolver_resolve_finish (resolver, + res, + &(data->error)); + g_main_loop_quit (data->loop); +} + + +gboolean +g_vfs_dns_sd_resolver_resolve_sync (GVfsDnsSdResolver *resolver, + GCancellable *cancellable, + GError **error) +{ + ResolveDataSync *data; + gboolean ret; + + g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), FALSE); + + data = g_new0 (ResolveDataSync, 1); + data->loop = g_main_loop_new (NULL, FALSE); + + g_vfs_dns_sd_resolver_resolve (resolver, + cancellable, + (GAsyncReadyCallback) resolve_sync_cb, + data); + + g_main_loop_run (data->loop); + + ret = data->ret; + if (data->error != NULL) + g_propagate_error (error, data->error); + + g_main_loop_unref (data->loop); + g_free (data); + + return ret; +} diff --git a/common/gvfsdnssdresolver.h b/common/gvfsdnssdresolver.h new file mode 100644 index 00000000..a694ff93 --- /dev/null +++ b/common/gvfsdnssdresolver.h @@ -0,0 +1,80 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen <davidz@redhat.com> + */ + +#ifndef __G_VFS_DNS_SD_RESOLVER_H__ +#define __G_VFS_DNS_SD_RESOLVER_H__ + +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define G_VFS_TYPE_DNS_SD_RESOLVER (g_vfs_dns_sd_resolver_get_type ()) +#define G_VFS_DNS_SD_RESOLVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_VFS_TYPE_DNS_SD_RESOLVER, GVfsDnsSdResolver)) +#define G_VFS_DNS_SD_RESOLVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_DNS_SD_RESOLVER, GVfsDnsSdResolverClass)) +#define G_VFS_IS_DNS_SD_RESOLVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_VFS_TYPE_DNS_SD_RESOLVER)) +#define G_VFS_IS_DNS_SD_RESOLVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_VFS_TYPE_DNS_SD_RESOLVER)) +#define G_VFS_DNS_SD_RESOLVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_VFS_TYPE_DNS_SD_RESOLVER, GVfsDnsSdResolverClass)) + +/** + * GVfsDnsSdResolver: + * + * Resolves DNS-SD triples. + */ +typedef struct _GVfsDnsSdResolver GVfsDnsSdResolver; +typedef struct _GVfsDnsSdResolverClass GVfsDnsSdResolverClass; + +GType g_vfs_dns_sd_resolver_get_type (void) G_GNUC_CONST; +GVfsDnsSdResolver *g_vfs_dns_sd_resolver_new_for_encoded_triple (const gchar *encoded_triple, + const gchar *required_txt_keys); +GVfsDnsSdResolver *g_vfs_dns_sd_resolver_new_for_service (const gchar *service_name, + const gchar *service_type, + const gchar *domain, + const gchar *required_txt_keys); +const gchar *g_vfs_dns_sd_resolver_get_encoded_triple (GVfsDnsSdResolver *resolver); +const gchar *g_vfs_dns_sd_resolver_get_service_name (GVfsDnsSdResolver *resolver); +const gchar *g_vfs_dns_sd_resolver_get_service_type (GVfsDnsSdResolver *resolver); +const gchar *g_vfs_dns_sd_resolver_get_domain (GVfsDnsSdResolver *resolver); +const gchar *g_vfs_dns_sd_resolver_get_required_txt_keys (GVfsDnsSdResolver *resolver); + +void g_vfs_dns_sd_resolver_resolve (GVfsDnsSdResolver *resolver, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean g_vfs_dns_sd_resolver_resolve_finish (GVfsDnsSdResolver *resolver, + GAsyncResult *res, + GError **error); + +gboolean g_vfs_dns_sd_resolver_resolve_sync (GVfsDnsSdResolver *resolver, + GCancellable *cancellable, + GError **error); + +gboolean g_vfs_dns_sd_resolver_is_resolved (GVfsDnsSdResolver *resolver); +gchar *g_vfs_dns_sd_resolver_get_address (GVfsDnsSdResolver *resolver); +guint g_vfs_dns_sd_resolver_get_port (GVfsDnsSdResolver *resolver); +gchar **g_vfs_dns_sd_resolver_get_txt_records (GVfsDnsSdResolver *resolver); +gchar *g_vfs_dns_sd_resolver_lookup_txt_record (GVfsDnsSdResolver *resolver, + const gchar *key); + +G_END_DECLS + +#endif /* __G_VFS_DNS_SD_RESOLVER_H__ */ diff --git a/common/gvfsdnssdutils.c b/common/gvfsdnssdutils.c new file mode 100644 index 00000000..f6592e1a --- /dev/null +++ b/common/gvfsdnssdutils.c @@ -0,0 +1,323 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen <davidz@redhat.com> + */ + +#include <config.h> +#include <string.h> +#include <glib/gi18n-lib.h> + +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> +#include <avahi-common/error.h> +#include <avahi-common/timeval.h> +#include <avahi-glib/glib-watch.h> +#include <avahi-glib/glib-malloc.h> + +#include "gvfsdnssdutils.h" + +static gchar * +escape_service_name (const gchar *service_name) +{ + GString *s; + char *res; + const gchar *p; + + g_return_val_if_fail (service_name != NULL, NULL); + + s = g_string_new (NULL); + + p = service_name; + while (*p != '\0') + { + if (*p == '\\') + g_string_append (s, "\\\\"); + else if (*p == '.') + g_string_append (s, "\\."); + else if (*p == '/') + g_string_append (s, "\\s"); + else + g_string_append_c (s, *p); + p++; + } + + res = g_uri_escape_string (s->str, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE); + g_string_free (s, TRUE); + return res; +} + +static gchar * +escape_service_name2 (const gchar *service_name) +{ + GString *s; + const gchar *p; + + g_return_val_if_fail (service_name != NULL, NULL); + + s = g_string_new (NULL); + + p = service_name; + while (*p != '\0') + { + if (*p == '.') + g_string_append (s, "%2e"); + else + g_string_append_c (s, *p); + p++; + } + + return g_string_free (s, FALSE); +} + +/** + * g_vfs_get_dns_sd_uri_for_triple: + * @service_name: DNS-SD service name. + * @service_type: DNS-SD service type. + * @domain: DNS-SD domain. + * + * Creates an URI for a file on the GVfs <literal>dns-sd</literal> + * virtual file system that provides live data for resolving the given + * DNS-SD service. + * + * The URI is of the form + * <literal>dns-sd://domain/service_name.service_type<literal> with + * suitable encoding added. + * + * Note that there may not exist a file at the returned URI, the + * resource providing the DNS-SD service will have to be available for + * the file to exist. + * + * Returns: An URI. Free with g_free(). + **/ +gchar * +g_vfs_get_dns_sd_uri_for_triple (const gchar *service_name, + const gchar *service_type, + const gchar *domain) +{ + gchar *escaped_service_name; + gchar *ret; + + g_return_val_if_fail (service_name != NULL, NULL); + g_return_val_if_fail (service_type != NULL, NULL); + g_return_val_if_fail (domain != NULL, NULL); + + escaped_service_name = escape_service_name (service_name); + + ret = g_strdup_printf ("dns-sd://%s/%s.%s", + domain, + escaped_service_name, + service_type); + g_free (escaped_service_name); + + return ret; +} + +/** + * g_vfs_encode_dns_sd_triple: + * @service_name: DNS-SD service name. + * @service_type: DNS-SD service type. + * @domain: DNS-SD domain. + * + * Creates an encoded triple representing a DNS-SD service. The triple + * will be of the form + * <literal>service_name.service_type.domain</literal> with suitable + * encoding. + * + * Use g_vfs_decode_dns_sd_triple() to decode the returned string. + * + * Returns: A string representing the triple, free with g_free(). + **/ +gchar * +g_vfs_encode_dns_sd_triple (const gchar *service_name, + const gchar *service_type, + const gchar *domain) +{ + char *dot_escaped_service_name; + char *escaped_service_name; + char *escaped_service_type; + char *escaped_domain; + char *s; + + escaped_service_name = g_uri_escape_string (service_name, NULL, FALSE); + dot_escaped_service_name = escape_service_name2 (escaped_service_name); + escaped_service_type = g_uri_escape_string (service_type, NULL, FALSE); + escaped_domain = g_uri_escape_string (domain, NULL, FALSE); + s = g_strdup_printf ("%s.%s.%s", + dot_escaped_service_name, + escaped_service_type, + escaped_domain); + g_free (dot_escaped_service_name); + g_free (escaped_service_name); + g_free (escaped_service_type); + g_free (escaped_domain); + return s; +} + +/** + * g_vfs_decode_dns_sd_triple: + * @encoded_triple: A string obtained from g_vfs_encode_dns_sd_triple(). + * @out_service_name: %NULL or return location for the service name. + * @out_service_type: %NULL or return location for the service type. + * @out_domain: %NULL or return location for the domain. + * @error: Return location for error or %NULL. + * + * Constructs a DNS-SD triple by decoding a string generated from + * g_vfs_encode_dns_sd_triple(). This can fail if @encoded_triple is + * malformed. + * + * Returns: %TRUE unless @error is set. + **/ +gboolean +g_vfs_decode_dns_sd_triple (const gchar *encoded_triple, + gchar **out_service_name, + gchar **out_service_type, + gchar **out_domain, + GError **error) +{ + gboolean ret; + int n; + int m; + int service_type_pos; + char *escaped_service_name; + char *escaped_service_type; + char *escaped_domain; + + g_return_val_if_fail (encoded_triple != NULL, FALSE); + + + escaped_service_name = NULL; + escaped_service_type = NULL; + escaped_domain = NULL; + ret = FALSE; + + if (out_service_name != NULL) + *out_service_name = NULL; + + if (out_service_type != NULL) + *out_service_type = NULL; + + if (out_domain != NULL) + *out_domain = NULL; + + /* Find first '.' followed by an underscore. */ + for (n = 0; encoded_triple[n] != '\0'; n++) + { + if (encoded_triple[n] == '.') + { + if (encoded_triple[n + 1] == '_') + break; + } + } + if (encoded_triple[n] == '\0') + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Malformed dns-sd encoded_triple '%s'"), + encoded_triple); + goto out; + } + + escaped_service_name = g_strndup (encoded_triple, n); + if (escaped_service_name == NULL) + goto out; + + if (out_service_name != NULL) + *out_service_name = g_uri_unescape_string (escaped_service_name, NULL); + + /* skip dot between service name and service type */ + n += 1; + + service_type_pos = n; + + /* skip next two dots */ + for (m = 0; m < 2; m++) + { + for (; encoded_triple[n] != '\0'; n++) + { + if (encoded_triple[n] == '.') + break; + } + if (encoded_triple[n] == '\0') + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Malformed dns-sd encoded_triple '%s'"), + encoded_triple); + goto out; + } + n++; + } + + escaped_service_type = g_strndup (encoded_triple + service_type_pos, n - service_type_pos - 1); + if (out_service_type != NULL) + *out_service_type = g_uri_unescape_string (escaped_service_type, NULL); + + /* the domain is the rest */ + if (encoded_triple[n] == '\0') + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Malformed dns-sd encoded_triple '%s'"), + encoded_triple); + goto out; + } + + escaped_domain = g_strdup (encoded_triple + n); + if (out_domain != NULL) + *out_domain = g_uri_unescape_string (escaped_domain, NULL); + + ret = TRUE; + + out: + g_free (escaped_service_name); + g_free (escaped_service_type); + g_free (escaped_domain); + return ret; +} + +gchar * +g_vfs_normalize_encoded_dns_sd_triple (const gchar *encoded_triple) +{ + char *service_name; + char *service_type; + char *domain; + char *ret; + + ret = NULL; + + if (!g_vfs_decode_dns_sd_triple (encoded_triple, + &service_name, + &service_type, + &domain, + NULL)) + goto out; + + ret = g_vfs_encode_dns_sd_triple (service_name, service_type, domain); + g_free (service_name); + g_free (service_type); + g_free (domain); + + out: + return ret; +} + diff --git a/common/gvfsdnssdutils.h b/common/gvfsdnssdutils.h new file mode 100644 index 00000000..21fe8a54 --- /dev/null +++ b/common/gvfsdnssdutils.h @@ -0,0 +1,50 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen <davidz@redhat.com> + */ + +#ifndef __G_VFS_DNS_SD_UTILS_H__ +#define __G_VFS_DNS_SD_UTILS_H__ + +#include <gio/gio.h> + +G_BEGIN_DECLS + +gchar *g_vfs_encode_dns_sd_triple (const gchar *service_name, + const gchar *service_type, + const gchar *domain); + +gchar *g_vfs_normalize_encoded_dns_sd_triple (const gchar *encoded_triple); + +gboolean +g_vfs_decode_dns_sd_triple (const gchar *encoded_triple, + gchar **out_service_name, + gchar **out_service_type, + gchar **out_domain, + GError **error); + +gchar * +g_vfs_get_dns_sd_uri_for_triple (const gchar *service_name, + const gchar *service_type, + const gchar *domain); + +G_END_DECLS + +#endif /* __G_VFS_DNS_SD_UTILS_H__ */ diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 653018b8..035be2c1 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -42,6 +42,9 @@ mount_DATA = sftp.mount trash.mount computer.mount burn.mount localtest.mount mount_in_files += http.mount.in dav.mount.in ftp.mount.in if HAVE_HTTP mount_DATA += http.mount dav.mount ftp.mount +if HAVE_AVAHI +mount_DATA += dav+sd.mount +endif libexec_PROGRAMS += gvfsd-http gvfsd-dav gvfsd-ftp endif @@ -304,7 +307,7 @@ gvfsd_dnssd_CPPFLAGS = \ $(AVAHI_CFLAGS) \ -DBACKEND_TYPES='"dns-sd", G_VFS_TYPE_BACKEND_DNS_SD,' -gvfsd_dnssd_LDADD = $(libraries) $(AVAHI_LIBS) +gvfsd_dnssd_LDADD = $(libraries) $(AVAHI_LIBS) $(top_builddir)/common/libgvfscommon-dnssd.la gvfsd_archive_SOURCES = \ @@ -391,8 +394,15 @@ gvfsd_dav_CPPFLAGS = \ -DBACKEND_HEADER=gvfsbackenddav.h \ -DDEFAULT_BACKEND_TYPE=dav \ -DMAX_JOB_THREADS=1 \ - $(HTTP_CFLAGS) \ - -DBACKEND_TYPES='"dav", G_VFS_TYPE_BACKEND_DAV,' + $(HTTP_CFLAGS) -gvfsd_dav_LDADD = $(libraries) $(HTTP_LIBS) +if HAVE_AVAHI +gvfsd_dav_CPPFLAGS += -DBACKEND_TYPES='"dav", G_VFS_TYPE_BACKEND_DAV, "dav+sd", G_VFS_TYPE_BACKEND_DAV, "davs+sd", G_VFS_TYPE_BACKEND_DAV,' +else +gvfsd_dav_CPPFLAGS += -DBACKEND_TYPES='"dav", G_VFS_TYPE_BACKEND_DAV,' +endif +gvfsd_dav_LDADD = $(libraries) $(HTTP_LIBS) +if HAVE_AVAHI +gvfsd_dav_LDADD += $(top_builddir)/common/libgvfscommon-dnssd.la +endif diff --git a/daemon/dav+sd.mount.in b/daemon/dav+sd.mount.in new file mode 100644 index 00000000..45703270 --- /dev/null +++ b/daemon/dav+sd.mount.in @@ -0,0 +1,4 @@ +[Mount] +Type=dav+sd;davs+sd +Exec=@libexecdir@/gvfsd-dav +AutoMount=false diff --git a/daemon/dav.mount.in b/daemon/dav.mount.in index 6e75bce7..6324625c 100644 --- a/daemon/dav.mount.in +++ b/daemon/dav.mount.in @@ -1,4 +1,4 @@ [Mount] -Type=dav +Type=dav;davs Exec=@libexecdir@/gvfsd-dav AutoMount=false diff --git a/daemon/gvfsbackenddav.c b/daemon/gvfsbackenddav.c index 24c2b34c..a693ac90 100644 --- a/daemon/gvfsbackenddav.c +++ b/daemon/gvfsbackenddav.c @@ -61,10 +61,20 @@ #include "soup-input-stream.h" #include "soup-output-stream.h" +#ifdef HAVE_AVAHI +#include "gvfsdnssdutils.h" +#include "gvfsdnssdresolver.h" +#endif + typedef struct _MountAuthData MountAuthData; static void mount_auth_info_free (MountAuthData *info); + +#ifdef HAVE_AVAHI +static void dns_sd_resolver_changed (GVfsDnsSdResolver *resolver, GVfsBackendDav *dav_backend); +#endif + typedef struct _AuthInfo { /* for server authentication */ @@ -91,6 +101,11 @@ struct _GVfsBackendDav GVfsBackendHttp parent_instance; MountAuthData auth_info; + +#ifdef HAVE_AVAHI + /* only set if we're handling a [dav|davs]+sd:// mounts */ + GVfsDnsSdResolver *resolver; +#endif }; G_DEFINE_TYPE (GVfsBackendDav, g_vfs_backend_dav, G_VFS_TYPE_BACKEND_HTTP); @@ -102,6 +117,14 @@ g_vfs_backend_dav_finalize (GObject *object) dav_backend = G_VFS_BACKEND_DAV (object); +#ifdef HAVE_AVAHI + if (dav_backend->resolver != NULL) + { + g_signal_handlers_disconnect_by_func (dav_backend->resolver, dns_sd_resolver_changed, dav_backend); + g_object_unref (dav_backend->resolver); + } +#endif + mount_auth_info_free (&(dav_backend->auth_info)); if (G_OBJECT_CLASS (g_vfs_backend_dav_parent_class)->finalize) @@ -209,10 +232,10 @@ path_equal (const char *a, const char *b, gboolean relax) a_len = strlen (a); b_len = strlen (b); - while (a[a_len - 1] == '/') + while (a_len > 0 && a[a_len - 1] == '/') a_len--; - while (b[b_len - 1] == '/') + while (b_len > 0 && b[b_len - 1] == '/') b_len--; if (a_len == b_len) @@ -1392,11 +1415,32 @@ g_mount_spec_to_dav_uri (GMountSpec *spec) } static GMountSpec * -g_mount_spec_from_dav_uri (SoupURI *uri) +g_mount_spec_from_dav_uri (GVfsBackendDav *dav_backend, + SoupURI *uri) { GMountSpec *spec; const char *ssl; +#ifdef HAVE_AVAHI + if (dav_backend->resolver != NULL) + { + const char *type; + const char *service_type; + + service_type = g_vfs_dns_sd_resolver_get_service_type (dav_backend->resolver); + if (strcmp (service_type, "_webdavs._tcp") == 0) + type = "davs+sd"; + else + type = "dav+sd"; + + spec = g_mount_spec_new (type); + g_mount_spec_set (spec, + "host", + g_vfs_dns_sd_resolver_get_encoded_triple (dav_backend->resolver)); + return spec; + } +#endif + spec = g_mount_spec_new ("dav"); g_mount_spec_set (spec, "host", uri->host); @@ -1423,6 +1467,63 @@ g_mount_spec_from_dav_uri (SoupURI *uri) return spec; } +#ifdef HAVE_AVAHI +static SoupURI * +dav_uri_from_dns_sd_resolver (GVfsBackendDav *dav_backend) +{ + SoupURI *uri; + char *user; + char *path; + char *address; + const char *service_type; + guint port; + + service_type = g_vfs_dns_sd_resolver_get_service_type (dav_backend->resolver); + address = g_vfs_dns_sd_resolver_get_address (dav_backend->resolver); + port = g_vfs_dns_sd_resolver_get_port (dav_backend->resolver); + user = g_vfs_dns_sd_resolver_lookup_txt_record (dav_backend->resolver, "u"); /* mandatory */ + path = g_vfs_dns_sd_resolver_lookup_txt_record (dav_backend->resolver, "path"); /* optional */ + + /* TODO: According to http://www.dns-sd.org/ServiceTypes.html + * there's also a TXT record "p" for password. Handle this. + */ + + uri = soup_uri_new (NULL); + + if (strcmp (service_type, "_webdavs._tcp") == 0) + soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS); + else + soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTP); + + soup_uri_set_user (uri, user); + + soup_uri_set_port (uri, port); + + soup_uri_set_host (uri, address); + + if (path != NULL) + soup_uri_set_path (uri, path); + else + soup_uri_set_path (uri, "/"); + + + g_free (address); + g_free (user); + g_free (path); + + return uri; +} +#endif + +#ifdef HAVE_AVAHI +static void +dns_sd_resolver_changed (GVfsDnsSdResolver *resolver, + GVfsBackendDav *dav_backend) +{ + /* TODO: handle when DNS-SD data changes */ +} +#endif + /* ************************************************************************* */ /* Backend Functions */ static void @@ -1432,6 +1533,7 @@ do_mount (GVfsBackend *backend, GMountSource *mount_source, gboolean is_automount) { + GVfsBackendDav *dav_backend = G_VFS_BACKEND_DAV (backend); MountAuthData *data; SoupSession *session; SoupMessage *msg_opts; @@ -1444,10 +1546,43 @@ do_mount (GVfsBackend *backend, gboolean res; char *last_good_path; char *display_name; + const char *host; + const char *type; g_print ("+ mount\n"); - mount_base = g_mount_spec_to_dav_uri (mount_spec); + host = g_mount_spec_get (mount_spec, "host"); + type = g_mount_spec_get (mount_spec, "type"); + +#ifdef HAVE_AVAHI + /* resolve DNS-SD style URIs */ + if ((strcmp (type, "dav+sd") == 0 || strcmp (type, "davs+sd") == 0) && host != NULL) + { + GError *error; + + dav_backend->resolver = g_vfs_dns_sd_resolver_new_for_encoded_triple (host, "u"); + + error = NULL; + if (!g_vfs_dns_sd_resolver_resolve_sync (dav_backend->resolver, + NULL, + &error)) + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + return; + } + g_signal_connect (dav_backend->resolver, + "changed", + (GCallback) dns_sd_resolver_changed, + dav_backend); + + mount_base = dav_uri_from_dns_sd_resolver (dav_backend); + } + else +#endif + { + mount_base = g_mount_spec_to_dav_uri (mount_spec); + } if (mount_base == NULL) { @@ -1546,12 +1681,17 @@ do_mount (GVfsBackend *backend, mount_base->path = last_good_path; /* dup the mountspec, but only copy known fields */ - mount_spec = g_mount_spec_from_dav_uri (mount_base); + mount_spec = g_mount_spec_from_dav_uri (dav_backend, mount_base); g_vfs_backend_set_mount_spec (backend, mount_spec); g_vfs_backend_set_icon_name (backend, "folder-remote"); - display_name = g_strdup_printf (_("WebDAV on %s"), mount_base->host); +#ifdef HAVE_AVAHI + if (dav_backend->resolver != NULL) + display_name = g_strdup (g_vfs_dns_sd_resolver_get_service_name (dav_backend->resolver)); + else +#endif + display_name = g_strdup_printf (_("WebDAV on %s"), mount_base->host); g_vfs_backend_set_display_name (backend, display_name); g_free (display_name); diff --git a/daemon/gvfsbackenddnssd.c b/daemon/gvfsbackenddnssd.c index 3a224a50..9ef21eaa 100644 --- a/daemon/gvfsbackenddnssd.c +++ b/daemon/gvfsbackenddnssd.c @@ -37,6 +37,7 @@ #include <avahi-glib/glib-malloc.h> #include "gvfsbackenddnssd.h" +#include "gvfsdnssdutils.h" #include "gvfsdaemonprotocol.h" #include "gvfsjobcreatemonitor.h" @@ -45,15 +46,40 @@ #include "gvfsmonitor.h" static struct { - char *type; - char *method; - char *icon; - gpointer handle; + char *type; + char *method; + gboolean use_dns_sd_uri; + char *icon; } dns_sd_types[] = { - {"_ftp._tcp", "ftp", "folder-remote-ftp"}, - {"_webdav._tcp", "dav", "folder-remote"}, - {"_webdavs._tcp", "davs", "folder-remote"}, - {"_sftp-ssh._tcp", "sftp", "folder-remote-ssh"}, + { + "_ftp._tcp", + "ftp", + FALSE, + "folder-remote-ftp" + }, + { + "_webdav._tcp", + "dav+sd", + TRUE, + "folder-remote-dav" + }, + { + "_webdavs._tcp", + "davs+sd", + TRUE, + "folder-remote-davs"}, + { + "_sftp-ssh._tcp", + "sftp", + FALSE, + "folder-remote-ssh" + }, + { + "_ssh._tcp", + "sftp", + FALSE, + "folder-remote-ssh" + }, }; static AvahiClient *global_client = NULL; @@ -62,15 +88,19 @@ static gboolean avahi_initialized = FALSE; static GList *dnssd_backends = NULL; typedef struct { - char *file_name; + char *file_name; char *name; char *type; + char *domain; char *target_uri; + GIcon *icon; } LinkFile; static LinkFile root = { "/" }; +static gboolean resolver_supports_mdns = FALSE; + struct _GVfsBackendDnsSd { GVfsBackend parent_instance; @@ -164,7 +194,7 @@ get_icon_for_type (const char *type) for (i = 0; i < G_N_ELEMENTS (dns_sd_types); i++) { if (strcmp (type, dns_sd_types[i].type) == 0) - return g_themed_icon_new (dns_sd_types[i].icon); + return g_themed_icon_new_with_default_fallbacks (dns_sd_types[i].icon); } return g_themed_icon_new ("text-x-generic"); @@ -184,34 +214,18 @@ get_method_for_type (const char *type) return NULL; } -static char * -encode_filename (const char *service, - const char *type) +static gboolean +use_dns_sd_uri_for_type (const char *type) { - GString *string; - const char *p; - - string = g_string_new (NULL); - - p = service; - - while (*p) + int i; + + for (i = 0; i < G_N_ELEMENTS (dns_sd_types); i++) { - if (*p == '\\') - g_string_append (string, "\\\\"); - else if (*p == '.') - g_string_append (string, "\\."); - else if (*p == '/') - g_string_append (string, "\\s"); - else - g_string_append_c (string, *p); - p++; + if (strcmp (type, dns_sd_types[i].type) == 0) + return dns_sd_types[i].use_dns_sd_uri; } - - g_string_append_c (string, '.'); - g_string_append (string, type); - - return g_string_free (string, FALSE); + + return FALSE; } static LinkFile * @@ -225,56 +239,89 @@ link_file_new (const char *name, AvahiStringList *txt) { LinkFile *file; - char *path, *user, *user_str; - AvahiStringList *path_l, *user_l; char a[128]; const char *method; - + char *uri; + file = g_slice_new0 (LinkFile); file->name = g_strdup (name); file->type = g_strdup (type); - file->file_name = encode_filename (name, type); + file->domain = g_strdup (domain); file->icon = get_icon_for_type (type); - - path = NULL; - user_str = NULL; - if (txt != NULL) - { - path_l = avahi_string_list_find (txt, "path"); - if (path_l != NULL) - avahi_string_list_get_pair (path_l, NULL, &path, NULL); - - user_l = avahi_string_list_find (txt, "u"); - if (user_l != NULL) - { - avahi_string_list_get_pair (user_l, NULL, &user, NULL); - - user_str = g_strconcat (user, "@", NULL); - } - } - - if (path == NULL) - path = g_strdup ("/"); - + uri = g_vfs_get_dns_sd_uri_for_triple (name, type, domain); + file->file_name = g_path_get_basename (uri); + g_free (uri); + avahi_address_snprint (a, sizeof(a), address); method = get_method_for_type (type); - if (protocol == AVAHI_PROTO_INET6) - /* an ipv6 address, follow rfc2732 */ - file->target_uri = g_strdup_printf ("%s://%s[%s]:%d%s", - method, - user_str?user_str:"", - a, port, path); + if (use_dns_sd_uri_for_type (type)) + { + char *encoded_triple; + encoded_triple = g_vfs_encode_dns_sd_triple (name, type, domain); + file->target_uri = g_strdup_printf ("%s://%s", + method, + encoded_triple); + g_free (encoded_triple); + } else - file->target_uri = g_strdup_printf ("%s://%s%s:%d%s", - method, - user_str?user_str:"", - a, port, path); - g_free (user_str); - g_free (path); + { + char *path, *user, *user_str; + AvahiStringList *path_l, *user_l; + + path = NULL; + user_str = NULL; + if (txt != NULL) + { + path_l = avahi_string_list_find (txt, "path"); + if (path_l != NULL) + avahi_string_list_get_pair (path_l, NULL, &path, NULL); + user_l = avahi_string_list_find (txt, "u"); + if (user_l != NULL) + { + avahi_string_list_get_pair (user_l, NULL, &user, NULL); + user_str = g_strconcat (user, "@", NULL); + } + } + if (path == NULL) + path = g_strdup ("/"); + + if (resolver_supports_mdns) + { + file->target_uri = g_strdup_printf ("%s://%s%s:%d%s", + method, + user_str != NULL ? user_str : "", + host_name, port, path); + } + else + { + if (protocol == AVAHI_PROTO_INET6) + { + /* an ipv6 address, follow rfc2732 */ + file->target_uri = g_strdup_printf ("%s://%s[%s]:%d%s", + method, + user_str?user_str:"", + a, + port, + path); + } + else + { + file->target_uri = g_strdup_printf ("%s://%s%s:%d%s", + method, + user_str?user_str:"", + a, + port, + path); + } + } + + g_free (user_str); + g_free (path); + } return file; } @@ -285,6 +332,7 @@ link_file_free (LinkFile *file) g_free (file->file_name); g_free (file->name); g_free (file->type); + g_free (file->domain); g_free (file->target_uri); if (file->icon) @@ -357,7 +405,7 @@ file_info_from_file (LinkFile *file, g_file_info_set_name (info, file->file_name); g_file_info_set_display_name (info, file->name); - if (file->icon) + if (file->icon) g_file_info_set_icon (info, file->icon); g_file_info_set_file_type (info, G_FILE_TYPE_SHORTCUT); @@ -366,8 +414,7 @@ file_info_from_file (LinkFile *file, g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL, TRUE); - g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, - file->target_uri); + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, file->target_uri); } /* Backend Functions */ @@ -427,13 +474,28 @@ try_query_info (GVfsBackend *backend, if (file == &root) { GIcon *icon; + char *display_name; + char *s; + + s = g_strdup (job->uri); + if (s[strlen(s) - 1] == '/') /* job->uri is guranteed to be longer than 1 byte */ + s[strlen(s) - 1] = '\0'; + display_name = g_path_get_basename (s); + if (strcmp (display_name, "local") == 0) + { + g_free (display_name); + display_name = g_strdup (_("Local Network")); + } + g_free (s); + g_file_info_set_name (info, "/"); g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); - /* TODO: Name */ - g_file_info_set_display_name (info, _("dns-sd")); + g_file_info_set_display_name (info, display_name); icon = g_themed_icon_new ("network-workgroup"); g_file_info_set_icon (info, icon); g_object_unref (icon); + g_free (display_name); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); @@ -689,6 +751,8 @@ g_vfs_backend_dns_sd_init (GVfsBackendDnsSd *network_backend) g_vfs_backend_set_icon_name (backend, "network-workgroup"); g_vfs_backend_set_user_visible (backend, FALSE); + resolver_supports_mdns = (avahi_nss_support () > 0); + } static void diff --git a/daemon/gvfsbackendnetwork.c b/daemon/gvfsbackendnetwork.c index d23438e7..939461e3 100644 --- a/daemon/gvfsbackendnetwork.c +++ b/daemon/gvfsbackendnetwork.c @@ -411,7 +411,7 @@ recompute_files (GVfsBackendNetwork *backend) file = network_file_new (file_name, domains[i], link_uri, - backend->server_icon); + backend->workgroup_icon); files = g_list_prepend (files, file); g_free (link_uri); g_free (file_name); |