cleanup
[platform/upstream/glib.git] / gio / gnetworkaddress.c
index 77a82ee..8e073fd 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
  */
 
 #include "config.h"
@@ -30,8 +28,9 @@
 #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"
@@ -64,6 +63,8 @@ struct _GNetworkAddressPrivate {
   guint16 port;
   GList *sockaddrs;
   gchar *scheme;
+
+  gint64 resolver_serial;
 };
 
 enum {
@@ -82,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))
 
@@ -114,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;
@@ -151,13 +152,13 @@ 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
@@ -180,8 +181,7 @@ g_network_address_set_property (GObject      *object,
       break;
 
     case PROP_SCHEME:
-      if (addr->priv->scheme)
-        g_free (addr->priv->scheme);
+      g_free (addr->priv->scheme);
       addr->priv->scheme = g_value_dup_string (value);
       break;
 
@@ -223,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;
@@ -238,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;
 }
 
 /**
@@ -248,7 +267,13 @@ g_network_address_set_addresses (GNetworkAddress *addr,
  * Creates a new #GSocketConnectable for connecting to the given
  * @hostname and @port.
  *
- * Return value: the new #GNetworkAddress
+ * Note that depending on the configuration of the machine, a
+ * @hostname of `localhost` may refer to the IPv4 loopback address
+ * only, or to both IPv4 and IPv6; use
+ * g_network_address_new_loopback() to create a #GNetworkAddress that
+ * is guaranteed to resolve to both addresses.
+ *
+ * Returns: (transfer full) (type GNetworkAddress): the new #GNetworkAddress
  *
  * Since: 2.22
  */
@@ -263,6 +288,45 @@ g_network_address_new (const gchar *hostname,
 }
 
 /**
+ * g_network_address_new_loopback:
+ * @port: the port
+ *
+ * Creates a new #GSocketConnectable for connecting to the local host
+ * over a loopback connection to the given @port. This is intended for
+ * use in connecting to local services which may be running on IPv4 or
+ * IPv6.
+ *
+ * The connectable will return IPv4 and IPv6 loopback addresses,
+ * regardless of how the host resolves `localhost`. By contrast,
+ * g_network_address_new() will often only return an IPv4 address when
+ * resolving `localhost`, and an IPv6 address for `localhost6`.
+ *
+ * g_network_address_get_hostname() will always return `localhost` for
+ * #GNetworkAddresses created with this constructor.
+ *
+ * Returns: (transfer full) (type GNetworkAddress): the new #GNetworkAddress
+ *
+ * Since: 2.44
+ */
+GSocketConnectable *
+g_network_address_new_loopback (guint16 port)
+{
+  GNetworkAddress *addr;
+  GList *addrs = NULL;
+
+  addr = g_object_new (G_TYPE_NETWORK_ADDRESS,
+                       "hostname", "localhost",
+                       "port", port,
+                       NULL);
+
+  addrs = g_list_append (addrs, g_inet_address_new_loopback (AF_INET6));
+  addrs = g_list_append (addrs, g_inet_address_new_loopback (AF_INET));
+  g_network_address_set_addresses (addr, addrs, 0);
+
+  return G_SOCKET_CONNECTABLE (addr);
+}
+
+/**
  * g_network_address_parse:
  * @host_and_port: the hostname and optionally a port
  * @default_port: the default port if not in @host_and_port
@@ -272,12 +336,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.
@@ -286,7 +349,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
  */
@@ -422,14 +490,14 @@ g_network_address_parse (const gchar  *host_and_port,
 #define G_URI_OTHER_UNRESERVED "-._~"
 
 /* This or something equivalent will eventually go into glib/guri.h */
-static gboolean
+gboolean
 _g_uri_parse_authority (const char  *uri,
                        char       **host,
                        guint16     *port,
                        char       **userinfo)
 {
   char *tmp_str;
-  const char *start, *p;
+  const char *start, *p, *at, *delim;
   char c;
 
   g_return_val_if_fail (uri != NULL, FALSE);
@@ -468,15 +536,22 @@ _g_uri_parse_authority (const char  *uri,
     return FALSE;
 
   start += 2;
-  p = strchr (start, '@');
 
-  if (p != NULL)
+  /* 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++;
@@ -497,7 +572,7 @@ _g_uri_parse_authority (const char  *uri,
            }
 
          /* unreserved /  sub-delims / : */
-         if (!(g_ascii_isalnum(c) ||
+         if (!(g_ascii_isalnum (c) ||
                strchr (G_URI_OTHER_UNRESERVED, c) ||
                strchr (G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, c) ||
                c == ':'))
@@ -523,6 +598,10 @@ _g_uri_parse_authority (const char  *uri,
   /* If IPv6 or IPvFuture */
   if (*p == '[')
     {
+      gboolean has_scope_id = FALSE, has_bad_scope_id = FALSE;
+
+      start++;
+      p++;
       while (1)
        {
          c = *p++;
@@ -530,14 +609,32 @@ _g_uri_parse_authority (const char  *uri,
          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) ||
+         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
     {
@@ -565,19 +662,19 @@ _g_uri_parse_authority (const char  *uri,
            }
 
          /* unreserved /  sub-delims */
-         if (!(g_ascii_isalnum(c) ||
+         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 (host)
+        *host = g_uri_unescape_segment (start, p - 1, NULL);
+    }
 
   if (c == ':')
     {
-      /* Decode pot:
+      /* Decode port:
        *  port          = *DIGIT
        */
       guint tmp = 0;
@@ -622,6 +719,46 @@ error:
   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
@@ -632,10 +769,10 @@ error:
  * @uri. May fail and return %NULL in case parsing @uri fails.
  *
  * Using this rather than g_network_address_new() or
- * g_network_address_parse_host() allows #GSocketClient to determine
+ * g_network_address_parse() allows #GSocketClient to determine
  * when to use application-specific proxy protocols.
  *
- * Return value: the new #GNetworkAddress, or %NULL on error
+ * Returns: (transfer full): the new #GNetworkAddress, or %NULL on error
  *
  * Since: 2.26
  */
@@ -681,7 +818,7 @@ g_network_address_parse_uri (const gchar  *uri,
  * 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
  */
@@ -699,7 +836,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
  */
@@ -717,7 +854,7 @@ g_network_address_get_port (GNetworkAddress *addr)
  *
  * Gets @addr's scheme
  *
- * Return value: @addr's scheme (%NULL if not built from URI)
+ * Returns: @addr's scheme (%NULL if not built from URI)
  *
  * Since: 2.26
  */
@@ -736,7 +873,8 @@ typedef struct {
   GSocketAddressEnumerator parent_instance;
 
   GNetworkAddress *addr;
-  GList *a;
+  GList *addresses;
+  GList *next;
 } GNetworkAddressAddressEnumerator;
 
 typedef struct {
@@ -744,6 +882,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
@@ -766,31 +905,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 (!addr->priv->sockaddrs)
+        g_network_address_parse_sockaddr (addr);
+      if (!addr->priv->sockaddrs)
+        {
+          GList *addresses;
 
-      if (!addresses)
-        return NULL;
+          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_enum->addr, addresses);
-      addr_enum->a = addr_enum->addr->priv->sockaddrs;
+          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
@@ -798,34 +979,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
@@ -836,27 +1006,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);
+
+      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;
+        }
 
-      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);
+      addr_enum->addresses = addr->priv->sockaddrs;
+      addr_enum->next = addr_enum->addresses;
+      g_object_unref (resolver);
     }
-  else
+
+  if (addr_enum->next)
     {
-      g_simple_async_result_complete_in_idle (simple);
-      g_object_unref (simple);
+      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 *
@@ -864,21 +1062,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
@@ -909,3 +1095,25 @@ g_network_address_connectable_enumerate (GSocketConnectable *connectable)
 
   return (GSocketAddressEnumerator *)addr_enum;
 }
+
+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;
+}