[libsoup] Try all IP addrs associated with a hostname if the first fails



commit b248daf6ab6bba3c4b346dc1f2b45cb73881d05b
Author: Dan Winship <danw gnome org>
Date:   Sun Nov 15 12:05:16 2009 -0500

    Try all IP addrs associated with a hostname if the first fails
    
    Make SoupAddress keep track of multiple IP addresses and implement
    GSocketConnectable, and make SoupSocket use GSocketClient (and thus
    GSocketAddressEnumerator) to connect, so that it tries all addresses
    associated with the SoupAddress.
    
    In particular, this fixes the bug where if a host has both IPv4 and
    IPv6 addresses, and glibc thinks you have IPv6 connectivity, but you
    don't really, that libsoup can't connect to that host.
    
    (Using GSocketClient rather than using GSocketAddressEnumerator
    directly is a bit heavyweight for SoupSocket, since we don't actually
    want to have a GSocket or GSocketConnection. But eventually, we will
    be porting SoupSocket to use GSocket, and this code is a stepping
    stone to that. Also, letting GSocketClient do the looping over the
    addresses simplifies SoupSocket.)
    
    (This change also silently breaks connection timeouts unless you have
    the very latest git glib.)
    
    https://bugzilla.gnome.org/show_bug.cgi?id=526321

 libsoup/soup-address.c |  360 ++++++++++++++++++++++++++++++++++++++----------
 libsoup/soup-address.h |    1 +
 libsoup/soup-socket.c  |  329 +++++++++++++-------------------------------
 tests/libsoup.supp     |   13 ++-
 4 files changed, 395 insertions(+), 308 deletions(-)
---
diff --git a/libsoup/soup-address.c b/libsoup/soup-address.c
index d8be7bd..7e95240 100644
--- a/libsoup/soup-address.c
+++ b/libsoup/soup-address.c
@@ -2,7 +2,7 @@
 /*
  * soup-address.c: Internet address handing
  *
- * Copyright (C) 2000-2003, Ximian, Inc.
+ * Copyright (C) 2010 Red Hat, Inc.
  */
 
 #ifdef HAVE_CONFIG_H
@@ -23,15 +23,6 @@
 #include "soup-marshal.h"
 #include "soup-misc.h"
 
-#ifndef INET_ADDRSTRLEN
-#  define INET_ADDRSTRLEN 16
-#  define INET6_ADDRSTRLEN 46
-#endif
-
-#ifndef INADDR_NONE
-#define INADDR_NONE -1
-#endif
-
 /**
  * SECTION:soup-address
  * @short_description: DNS support
@@ -54,7 +45,8 @@ enum {
 };
 
 typedef struct {
-	struct sockaddr *sockaddr;
+	struct sockaddr_storage *sockaddr;
+	int n_addrs, offset;
 
 	char *name, *physical;
 	guint port;
@@ -69,9 +61,9 @@ typedef struct {
 #define SOUP_SIN6(priv) ((struct sockaddr_in6 *)priv->sockaddr)
 
 /* sockaddr family macros */
-#define SOUP_ADDRESS_GET_FAMILY(priv) (priv->sockaddr->sa_family)
+#define SOUP_ADDRESS_GET_FAMILY(priv) (priv->sockaddr->ss_family)
 #define SOUP_ADDRESS_SET_FAMILY(priv, family) \
-	(priv->sockaddr->sa_family = family)
+	(priv->sockaddr->ss_family = family)
 #define SOUP_ADDRESS_FAMILY_IS_VALID(family) \
 	(family == AF_INET || family == AF_INET6)
 #define SOUP_ADDRESS_FAMILY_SOCKADDR_SIZE(family) \
@@ -84,12 +76,12 @@ typedef struct {
 /* sockaddr port macros */
 #define SOUP_ADDRESS_PORT_IS_VALID(port) (port >= 0 && port <= 65535)
 #define SOUP_ADDRESS_GET_PORT(priv) \
-	(priv->sockaddr->sa_family == AF_INET ? \
+	(priv->sockaddr->ss_family == AF_INET ? \
 		SOUP_SIN(priv)->sin_port : \
 		SOUP_SIN6(priv)->sin6_port)
 #define SOUP_ADDRESS_SET_PORT(priv, port) \
 	G_STMT_START {					\
-	if (priv->sockaddr->sa_family == AF_INET)	\
+	if (priv->sockaddr->ss_family == AF_INET)	\
 		SOUP_SIN(priv)->sin_port = port;	\
 	else						\
 		SOUP_SIN6(priv)->sin6_port = port;	\
@@ -97,7 +89,7 @@ typedef struct {
 
 /* sockaddr data macros */
 #define SOUP_ADDRESS_GET_DATA(priv) \
-	(priv->sockaddr->sa_family == AF_INET ? \
+	(priv->sockaddr->ss_family == AF_INET ? \
 		(gpointer)&SOUP_SIN(priv)->sin_addr : \
 		(gpointer)&SOUP_SIN6(priv)->sin6_addr)
 #define SOUP_ADDRESS_SET_DATA(priv, data, length) \
@@ -112,7 +104,12 @@ static void set_property (GObject *object, guint prop_id,
 static void get_property (GObject *object, guint prop_id,
 			  GValue *value, GParamSpec *pspec);
 
-G_DEFINE_TYPE (SoupAddress, soup_address, G_TYPE_OBJECT)
+static void soup_address_connectable_iface_init (GSocketConnectableIface *connectable_iface);
+static GSocketAddressEnumerator *soup_address_connectable_enumerate (GSocketConnectable *connectable);
+
+G_DEFINE_TYPE_WITH_CODE (SoupAddress, soup_address, G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (G_TYPE_SOCKET_CONNECTABLE,
+						soup_address_connectable_iface_init))
 
 static void
 soup_address_init (SoupAddress *addr)
@@ -221,6 +218,12 @@ soup_address_class_init (SoupAddressClass *address_class)
 				      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 }
 
+static void
+soup_address_connectable_iface_init (GSocketConnectableIface *connectable_iface)
+{
+  connectable_iface->enumerate  = soup_address_connectable_enumerate;
+}
+
 static GObject *
 constructor (GType                  type,
 	     guint                  n_construct_properties,
@@ -272,6 +275,7 @@ set_property (GObject *object, guint prop_id,
 		priv->sockaddr = g_malloc0 (SOUP_ADDRESS_FAMILY_SOCKADDR_SIZE (family));
 		SOUP_ADDRESS_SET_FAMILY (priv, family);
 		SOUP_ADDRESS_SET_PORT (priv, htons (priv->port));
+		priv->n_addrs = 1;
 		break;
 
 	case PROP_PORT:
@@ -293,6 +297,7 @@ set_property (GObject *object, guint prop_id,
 
 		len = SOUP_ADDRESS_FAMILY_SOCKADDR_SIZE (sa->sa_family);
 		priv->sockaddr = g_memdup (sa, len);
+		priv->n_addrs = 1;
 		priv->port = ntohs (SOUP_ADDRESS_GET_PORT (priv));
 		break;
 	default:
@@ -463,7 +468,25 @@ soup_address_get_sockaddr (SoupAddress *addr, int *len)
 
 	if (priv->sockaddr && len)
 		*len = SOUP_ADDRESS_FAMILY_SOCKADDR_SIZE (SOUP_ADDRESS_GET_FAMILY (priv));
-	return priv->sockaddr;
+	return (struct sockaddr *)priv->sockaddr;
+}
+
+/**
+ * soup_address_get_gsockaddr:
+ * @addr: a #SoupAddress
+ *
+ * Creates a new #GSocketAddress corresponding to @addr (which is assumed
+ * to only have one socket address associated with it).
+ *
+ * Return value: a new #GSocketAddress
+ */
+GSocketAddress *
+soup_address_get_gsockaddr (SoupAddress *addr)
+{
+	SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr);
+
+	return g_socket_address_new_from_native (priv->sockaddr,
+						 SOUP_ADDRESS_FAMILY_SOCKADDR_SIZE (SOUP_ADDRESS_GET_FAMILY (priv)));
 }
 
 static GInetAddress *
@@ -540,7 +563,7 @@ update_addrs (SoupAddress *addr, GList *addrs, GError *error)
 	SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr);
 	GInetAddress *gia;
 	GSocketAddress *gsa;
-	gsize len;
+	int i;
 
 	if (error) {
 		if (error->domain == G_IO_ERROR &&
@@ -553,17 +576,22 @@ update_addrs (SoupAddress *addr, GList *addrs, GError *error)
 	else if (priv->sockaddr)
 		return SOUP_STATUS_OK;
 
-	gia = addrs->data;
-	gsa = g_inet_socket_address_new (gia, priv->port);
-
-	len = g_socket_address_get_native_size (gsa);
-	priv->sockaddr = g_malloc (len);
-	if (!g_socket_address_to_native (gsa, priv->sockaddr, len, NULL)) {
-                /* can't happen: We know the address format is supported
-                 * and the buffer is large enough */
-                g_warn_if_reached ();
-        }
-	g_object_unref (gsa);
+	priv->n_addrs = g_list_length (addrs);
+	priv->sockaddr = g_new (struct sockaddr_storage, priv->n_addrs);
+	for (i = 0; addrs; addrs = addrs->next, i++) {
+		gia = addrs->data;
+		gsa = g_inet_socket_address_new (gia, priv->port);
+
+		if (!g_socket_address_to_native (gsa, &priv->sockaddr[i],
+						 sizeof (struct sockaddr_storage),
+						 NULL)) {
+			/* can't happen: We know the address format is supported
+			 * and the buffer is large enough
+			 */
+			g_warn_if_reached ();
+		}
+		g_object_unref (gsa);
+	}
 
 	return SOUP_STATUS_OK;
 }
@@ -599,10 +627,24 @@ complete_resolve_async (SoupAddress *addr, guint status)
 	SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr);
 	SoupAddressResolveAsyncData *res_data;
 	GSList *lookups, *l;
+	GSource *current_source;
+	GMainContext *current_context;
 
 	lookups = priv->async_lookups;
 	priv->async_lookups = NULL;
 
+	/* Awful hack; to make soup_socket_connect_async() with an
+	 * non-default async_context work correctly, we need to ensure
+	 * that the non-default context (which we're now running in)
+	 * is the thread-default when the callbacks are run...
+	 */
+	current_source = g_main_current_source ();
+	if (current_source && !g_source_is_destroyed (current_source))
+		current_context = g_source_get_context (current_source);
+	else
+		current_context = NULL;
+	g_main_context_push_thread_default (current_context);
+
 	for (l = lookups; l; l = l->next) {
 		res_data = l->data;
 
@@ -614,6 +656,8 @@ complete_resolve_async (SoupAddress *addr, guint status)
 	}
 	g_slist_free (lookups);
 
+	g_main_context_pop_thread_default (current_context);
+
 	g_object_unref (addr);
 }
 
@@ -643,10 +687,16 @@ lookup_resolved (GObject *source, GAsyncResult *result, gpointer user_data)
 	} else
 		status = SOUP_STATUS_OK;
 
-	if (error)
-		g_error_free (error);
+	/* For the enumerator impl, below */
+	g_object_ref (addr);
+	g_object_set_data (G_OBJECT (addr), "async-resolved-error", error);
 
 	complete_resolve_async (addr, status);
+
+	g_object_set_data (G_OBJECT (addr), "async-resolved-error", NULL);
+	g_object_unref (addr);
+	if (error)
+		g_error_free (error);
 }
 
 static gboolean
@@ -729,7 +779,7 @@ soup_address_resolve_async (SoupAddress *addr, GMainContext *async_context,
 	}
 
 	resolver = g_resolver_get_default ();
-	if (async_context && async_context != g_main_context_default ())
+	if (async_context)
 		g_main_context_push_thread_default (async_context);
 
 	if (priv->name) {
@@ -746,42 +796,18 @@ soup_address_resolve_async (SoupAddress *addr, GMainContext *async_context,
 		g_object_unref (gia);
 	}
 
-	if (async_context && async_context != g_main_context_default ())
+	if (async_context)
 		g_main_context_pop_thread_default (async_context);
 	g_object_unref (resolver);
 }
 
-/**
- * soup_address_resolve_sync:
- * @addr: a #SoupAddress
- * @cancellable: a #GCancellable object, or %NULL
- *
- * Synchronously resolves the missing half of @addr, as with
- * soup_address_resolve_async().
- *
- * If @cancellable is non-%NULL, it can be used to cancel the
- * resolution. soup_address_resolve_sync() will then return a status
- * of %SOUP_STATUS_CANCELLED.
- *
- * It is safe to call this more than once, even from different
- * threads, but it is not safe to mix calls to
- * soup_address_resolve_sync() with calls to
- * soup_address_resolve_async() on the same address.
- *
- * Return value: %SOUP_STATUS_OK, %SOUP_STATUS_CANT_RESOLVE, or
- * %SOUP_STATUS_CANCELLED.
- **/
-guint
-soup_address_resolve_sync (SoupAddress *addr, GCancellable *cancellable)
+static guint
+resolve_sync_internal (SoupAddress *addr, GCancellable *cancellable, GError **error)
 {
-	SoupAddressPrivate *priv;
+	SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr);
 	GResolver *resolver;
-	GError *error = NULL;
 	guint status;
-
-	g_return_val_if_fail (SOUP_IS_ADDRESS (addr), SOUP_STATUS_MALFORMED);
-	priv = SOUP_ADDRESS_GET_PRIVATE (addr);
-	g_return_val_if_fail (priv->name || priv->sockaddr, SOUP_STATUS_MALFORMED);
+	GError *my_err = NULL;
 
 	resolver = g_resolver_get_default ();
 
@@ -797,10 +823,10 @@ soup_address_resolve_sync (SoupAddress *addr, GCancellable *cancellable)
 
 		g_mutex_unlock (priv->lock);
 		addrs = g_resolver_lookup_by_name (resolver, priv->name,
-						   cancellable, &error);
+						   cancellable, &my_err);
 		g_mutex_lock (priv->lock);
 
-		status = update_addrs (addr, addrs, error);
+		status = update_addrs (addr, addrs, my_err);
 		g_resolver_free_addresses (addrs);
 	} else if (!priv->name) {
 		GInetAddress *gia;
@@ -809,24 +835,56 @@ soup_address_resolve_sync (SoupAddress *addr, GCancellable *cancellable)
 		g_mutex_unlock (priv->lock);
 		gia = soup_address_make_inet_address (addr);
 		name = g_resolver_lookup_by_address (resolver, gia,
-						     cancellable, &error);
+						     cancellable, &my_err);
 		g_object_unref (gia);
 		g_mutex_lock (priv->lock);
 
-		status = update_name (addr, name, error);
+		status = update_name (addr, name, my_err);
 		g_free (name);
 	} else
 		status = SOUP_STATUS_OK;
 	g_mutex_unlock (priv->lock);
 
-	if (error)
-		g_error_free (error);
+	if (my_err)
+		g_propagate_error (error, my_err);
 	g_object_unref (resolver);
 
 	return status;
 }
 
 /**
+ * soup_address_resolve_sync:
+ * @addr: a #SoupAddress
+ * @cancellable: a #GCancellable object, or %NULL
+ *
+ * Synchronously resolves the missing half of @addr, as with
+ * soup_address_resolve_async().
+ *
+ * If @cancellable is non-%NULL, it can be used to cancel the
+ * resolution. soup_address_resolve_sync() will then return a status
+ * of %SOUP_STATUS_CANCELLED.
+ *
+ * It is safe to call this more than once, even from different
+ * threads, but it is not safe to mix calls to
+ * soup_address_resolve_sync() with calls to
+ * soup_address_resolve_async() on the same address.
+ *
+ * Return value: %SOUP_STATUS_OK, %SOUP_STATUS_CANT_RESOLVE, or
+ * %SOUP_STATUS_CANCELLED.
+ **/
+guint
+soup_address_resolve_sync (SoupAddress *addr, GCancellable *cancellable)
+{
+	SoupAddressPrivate *priv;
+
+	g_return_val_if_fail (SOUP_IS_ADDRESS (addr), SOUP_STATUS_MALFORMED);
+	priv = SOUP_ADDRESS_GET_PRIVATE (addr);
+	g_return_val_if_fail (priv->name || priv->sockaddr, SOUP_STATUS_MALFORMED);
+
+	return resolve_sync_internal (addr, cancellable, NULL);
+}
+
+/**
  * soup_address_is_resolved:
  * @addr: a #SoupAddress
  *
@@ -934,7 +992,7 @@ soup_address_hash_by_ip (gconstpointer addr)
 	g_return_val_if_fail (priv->sockaddr != NULL, 0);
 
 	memcpy (&hash, SOUP_ADDRESS_GET_DATA (priv),
-		MIN (sizeof (hash), SOUP_ADDRESS_FAMILY_DATA_SIZE (priv->sockaddr->sa_family)));
+		MIN (sizeof (hash), SOUP_ADDRESS_FAMILY_DATA_SIZE (priv->sockaddr->ss_family)));
 	return hash;
 }
 
@@ -971,8 +1029,166 @@ soup_address_equal_by_ip (gconstpointer addr1, gconstpointer addr2)
 	g_return_val_if_fail (priv1->sockaddr != NULL, FALSE);
 	g_return_val_if_fail (priv2->sockaddr != NULL, FALSE);
 
-	size = SOUP_ADDRESS_FAMILY_SOCKADDR_SIZE (priv1->sockaddr->sa_family);
-	return (priv1->sockaddr->sa_family ==
-		priv2->sockaddr->sa_family &&
+	size = SOUP_ADDRESS_FAMILY_SOCKADDR_SIZE (priv1->sockaddr->ss_family);
+	return (priv1->sockaddr->ss_family ==
+		priv2->sockaddr->ss_family &&
 		!memcmp (priv1->sockaddr, priv2->sockaddr, size));
 }
+
+
+#define SOUP_TYPE_ADDRESS_ADDRESS_ENUMERATOR (_soup_address_address_enumerator_get_type ())
+#define SOUP_ADDRESS_ADDRESS_ENUMERATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_ADDRESS_ADDRESS_ENUMERATOR, SoupAddressAddressEnumerator))
+
+typedef struct {
+	GSocketAddressEnumerator parent_instance;
+
+	SoupAddress *addr;
+	int orig_offset;
+	int n;
+} SoupAddressAddressEnumerator;
+
+typedef struct {
+	GSocketAddressEnumeratorClass parent_class;
+
+} SoupAddressAddressEnumeratorClass;
+
+GType _soup_address_address_enumerator_get_type (void);
+G_DEFINE_TYPE (SoupAddressAddressEnumerator, _soup_address_address_enumerator, G_TYPE_SOCKET_ADDRESS_ENUMERATOR)
+
+static void
+soup_address_address_enumerator_finalize (GObject *object)
+{
+	SoupAddressAddressEnumerator *addr_enum =
+		SOUP_ADDRESS_ADDRESS_ENUMERATOR (object);
+
+	g_object_unref (addr_enum->addr);
+
+	G_OBJECT_CLASS (_soup_address_address_enumerator_parent_class)->finalize (object);
+}
+
+static GSocketAddress *
+next_address (SoupAddressAddressEnumerator *addr_enum)
+{
+	SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr_enum->addr);
+	struct sockaddr_storage *ss;
+	int next_addr;
+
+	/* If there are two addresses but the first one is unusable
+	 * (eg, it's IPv6 and we can only do IPv4), then we don't want to
+	 * try the bad one every time. So we use priv->offset to remember
+	 * the offset of the first usable address (ie, the first address
+	 * that we weren't called again after returning).
+	 */
+	next_addr = (addr_enum->orig_offset + addr_enum->n) % priv->n_addrs;
+	priv->offset = next_addr;
+
+	if (addr_enum->n >= priv->n_addrs)
+		return NULL;
+	addr_enum->n++;
+
+	ss = &priv->sockaddr[next_addr];
+	return g_socket_address_new_from_native (ss, SOUP_ADDRESS_FAMILY_SOCKADDR_SIZE (ss->ss_family));
+}
+
+static GSocketAddress *
+soup_address_address_enumerator_next (GSocketAddressEnumerator  *enumerator,
+				      GCancellable              *cancellable,
+				      GError                   **error)
+{
+	SoupAddressAddressEnumerator *addr_enum =
+		SOUP_ADDRESS_ADDRESS_ENUMERATOR (enumerator);
+	SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr_enum->addr);
+
+	if (!priv->sockaddr) {
+		if (resolve_sync_internal (addr_enum->addr, cancellable, error) != SOUP_STATUS_OK)
+			return NULL;
+	}
+
+	return next_address (addr_enum);
+}
+
+static void
+got_addresses (SoupAddress *addr, guint status, gpointer user_data)
+{
+	GSimpleAsyncResult *simple = user_data;
+	GError *error;
+
+	error = g_object_get_data (G_OBJECT (addr), "async-resolved-error");
+	if (error)
+		g_simple_async_result_set_from_error (simple, error);
+
+	g_simple_async_result_complete (simple);
+	g_object_unref (simple);
+}
+
+static void
+soup_address_address_enumerator_next_async (GSocketAddressEnumerator  *enumerator,
+					    GCancellable              *cancellable,
+					    GAsyncReadyCallback        callback,
+					    gpointer                   user_data)
+{
+	SoupAddressAddressEnumerator *addr_enum =
+		SOUP_ADDRESS_ADDRESS_ENUMERATOR (enumerator);
+	SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr_enum->addr);
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (G_OBJECT (enumerator),
+					    callback, user_data,
+					    soup_address_address_enumerator_next_async);
+
+	if (!priv->sockaddr) {
+		soup_address_resolve_async (addr_enum->addr, NULL, cancellable,
+					    got_addresses, simple);
+	} else {
+		g_simple_async_result_complete_in_idle (simple);
+		g_object_unref (simple);
+	}
+}
+
+static GSocketAddress *
+soup_address_address_enumerator_next_finish (GSocketAddressEnumerator  *enumerator,
+					     GAsyncResult              *result,
+					     GError                   **error)
+{
+	SoupAddressAddressEnumerator *addr_enum =
+		SOUP_ADDRESS_ADDRESS_ENUMERATOR (enumerator);
+	GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return NULL;
+	else 
+		return next_address (addr_enum);
+}
+
+static void
+_soup_address_address_enumerator_init (SoupAddressAddressEnumerator *enumerator)
+{
+}
+
+static void
+_soup_address_address_enumerator_class_init (SoupAddressAddressEnumeratorClass *addrenum_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (addrenum_class);
+	GSocketAddressEnumeratorClass *enumerator_class =
+		G_SOCKET_ADDRESS_ENUMERATOR_CLASS (addrenum_class);
+
+	enumerator_class->next = soup_address_address_enumerator_next;
+	enumerator_class->next_async = soup_address_address_enumerator_next_async;
+	enumerator_class->next_finish = soup_address_address_enumerator_next_finish;
+	object_class->finalize = soup_address_address_enumerator_finalize;
+}
+
+static GSocketAddressEnumerator *
+soup_address_connectable_enumerate (GSocketConnectable *connectable)
+{
+	SoupAddressAddressEnumerator *addr_enum;
+	SoupAddressPrivate *priv;
+
+	addr_enum = g_object_new (SOUP_TYPE_ADDRESS_ADDRESS_ENUMERATOR, NULL);
+	addr_enum->addr = g_object_ref (connectable);
+
+	priv = SOUP_ADDRESS_GET_PRIVATE (addr_enum->addr);
+	addr_enum->orig_offset = priv->offset;
+
+	return (GSocketAddressEnumerator *)addr_enum;
+}
diff --git a/libsoup/soup-address.h b/libsoup/soup-address.h
index b73b4ce..15c66fd 100644
--- a/libsoup/soup-address.h
+++ b/libsoup/soup-address.h
@@ -78,6 +78,7 @@ const char      *soup_address_get_physical       (SoupAddress         *addr);
 guint            soup_address_get_port           (SoupAddress         *addr);
 struct sockaddr *soup_address_get_sockaddr       (SoupAddress         *addr,
 						  int                 *len);
+GSocketAddress  *soup_address_get_gsockaddr      (SoupAddress         *addr);
 gboolean         soup_address_is_resolved        (SoupAddress         *addr);
 
 guint            soup_address_hash_by_name       (gconstpointer        addr);
diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c
index 7ebe5bc..c9c2b92 100644
--- a/libsoup/soup-socket.c
+++ b/libsoup/soup-socket.c
@@ -35,18 +35,7 @@
  * soup_socket_get_remote_address()) may be useful to applications.
  **/
 
-static void
-_soup_networking_init (void)
-{
-#ifdef G_OS_WIN32
-  WSADATA wsadata;
-  if (WSAStartup (MAKEWORD (2, 0), &wsadata) != 0)
-    g_error ("Windows Sockets could not be initialized");
-#endif
-}
-
-G_DEFINE_TYPE_WITH_CODE (SoupSocket, soup_socket, G_TYPE_OBJECT,
-			 _soup_networking_init ();)
+G_DEFINE_TYPE (SoupSocket, soup_socket, G_TYPE_OBJECT)
 
 enum {
 	READABLE,
@@ -79,6 +68,7 @@ typedef struct {
 	int sockfd;
 	SoupAddress *local_addr, *remote_addr;
 	GIOChannel *iochannel;
+	GSocketConnection *conn;
 
 	guint non_blocking:1;
 	guint is_server:1;
@@ -92,7 +82,6 @@ typedef struct {
 	GSource        *watch_src;
 	GSource        *read_src, *write_src;
 	GSource        *read_timeout, *write_timeout;
-	GSource        *connect_timeout;
 	GByteArray     *read_buf;
 
 	GMutex *iolock, *addrlock;
@@ -110,12 +99,10 @@ static void get_property (GObject *object, guint prop_id,
 #ifdef G_OS_WIN32
 #define SOUP_IS_SOCKET_ERROR(status) ((status) == SOCKET_ERROR)
 #define SOUP_IS_INVALID_SOCKET(socket) ((socket) == INVALID_SOCKET)
-#define SOUP_IS_CONNECT_STATUS_INPROGRESS() (WSAGetLastError () == WSAEWOULDBLOCK)
 #define SHUT_RDWR SD_BOTH
 #else
 #define SOUP_IS_SOCKET_ERROR(status) ((status) == -1)
 #define SOUP_IS_INVALID_SOCKET(socket) ((socket) < 0)
-#define SOUP_IS_CONNECT_STATUS_INPROGRESS() (errno == EINPROGRESS)
 #endif
 
 static void
@@ -137,6 +124,10 @@ disconnect_internal (SoupSocketPrivate *priv)
 		g_io_channel_unref (priv->iochannel);
 		priv->iochannel = NULL;
 	}
+	if (priv->conn) {
+		g_object_unref (priv->conn);
+		priv->conn = NULL;
+	}
 	priv->sockfd = -1;
 
 	if (priv->read_src) {
@@ -183,11 +174,6 @@ finalize (GObject *object)
 			g_warning ("Disposing socket %p during async op", object);
 		g_source_destroy (priv->watch_src);
 	}
-	if (priv->connect_timeout) {
-		if (priv->clean_dispose)
-			g_warning ("Disposing socket %p during connect (2)", object);
-		g_source_destroy (priv->connect_timeout);
-	}
 	if (priv->async_context)
 		g_main_context_unref (priv->async_context);
 
@@ -205,10 +191,6 @@ soup_socket_class_init (SoupSocketClass *socket_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (socket_class);
 
-#ifdef SIGPIPE
-	signal (SIGPIPE, SIG_IGN);
-#endif
-
 	g_type_class_add_private (socket_class, sizeof (SoupSocketPrivate));
 
 	/* virtual method override */
@@ -526,7 +508,7 @@ set_fdflags (SoupSocketPrivate *priv)
 	priv->iochannel =
 		g_io_channel_win32_new_socket (priv->sockfd);
 #endif
-	g_io_channel_set_close_on_unref (priv->iochannel, TRUE);
+	g_io_channel_set_close_on_unref (priv->iochannel, priv->conn == NULL);
 	g_io_channel_set_encoding (priv->iochannel, NULL, NULL);
 	g_io_channel_set_buffered (priv->iochannel, FALSE);
 }
@@ -638,150 +620,35 @@ soup_socket_new (const char *optname1, ...)
 	return sock;
 }
 
-typedef struct {
-	SoupSocket *sock;
-	GCancellable *cancellable;
-	guint cancel_id;
-	SoupSocketCallback callback;
-	gpointer user_data;
-} SoupSocketAsyncConnectData;
-
-static void
-free_sacd (SoupSocketAsyncConnectData *sacd)
-{
-	if (sacd->cancel_id)
-		g_signal_handler_disconnect (sacd->cancellable, sacd->cancel_id);
-	g_object_unref (sacd->cancellable);
-	g_object_unref (sacd->sock);
-	g_slice_free (SoupSocketAsyncConnectData, sacd);
-}
-
-static gboolean
-idle_connect_result (gpointer user_data)
+static guint
+socket_connected (SoupSocket *sock, GSocketConnection *conn, GError *error)
 {
-	SoupSocketAsyncConnectData *sacd = user_data;
-	SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock);
-	guint status;
+	SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+	GSocket *gsock;
 
+	g_object_unref (priv->connect_cancel);
 	priv->connect_cancel = NULL;
-	priv->watch_src = NULL;
-
-	if (priv->sockfd == -1) {
-		if (g_cancellable_is_cancelled (sacd->cancellable))
-			status = SOUP_STATUS_CANCELLED;
-		else
-			status = SOUP_STATUS_CANT_CONNECT;
-	} else
-		status = SOUP_STATUS_OK;
-
-	/* Have to do this before calling the callback... */
-	if (sacd->cancel_id) {
-		g_signal_handler_disconnect (sacd->cancellable, sacd->cancel_id);
-		sacd->cancel_id = 0;
-	}
-
-	sacd->callback (sacd->sock, status, sacd->user_data);
-	free_sacd (sacd);
-	return FALSE;
-}
 
-static gboolean
-connect_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data)
-{
-	SoupSocketAsyncConnectData *sacd = data;
-	SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock);
-	int error = 0;
-	int len = sizeof (error);
-
-	/* Remove the watch now in case we don't return immediately */
-	g_source_destroy (priv->watch_src);
-	priv->watch_src = NULL;
-	if (priv->connect_timeout) {
-		g_source_destroy (priv->connect_timeout);
-		priv->connect_timeout = NULL;
+	if (error) {
+		if (error->domain == G_RESOLVER_ERROR) {
+			g_error_free (error);
+			return SOUP_STATUS_CANT_RESOLVE;
+		} else {
+			g_error_free (error);
+			return SOUP_STATUS_CANT_CONNECT;
+		}
 	}
 
-	if ((condition & ~(G_IO_IN | G_IO_OUT)) ||
-	    (getsockopt (priv->sockfd, SOL_SOCKET, SO_ERROR,
-			 (void *)&error, (void *)&len) != 0) ||
-	    error)
-		disconnect_internal (priv);
-
-	return idle_connect_result (sacd);
-}
-
-static gboolean
-connect_timeout (gpointer data)
-{
-	SoupSocketAsyncConnectData *sacd = data;
-	SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock);
-
-	/* Remove the watch now in case we don't return immediately */
-	g_source_destroy (priv->watch_src);
-	priv->watch_src = NULL;
-	g_source_destroy (priv->connect_timeout);
-	priv->connect_timeout = NULL;
-
-	disconnect_internal (priv);
-	return idle_connect_result (sacd);
-}
-
-static void
-got_address (SoupAddress *addr, guint status, gpointer user_data)
-{
-	SoupSocketAsyncConnectData *sacd = user_data;
-	SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock);
-
-	priv->connect_cancel = NULL;
-
-	if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
-		soup_socket_connect_async (sacd->sock, sacd->cancellable,
-					   sacd->callback, sacd->user_data);
-	} else
-		sacd->callback (sacd->sock, status, sacd->user_data);
-
-	free_sacd (sacd);
-}
-
-static void
-async_cancel (GCancellable *cancellable, gpointer user_data)
-{
-	SoupSocketAsyncConnectData *sacd = user_data;
-	SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock);
-
-	if (priv->watch_src)
-		g_source_destroy (priv->watch_src);
-	disconnect_internal (priv);
-	priv->watch_src = soup_add_completion (priv->async_context,
-					       idle_connect_result, sacd);
-}
-
-static guint
-socket_connect_internal (SoupSocket *sock)
-{
-	SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
-	struct sockaddr *sa;
-	int len, status;
-
-	sa = soup_address_get_sockaddr (priv->remote_addr, &len);
-	if (!sa)
-		return SOUP_STATUS_CANT_RESOLVE;
+	/* We keep the GSocketConnection around because its GSocket
+	 * will close the fd when it's destroyed.
+	 */
+	priv->conn = conn;
 
-	priv->sockfd = socket (sa->sa_family, SOCK_STREAM, 0);
-	if (SOUP_IS_INVALID_SOCKET (priv->sockfd))
-		return SOUP_STATUS_CANT_CONNECT;
+	gsock = g_socket_connection_get_socket (conn);
+	priv->sockfd = g_socket_get_fd (gsock);
 	set_fdflags (priv);
 
-	status = connect (priv->sockfd, sa, len);
-
-	if (SOUP_IS_SOCKET_ERROR (status)) {
-		if (SOUP_IS_CONNECT_STATUS_INPROGRESS ())
-			return SOUP_STATUS_CONTINUE;
-
-		disconnect_internal (priv);
-		return SOUP_STATUS_CANT_CONNECT;
-	} else
-		return SOUP_STATUS_OK;
+	return SOUP_STATUS_OK;
 }
 
 /**
@@ -793,6 +660,29 @@ socket_connect_internal (SoupSocket *sock)
  * The callback function passed to soup_socket_connect_async().
  **/
 
+typedef struct {
+	SoupSocket *sock;
+	SoupSocketCallback callback;
+	gpointer user_data;
+} SoupSocketAsyncConnectData;
+
+static void
+async_connected (GObject *client, GAsyncResult *result, gpointer data)
+{
+	SoupSocketAsyncConnectData *sacd = data;
+	GError *error = NULL;
+	GSocketConnection *conn;
+	guint status;
+
+	conn = g_socket_client_connect_finish (G_SOCKET_CLIENT (client),
+					       result, &error);
+	status = socket_connected (sacd->sock, conn, error);
+
+	sacd->callback (sacd->sock, status, sacd->user_data);
+	g_object_unref (sacd->sock);
+	g_slice_free (SoupSocketAsyncConnectData, sacd);
+}
+
 /**
  * soup_socket_connect_async:
  * @sock: a client #SoupSocket (which must not already be connected)
@@ -814,7 +704,7 @@ soup_socket_connect_async (SoupSocket *sock, GCancellable *cancellable,
 {
 	SoupSocketPrivate *priv;
 	SoupSocketAsyncConnectData *sacd;
-	guint status;
+	GSocketClient *client;
 
 	g_return_if_fail (SOUP_IS_SOCKET (sock));
 	priv = SOUP_SOCKET_GET_PRIVATE (sock);
@@ -822,58 +712,31 @@ soup_socket_connect_async (SoupSocket *sock, GCancellable *cancellable,
 
 	sacd = g_slice_new0 (SoupSocketAsyncConnectData);
 	sacd->sock = g_object_ref (sock);
-	sacd->cancellable = cancellable ? g_object_ref (cancellable) : g_cancellable_new ();
 	sacd->callback = callback;
 	sacd->user_data = user_data;
 
-	priv->connect_cancel = sacd->cancellable;
-
-	if (g_cancellable_is_cancelled (priv->connect_cancel)) {
-		priv->watch_src = soup_add_completion (priv->async_context,
-						       idle_connect_result, sacd);
-		return;
-	}
+	priv->connect_cancel = cancellable ? g_object_ref (cancellable) : g_cancellable_new ();
 
-	if (!soup_address_get_sockaddr (priv->remote_addr, NULL)) {
-		soup_address_resolve_async (priv->remote_addr,
-					    priv->async_context,
-					    sacd->cancellable,
-					    got_address, sacd);
-		return;
-	}
+	if (priv->async_context)
+		g_main_context_push_thread_default (priv->async_context);
 
-	status = socket_connect_internal (sock);
-	if (status == SOUP_STATUS_CONTINUE) {
-		/* Wait for connect to succeed or fail */
-		priv->watch_src =
-			soup_add_io_watch (priv->async_context,
-					   priv->iochannel,
-					   G_IO_IN | G_IO_OUT |
-					   G_IO_PRI | G_IO_ERR |
-					   G_IO_HUP | G_IO_NVAL,
-					   connect_watch, sacd);
-		if (priv->timeout) {
-			priv->connect_timeout =
-				soup_add_timeout (priv->async_context,
-						  priv->timeout * 1000,
-						  connect_timeout, sacd);
+	client = g_socket_client_new ();
+	if (priv->timeout) {
+		/* FIXME: temporary workaround for not-new-enough glib */
+		if (g_object_class_find_property (G_OBJECT_GET_CLASS (client), "timeout")) {
+			g_object_set (G_OBJECT (client),
+				      "timeout", priv->timeout,
+				      NULL);
 		}
-		sacd->cancel_id =
-			g_signal_connect (sacd->cancellable, "cancelled",
-					  G_CALLBACK (async_cancel),
-					  sacd);
-	} else {
-		priv->watch_src = soup_add_completion (priv->async_context,
-						       idle_connect_result, sacd);
 	}
-}
+	g_socket_client_connect_async (client,
+				       G_SOCKET_CONNECTABLE (priv->remote_addr),
+				       priv->connect_cancel,
+				       async_connected, sacd);
+	g_object_unref (client);
 
-static void
-sync_cancel (GCancellable *cancellable, gpointer sock)
-{
-	SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
-
-	shutdown (priv->sockfd, SHUT_RDWR);
+	if (priv->async_context)
+		g_main_context_pop_thread_default (priv->async_context);
 }
 
 /**
@@ -893,7 +756,9 @@ guint
 soup_socket_connect_sync (SoupSocket *sock, GCancellable *cancellable)
 {
 	SoupSocketPrivate *priv;
-	guint status, cancel_id;
+	GSocketClient *client;
+	GSocketConnection *conn;
+	GError *error = NULL;
 
 	g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_STATUS_MALFORMED);
 	priv = SOUP_SOCKET_GET_PRIVATE (sock);
@@ -907,31 +772,21 @@ soup_socket_connect_sync (SoupSocket *sock, GCancellable *cancellable)
 		cancellable = g_cancellable_new ();
 	priv->connect_cancel = cancellable;
 
-	if (!soup_address_get_sockaddr (priv->remote_addr, NULL)) {
-		status = soup_address_resolve_sync (priv->remote_addr,
-						    cancellable);
-		if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
-			priv->connect_cancel = NULL;
-			g_object_unref (cancellable);
-			return status;
+	client = g_socket_client_new ();
+	if (priv->timeout) {
+		/* FIXME: temporary workaround for not-new-enough glib */
+		if (g_object_class_find_property (G_OBJECT_GET_CLASS (client), "timeout")) {
+			g_object_set (G_OBJECT (client),
+				      "timeout", priv->timeout,
+				      NULL);
 		}
 	}
+	conn = g_socket_client_connect (client,
+					G_SOCKET_CONNECTABLE (priv->remote_addr),
+					priv->connect_cancel, &error);
+	g_object_unref (client);
 
-	cancel_id = g_signal_connect (cancellable, "cancelled",
-				      G_CALLBACK (sync_cancel), sock);
-
-	status = socket_connect_internal (sock);
-	priv->connect_cancel = NULL;
-
-	if (status != SOUP_STATUS_OK &&
-	    g_cancellable_is_cancelled (cancellable)) {
-		status = SOUP_STATUS_CANCELLED;
-		disconnect_internal (priv);
-	}
-	g_signal_handler_disconnect (cancellable, cancel_id);
-	g_object_unref (cancellable);
-
-	return status;
+	return socket_connected (sock, conn, error);
 }
 
 int
@@ -951,7 +806,6 @@ listen_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data)
 	int sa_len, sockfd;
 
 	if (condition & (G_IO_HUP | G_IO_ERR)) {
-		g_source_destroy (priv->watch_src);
 		priv->watch_src = NULL;
 		return FALSE;
 	}
@@ -1001,8 +855,9 @@ soup_socket_listen (SoupSocket *sock)
 
 {
 	SoupSocketPrivate *priv;
-	struct sockaddr *sa;
+	struct sockaddr_storage sa;
 	int sa_len;
+	GSocketAddress *addr;
 
 	g_return_val_if_fail (SOUP_IS_SOCKET (sock), FALSE);
 	priv = SOUP_SOCKET_GET_PRIVATE (sock);
@@ -1017,16 +872,20 @@ soup_socket_listen (SoupSocket *sock)
 	 * have to make a new addr by calling getsockname(), which
 	 * will have the right port number.
 	 */
-	sa = soup_address_get_sockaddr (priv->local_addr, &sa_len);
-	g_return_val_if_fail (sa != NULL, FALSE);
+	addr = soup_address_get_gsockaddr (priv->local_addr);
+	g_return_val_if_fail (addr != NULL, FALSE);
+
+	sa_len = g_socket_address_get_native_size (addr);
+	g_socket_address_to_native (addr, &sa, sizeof (sa), NULL);
+	g_object_unref (addr);
 
-	priv->sockfd = socket (sa->sa_family, SOCK_STREAM, 0);
+	priv->sockfd = socket (sa.ss_family, SOCK_STREAM, 0);
 	if (SOUP_IS_INVALID_SOCKET (priv->sockfd))
 		goto cant_listen;
 	set_fdflags (priv);
 
 	/* Bind */
-	if (bind (priv->sockfd, sa, sa_len) != 0)
+	if (bind (priv->sockfd, (struct sockaddr *)&sa, sa_len) != 0)
 		goto cant_listen;
 	/* Force local_addr to be re-resolved now */
 	g_object_unref (priv->local_addr);
diff --git a/tests/libsoup.supp b/tests/libsoup.supp
index 65eb3c3..cf144a0 100644
--- a/tests/libsoup.supp
+++ b/tests/libsoup.supp
@@ -83,7 +83,6 @@
    Memcheck:Leak
    ...
    fun:get_dispatch
-   fun:g_main_context_dispatch
 }
 {
    glib/g_signal_handlers_destroy
@@ -127,6 +126,18 @@
    ...
    fun:g_main_context_push_thread_default
 }
+{
+   glib/g_socket_connection_factory
+   Memcheck:Leak
+   ...
+   fun:g_socket_connection_factory_register_type
+}
+{
+   glib/g_get_language_names
+   Memcheck:Leak
+   ...
+   fun:g_get_language_names
+}
 
 # probably using uninitialized memory as padding or something
 {



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