X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gio%2Fgnetworkaddress.c;h=5cb4c74388188176e03d13a2780d0e0d3d0bc5fd;hb=634b69219979c084837c59874e5b2aec01a1d3e4;hp=09df3dbf229f1b8be1fb2ffec0b82ff016097daa;hpb=5b3d62a7a6d4354b1588dd94f477c44bd15f1753;p=platform%2Fupstream%2Fglib.git diff --git a/gio/gnetworkaddress.c b/gio/gnetworkaddress.c index 09df3db..5cb4c74 100644 --- a/gio/gnetworkaddress.c +++ b/gio/gnetworkaddress.c @@ -15,9 +15,7 @@ * 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. + * Public License along with this library; if not, see . */ #include "config.h" @@ -30,15 +28,15 @@ #include "ginetaddress.h" #include "ginetsocketaddress.h" #include "gnetworkingprivate.h" +#include "gproxyaddressenumerator.h" #include "gresolver.h" -#include "gsimpleasyncresult.h" +#include "gtask.h" #include "gsocketaddressenumerator.h" #include "gioerror.h" #include "gsocketconnectable.h" #include -#include "gioalias.h" /** * SECTION:gnetworkaddress @@ -64,12 +62,16 @@ struct _GNetworkAddressPrivate { gchar *hostname; guint16 port; GList *sockaddrs; + gchar *scheme; + + gint64 resolver_serial; }; enum { PROP_0, PROP_HOSTNAME, PROP_PORT, + PROP_SCHEME, }; static void g_network_address_set_property (GObject *object, @@ -81,10 +83,12 @@ static void g_network_address_get_property (GObject *object, GValue *value, GParamSpec *pspec); -static void g_network_address_connectable_iface_init (GSocketConnectableIface *iface); -static GSocketAddressEnumerator *g_network_address_connectable_enumerate (GSocketConnectable *connectable); +static void g_network_address_connectable_iface_init (GSocketConnectableIface *iface); +static GSocketAddressEnumerator *g_network_address_connectable_enumerate (GSocketConnectable *connectable); +static GSocketAddressEnumerator *g_network_address_connectable_proxy_enumerate (GSocketConnectable *connectable); G_DEFINE_TYPE_WITH_CODE (GNetworkAddress, g_network_address, G_TYPE_OBJECT, + G_ADD_PRIVATE (GNetworkAddress) G_IMPLEMENT_INTERFACE (G_TYPE_SOCKET_CONNECTABLE, g_network_address_connectable_iface_init)) @@ -94,6 +98,7 @@ g_network_address_finalize (GObject *object) GNetworkAddress *addr = G_NETWORK_ADDRESS (object); g_free (addr->priv->hostname); + g_free (addr->priv->scheme); if (addr->priv->sockaddrs) { @@ -112,8 +117,6 @@ g_network_address_class_init (GNetworkAddressClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - g_type_class_add_private (klass, sizeof (GNetworkAddressPrivate)); - gobject_class->set_property = g_network_address_set_property; gobject_class->get_property = g_network_address_get_property; gobject_class->finalize = g_network_address_finalize; @@ -134,19 +137,28 @@ g_network_address_class_init (GNetworkAddressClass *klass) G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SCHEME, + g_param_spec_string ("scheme", + P_("Scheme"), + P_("URI Scheme"), + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); } static void g_network_address_connectable_iface_init (GSocketConnectableIface *connectable_iface) { connectable_iface->enumerate = g_network_address_connectable_enumerate; + connectable_iface->proxy_enumerate = g_network_address_connectable_proxy_enumerate; } static void g_network_address_init (GNetworkAddress *addr) { - addr->priv = G_TYPE_INSTANCE_GET_PRIVATE (addr, G_TYPE_NETWORK_ADDRESS, - GNetworkAddressPrivate); + addr->priv = g_network_address_get_instance_private (addr); } static void @@ -160,8 +172,7 @@ g_network_address_set_property (GObject *object, switch (prop_id) { case PROP_HOSTNAME: - if (addr->priv->hostname) - g_free (addr->priv->hostname); + g_free (addr->priv->hostname); addr->priv->hostname = g_value_dup_string (value); break; @@ -169,6 +180,11 @@ g_network_address_set_property (GObject *object, addr->priv->port = g_value_get_uint (value); break; + case PROP_SCHEME: + g_free (addr->priv->scheme); + addr->priv->scheme = g_value_dup_string (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -194,6 +210,10 @@ g_network_address_get_property (GObject *object, g_value_set_uint (value, addr->priv->port); break; + case PROP_SCHEME: + g_value_set_string (value, addr->priv->scheme); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -203,7 +223,8 @@ g_network_address_get_property (GObject *object, static void g_network_address_set_addresses (GNetworkAddress *addr, - GList *addresses) + GList *addresses, + guint64 resolver_serial) { GList *a; GSocketAddress *sockaddr; @@ -218,6 +239,24 @@ g_network_address_set_addresses (GNetworkAddress *addr, } g_list_free (addresses); addr->priv->sockaddrs = g_list_reverse (addr->priv->sockaddrs); + + addr->priv->resolver_serial = resolver_serial; +} + +static gboolean +g_network_address_parse_sockaddr (GNetworkAddress *addr) +{ + GSocketAddress *sockaddr; + + sockaddr = g_inet_socket_address_new_from_string (addr->priv->hostname, + addr->priv->port); + if (sockaddr) + { + addr->priv->sockaddrs = g_list_prepend (addr->priv->sockaddrs, sockaddr); + return TRUE; + } + else + return FALSE; } /** @@ -228,7 +267,7 @@ g_network_address_set_addresses (GNetworkAddress *addr, * Creates a new #GSocketConnectable for connecting to the given * @hostname and @port. * - * Return value: the new #GNetworkAddress + * Returns: (transfer full) (type GNetworkAddress): the new #GNetworkAddress * * Since: 2.22 */ @@ -252,12 +291,11 @@ g_network_address_new (const gchar *hostname, * @hostname and @port. May fail and return %NULL in case * parsing @host_and_port fails. * - * @host_and_port may be in any of a number of recognised formats: an IPv6 + * @host_and_port may be in any of a number of recognised formats; an IPv6 * address, an IPv4 address, or a domain name (in which case a DNS * lookup is performed). Quoting with [] is supported for all address * types. A port override may be specified in the usual way with a - * colon. Ports may be given as decimal numbers or symbolic names (in - * which case an /etc/services lookup is performed). + * colon. * * If no port is specified in @host_and_port then @default_port will be * used as the port number to connect to. @@ -266,7 +304,12 @@ g_network_address_new (const gchar *hostname, * (allowing them to give the hostname, and a port overide if necessary) * and @default_port is expected to be provided by the application. * - * Return value: the new #GNetworkAddress, or %NULL on error + * (The port component of @host_and_port can also be specified as a + * service name rather than as a numeric port, but this functionality + * is deprecated, because it depends on the contents of /etc/services, + * which is generally quite sparse on platforms other than Linux.) + * + * Returns: (transfer full): the new #GNetworkAddress, or %NULL on error * * Since: 2.22 */ @@ -398,6 +441,331 @@ g_network_address_parse (const gchar *host_and_port, return connectable; } +/* Allowed characters outside alphanumeric for unreserved. */ +#define G_URI_OTHER_UNRESERVED "-._~" + +/* This or something equivalent will eventually go into glib/guri.h */ +gboolean +_g_uri_parse_authority (const char *uri, + char **host, + guint16 *port, + char **userinfo) +{ + char *tmp_str; + const char *start, *p, *at, *delim; + char c; + + g_return_val_if_fail (uri != NULL, FALSE); + + if (host) + *host = NULL; + + if (port) + *port = 0; + + if (userinfo) + *userinfo = NULL; + + /* From RFC 3986 Decodes: + * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + * hier-part = "//" authority path-abempty + * path-abempty = *( "/" segment ) + * authority = [ userinfo "@" ] host [ ":" port ] + */ + + /* Check we have a valid scheme */ + tmp_str = g_uri_parse_scheme (uri); + + if (tmp_str == NULL) + return FALSE; + + g_free (tmp_str); + + /* Decode hier-part: + * hier-part = "//" authority path-abempty + */ + p = uri; + start = strstr (p, "//"); + + if (start == NULL) + return FALSE; + + start += 2; + + /* check if the @ sign is part of the authority before attempting to + * decode the userinfo */ + delim = strpbrk (start, "/?#[]"); + at = strchr (start, '@'); + if (at && delim && at > delim) + at = NULL; + + if (at != NULL) + { + /* Decode userinfo: + * userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + */ + p = start; + while (1) + { + c = *p++; + + if (c == '@') + break; + + /* pct-encoded */ + if (c == '%') + { + if (!(g_ascii_isxdigit (p[0]) || + g_ascii_isxdigit (p[1]))) + return FALSE; + + p++; + + continue; + } + + /* unreserved / sub-delims / : */ + if (!(g_ascii_isalnum (c) || + strchr (G_URI_OTHER_UNRESERVED, c) || + strchr (G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, c) || + c == ':')) + return FALSE; + } + + if (userinfo) + *userinfo = g_strndup (start, p - start - 1); + + start = p; + } + else + { + p = start; + } + + + /* decode host: + * host = IP-literal / IPv4address / reg-name + * reg-name = *( unreserved / pct-encoded / sub-delims ) + */ + + /* If IPv6 or IPvFuture */ + if (*p == '[') + { + gboolean has_scope_id = FALSE, has_bad_scope_id = FALSE; + + start++; + p++; + while (1) + { + c = *p++; + + if (c == ']') + break; + + if (c == '%' && !has_scope_id) + { + has_scope_id = TRUE; + if (p[0] != '2' || p[1] != '5') + has_bad_scope_id = TRUE; + continue; + } + + /* unreserved / sub-delims */ + if (!(g_ascii_isalnum (c) || + strchr (G_URI_OTHER_UNRESERVED, c) || + strchr (G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, c) || + c == ':' || + c == '.')) + goto error; + } + + if (host) + { + if (has_bad_scope_id) + *host = g_strndup (start, p - start - 1); + else + *host = g_uri_unescape_segment (start, p - 1, NULL); + } + + c = *p++; + } + else + { + while (1) + { + c = *p++; + + if (c == ':' || + c == '/' || + c == '?' || + c == '#' || + c == '\0') + break; + + /* pct-encoded */ + if (c == '%') + { + if (!(g_ascii_isxdigit (p[0]) || + g_ascii_isxdigit (p[1]))) + goto error; + + p++; + + continue; + } + + /* unreserved / sub-delims */ + if (!(g_ascii_isalnum (c) || + strchr (G_URI_OTHER_UNRESERVED, c) || + strchr (G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, c))) + goto error; + } + + if (host) + *host = g_uri_unescape_segment (start, p - 1, NULL); + } + + if (c == ':') + { + /* Decode port: + * port = *DIGIT + */ + guint tmp = 0; + + while (1) + { + c = *p++; + + if (c == '/' || + c == '?' || + c == '#' || + c == '\0') + break; + + if (!g_ascii_isdigit (c)) + goto error; + + tmp = (tmp * 10) + (c - '0'); + + if (tmp > 65535) + goto error; + } + if (port) + *port = (guint16) tmp; + } + + return TRUE; + +error: + if (host && *host) + { + g_free (*host); + *host = NULL; + } + + if (userinfo && *userinfo) + { + g_free (*userinfo); + *userinfo = NULL; + } + + return FALSE; +} + +gchar * +_g_uri_from_authority (const gchar *protocol, + const gchar *host, + guint port, + const gchar *userinfo) +{ + GString *uri; + + uri = g_string_new (protocol); + g_string_append (uri, "://"); + + if (userinfo) + { + g_string_append_uri_escaped (uri, userinfo, G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO, FALSE); + g_string_append_c (uri, '@'); + } + + if (g_hostname_is_non_ascii (host)) + { + gchar *ace_encoded = g_hostname_to_ascii (host); + + if (!ace_encoded) + { + g_string_free (uri, TRUE); + return NULL; + } + g_string_append (uri, ace_encoded); + g_free (ace_encoded); + } + else if (strchr (host, ':')) + g_string_append_printf (uri, "[%s]", host); + else + g_string_append (uri, host); + + if (port != 0) + g_string_append_printf (uri, ":%u", port); + + return g_string_free (uri, FALSE); +} + +/** + * g_network_address_parse_uri: + * @uri: the hostname and optionally a port + * @default_port: The default port if none is found in the URI + * @error: a pointer to a #GError, or %NULL + * + * Creates a new #GSocketConnectable for connecting to the given + * @uri. May fail and return %NULL in case parsing @uri fails. + * + * Using this rather than g_network_address_new() or + * g_network_address_parse() allows #GSocketClient to determine + * when to use application-specific proxy protocols. + * + * Returns: (transfer full): the new #GNetworkAddress, or %NULL on error + * + * Since: 2.26 + */ +GSocketConnectable * +g_network_address_parse_uri (const gchar *uri, + guint16 default_port, + GError **error) +{ + GSocketConnectable *conn; + gchar *scheme; + gchar *hostname; + guint16 port; + + if (!_g_uri_parse_authority (uri, &hostname, &port, NULL)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Invalid URI '%s'", + uri); + return NULL; + } + + if (port == 0) + port = default_port; + + scheme = g_uri_parse_scheme (uri); + + conn = g_object_new (G_TYPE_NETWORK_ADDRESS, + "hostname", hostname, + "port", port, + "scheme", scheme, + NULL); + + g_free (scheme); + g_free (hostname); + + return conn; +} + /** * g_network_address_get_hostname: * @addr: a #GNetworkAddress @@ -405,7 +773,7 @@ g_network_address_parse (const gchar *host_and_port, * Gets @addr's hostname. This might be either UTF-8 or ASCII-encoded, * depending on what @addr was created with. * - * Return value: @addr's hostname + * Returns: @addr's hostname * * Since: 2.22 */ @@ -423,7 +791,7 @@ g_network_address_get_hostname (GNetworkAddress *addr) * * Gets @addr's port number * - * Return value: @addr's port (which may be 0) + * Returns: @addr's port (which may be 0) * * Since: 2.22 */ @@ -435,6 +803,24 @@ g_network_address_get_port (GNetworkAddress *addr) return addr->priv->port; } +/** + * g_network_address_get_scheme: + * @addr: a #GNetworkAddress + * + * Gets @addr's scheme + * + * Returns: @addr's scheme (%NULL if not built from URI) + * + * Since: 2.26 + */ +const gchar * +g_network_address_get_scheme (GNetworkAddress *addr) +{ + g_return_val_if_fail (G_IS_NETWORK_ADDRESS (addr), NULL); + + return addr->priv->scheme; +} + #define G_TYPE_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (_g_network_address_address_enumerator_get_type ()) #define G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_NETWORK_ADDRESS_ADDRESS_ENUMERATOR, GNetworkAddressAddressEnumerator)) @@ -442,7 +828,8 @@ typedef struct { GSocketAddressEnumerator parent_instance; GNetworkAddress *addr; - GList *a; + GList *addresses; + GList *next; } GNetworkAddressAddressEnumerator; typedef struct { @@ -450,6 +837,7 @@ typedef struct { } GNetworkAddressAddressEnumeratorClass; +static GType _g_network_address_address_enumerator_get_type (void); G_DEFINE_TYPE (GNetworkAddressAddressEnumerator, _g_network_address_address_enumerator, G_TYPE_SOCKET_ADDRESS_ENUMERATOR) static void @@ -472,31 +860,73 @@ g_network_address_address_enumerator_next (GSocketAddressEnumerator *enumerator G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (enumerator); GSocketAddress *sockaddr; - if (!addr_enum->addr->priv->sockaddrs) + if (addr_enum->addresses == NULL) { + GNetworkAddress *addr = addr_enum->addr; GResolver *resolver = g_resolver_get_default (); - GList *addresses; + gint64 serial = g_resolver_get_serial (resolver); - addresses = g_resolver_lookup_by_name (resolver, - addr_enum->addr->priv->hostname, - cancellable, error); - g_object_unref (resolver); + if (addr->priv->resolver_serial != 0 && + addr->priv->resolver_serial != serial) + { + /* Resolver has reloaded, discard cached addresses */ + g_list_free_full (addr->priv->sockaddrs, g_object_unref); + addr->priv->sockaddrs = NULL; + } - if (!addresses) - return NULL; + if (!addr->priv->sockaddrs) + g_network_address_parse_sockaddr (addr); + if (!addr->priv->sockaddrs) + { + GList *addresses; - g_network_address_set_addresses (addr_enum->addr, addresses); - addr_enum->a = addr_enum->addr->priv->sockaddrs; + addresses = g_resolver_lookup_by_name (resolver, + addr->priv->hostname, + cancellable, error); + if (!addresses) + { + g_object_unref (resolver); + return NULL; + } + + g_network_address_set_addresses (addr, addresses, serial); + } + + addr_enum->addresses = addr->priv->sockaddrs; + addr_enum->next = addr_enum->addresses; + g_object_unref (resolver); } - if (!addr_enum->a) + if (addr_enum->next == NULL) return NULL; - else + + sockaddr = addr_enum->next->data; + addr_enum->next = addr_enum->next->next; + return g_object_ref (sockaddr); +} + +static void +have_addresses (GNetworkAddressAddressEnumerator *addr_enum, + GTask *task, GError *error) +{ + GSocketAddress *sockaddr; + + addr_enum->addresses = addr_enum->addr->priv->sockaddrs; + addr_enum->next = addr_enum->addresses; + + if (addr_enum->next) { - sockaddr = addr_enum->a->data; - addr_enum->a = addr_enum->a->next; - return g_object_ref (sockaddr); + sockaddr = g_object_ref (addr_enum->next->data); + addr_enum->next = addr_enum->next->next; } + else + sockaddr = NULL; + + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, sockaddr, g_object_unref); + g_object_unref (task); } static void @@ -504,34 +934,23 @@ got_addresses (GObject *source_object, GAsyncResult *result, gpointer user_data) { - GSimpleAsyncResult *simple = user_data; - GNetworkAddressAddressEnumerator *addr_enum = - g_simple_async_result_get_op_res_gpointer (simple); + GTask *task = user_data; + GNetworkAddressAddressEnumerator *addr_enum = g_task_get_source_object (task); GResolver *resolver = G_RESOLVER (source_object); GList *addresses; GError *error = NULL; - addresses = g_resolver_lookup_by_name_finish (resolver, result, &error); if (!addr_enum->addr->priv->sockaddrs) { - if (error) - { - g_simple_async_result_set_from_error (simple, error); - g_error_free (error); - } - else + addresses = g_resolver_lookup_by_name_finish (resolver, result, &error); + + if (!error) { - g_network_address_set_addresses (addr_enum->addr, addresses); - addr_enum->a = addr_enum->addr->priv->sockaddrs; + g_network_address_set_addresses (addr_enum->addr, addresses, + g_resolver_get_serial (resolver)); } } - else if (error) - g_error_free (error); - - g_object_unref (resolver); - - g_simple_async_result_complete (simple); - g_object_unref (simple); + have_addresses (addr_enum, task, error); } static void @@ -542,32 +961,55 @@ g_network_address_address_enumerator_next_async (GSocketAddressEnumerator *enum { GNetworkAddressAddressEnumerator *addr_enum = G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (enumerator); - GSimpleAsyncResult *simple; GSocketAddress *sockaddr; + GTask *task; - simple = g_simple_async_result_new (G_OBJECT (enumerator), - callback, user_data, - g_network_address_address_enumerator_next_async); + task = g_task_new (addr_enum, cancellable, callback, user_data); - if (!addr_enum->addr->priv->sockaddrs) + if (addr_enum->addresses == NULL) { + GNetworkAddress *addr = addr_enum->addr; GResolver *resolver = g_resolver_get_default (); + gint64 serial = g_resolver_get_serial (resolver); - g_simple_async_result_set_op_res_gpointer (simple, g_object_ref (addr_enum), g_object_unref); - g_resolver_lookup_by_name_async (resolver, - addr_enum->addr->priv->hostname, - cancellable, - got_addresses, simple); + if (addr->priv->resolver_serial != 0 && + addr->priv->resolver_serial != serial) + { + /* Resolver has reloaded, discard cached addresses */ + g_list_free_full (addr->priv->sockaddrs, g_object_unref); + addr->priv->sockaddrs = NULL; + } + + if (!addr->priv->sockaddrs) + { + if (g_network_address_parse_sockaddr (addr)) + have_addresses (addr_enum, task, NULL); + else + { + g_resolver_lookup_by_name_async (resolver, + addr->priv->hostname, + cancellable, + got_addresses, task); + } + g_object_unref (resolver); + return; + } + + addr_enum->addresses = addr->priv->sockaddrs; + addr_enum->next = addr_enum->addresses; + g_object_unref (resolver); } - else - { - sockaddr = g_network_address_address_enumerator_next (enumerator, NULL, NULL); - if (sockaddr) - g_simple_async_result_set_op_res_gpointer (simple, sockaddr, g_object_unref); - g_simple_async_result_complete_in_idle (simple); - g_object_unref (simple); + if (addr_enum->next) + { + sockaddr = g_object_ref (addr_enum->next->data); + addr_enum->next = addr_enum->next->next; } + else + sockaddr = NULL; + + g_task_return_pointer (task, sockaddr, g_object_unref); + g_object_unref (task); } static GSocketAddress * @@ -575,21 +1017,9 @@ g_network_address_address_enumerator_next_finish (GSocketAddressEnumerator *enu GAsyncResult *result, GError **error) { - GNetworkAddressAddressEnumerator *addr_enum = - G_NETWORK_ADDRESS_ADDRESS_ENUMERATOR (enumerator); - GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); - GSocketAddress *sockaddr; + g_return_val_if_fail (g_task_is_valid (result, enumerator), NULL); - if (g_simple_async_result_propagate_error (simple, error)) - return NULL; - else if (!addr_enum->a) - return NULL; - else - { - sockaddr = addr_enum->a->data; - addr_enum->a = addr_enum->a->next; - return g_object_ref (sockaddr); - } + return g_task_propagate_pointer (G_TASK (result), error); } static void @@ -621,5 +1051,24 @@ g_network_address_connectable_enumerate (GSocketConnectable *connectable) return (GSocketAddressEnumerator *)addr_enum; } -#define __G_NETWORK_ADDRESS_C__ -#include "gioaliasdef.c" +static GSocketAddressEnumerator * +g_network_address_connectable_proxy_enumerate (GSocketConnectable *connectable) +{ + GNetworkAddress *self = G_NETWORK_ADDRESS (connectable); + GSocketAddressEnumerator *proxy_enum; + gchar *uri; + + uri = _g_uri_from_authority (self->priv->scheme ? self->priv->scheme : "none", + self->priv->hostname, + self->priv->port, + NULL); + + proxy_enum = g_object_new (G_TYPE_PROXY_ADDRESS_ENUMERATOR, + "connectable", connectable, + "uri", uri, + NULL); + + g_free (uri); + + return proxy_enum; +}