Tizen 2.1 base
[platform/upstream/glib2.0.git] / gio / gnetworkaddress.c
index 16af7f5..cbb272d 100644 (file)
@@ -30,6 +30,7 @@
 #include "ginetaddress.h"
 #include "ginetsocketaddress.h"
 #include "gnetworkingprivate.h"
+#include "gproxyaddressenumerator.h"
 #include "gresolver.h"
 #include "gsimpleasyncresult.h"
 #include "gsocketaddressenumerator.h"
@@ -38,7 +39,6 @@
 
 #include <string.h>
 
-#include "gioalias.h"
 
 /**
  * SECTION:gnetworkaddress
@@ -64,12 +64,14 @@ struct _GNetworkAddressPrivate {
   gchar *hostname;
   guint16 port;
   GList *sockaddrs;
+  gchar *scheme;
 };
 
 enum {
   PROP_0,
   PROP_HOSTNAME,
   PROP_PORT,
+  PROP_SCHEME,
 };
 
 static void g_network_address_set_property (GObject      *object,
@@ -81,8 +83,9 @@ 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_IMPLEMENT_INTERFACE (G_TYPE_SOCKET_CONNECTABLE,
@@ -94,6 +97,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)
     {
@@ -134,12 +138,22 @@ 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
@@ -160,8 +174,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 +182,12 @@ g_network_address_set_property (GObject      *object,
       addr->priv->port = g_value_get_uint (value);
       break;
 
+    case PROP_SCHEME:
+      if (addr->priv->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 +213,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;
@@ -228,7 +251,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
+ * Return value: (transfer full) (type GNetworkAddress): the new #GNetworkAddress
  *
  * Since: 2.22
  */
@@ -252,12 +275,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 +288,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.)
+ *
+ * Return value: (transfer full): the new #GNetworkAddress, or %NULL on error
  *
  * Since: 2.22
  */
@@ -398,6 +425,304 @@ 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;
+  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;
+
+  if (strchr (start, '@') != 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 == '[')
+    {
+      start++;
+      p++;
+      while (1)
+       {
+         c = *p++;
+
+         if (c == ']')
+           break;
+
+         /* 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;
+       }
+    }
+  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 pot:
+       *  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.
+ *
+ * Return value: (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
@@ -435,6 +760,24 @@ g_network_address_get_port (GNetworkAddress *addr)
   return addr->priv->port;
 }
 
+/**
+ * g_network_address_get_scheme:
+ * @addr: a #GNetworkAddress
+ *
+ * Gets @addr's scheme
+ *
+ * Return value: @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 +785,8 @@ typedef struct {
   GSocketAddressEnumerator parent_instance;
 
   GNetworkAddress *addr;
-  GList *a;
+  GList *addresses;
+  GList *next;
 } GNetworkAddressAddressEnumerator;
 
 typedef struct {
@@ -450,6 +794,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 +817,34 @@ 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)
     {
-      GResolver *resolver = g_resolver_get_default ();
-      GList *addresses;
+      if (!addr_enum->addr->priv->sockaddrs)
+        {
+          GResolver *resolver = g_resolver_get_default ();
+          GList *addresses;
 
-      addresses = g_resolver_lookup_by_name (resolver,
-                                             addr_enum->addr->priv->hostname,
-                                             cancellable, error);
-      g_object_unref (resolver);
+          addresses = g_resolver_lookup_by_name (resolver,
+                                                 addr_enum->addr->priv->hostname,
+                                                 cancellable, error);
+          g_object_unref (resolver);
 
-      if (!addresses)
-        return NULL;
+          if (!addresses)
+            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_enum->addr, addresses);
+        }
+          
+      addr_enum->addresses = addr_enum->addr->priv->sockaddrs;
+      addr_enum->next = addr_enum->addresses;
     }
 
-  if (!addr_enum->a)
+  if (addr_enum->next == NULL)
     return NULL;
-  else
-    {
-      sockaddr = addr_enum->a->data;
-      addr_enum->a = addr_enum->a->next;
-      return g_object_ref (sockaddr);
-    }
+
+  sockaddr = addr_enum->next->data;
+  addr_enum->next = addr_enum->next->next;
+  return g_object_ref (sockaddr);
 }
 
 static void
@@ -511,25 +859,21 @@ got_addresses (GObject      *source_object,
   GList *addresses;
   GError *error = NULL;
 
-  addresses = g_resolver_lookup_by_name_finish (resolver, result, &error);
   if (!addr_enum->addr->priv->sockaddrs)
     {
+      addresses = g_resolver_lookup_by_name_finish (resolver, result, &error);
+
       if (error)
-        {
-          g_simple_async_result_set_from_error (simple, error);
-          g_error_free (error);
-        }
+        g_simple_async_result_take_error (simple, error);
       else
-        {
-          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);
     }
-  else if (error)
-    g_error_free (error);
 
   g_object_unref (resolver);
 
+  addr_enum->addresses = addr_enum->addr->priv->sockaddrs;
+  addr_enum->next = addr_enum->addresses;
+
   g_simple_async_result_complete (simple);
   g_object_unref (simple);
 }
@@ -548,21 +892,26 @@ g_network_address_address_enumerator_next_async (GSocketAddressEnumerator  *enum
                                       callback, user_data,
                                       g_network_address_address_enumerator_next_async);
 
-  if (!addr_enum->addr->priv->sockaddrs)
+  if (addr_enum->addresses == NULL)
     {
-      GResolver *resolver = g_resolver_get_default ();
+      if (!addr_enum->addr->priv->sockaddrs)
+        {
+          GResolver *resolver = g_resolver_get_default ();
+
+          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);
+          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);
-    }
-  else
-    {
-      g_simple_async_result_complete_in_idle (simple);
-      g_object_unref (simple);
+      addr_enum->addresses = addr_enum->addr->priv->sockaddrs;
+      addr_enum->next = addr_enum->addresses;
     }
+
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
 }
 
 static GSocketAddress *
@@ -577,12 +926,12 @@ g_network_address_address_enumerator_next_finish (GSocketAddressEnumerator  *enu
 
   if (g_simple_async_result_propagate_error (simple, error))
     return NULL;
-  else if (!addr_enum->a)
+  else if (!addr_enum->next)
     return NULL;
   else
     {
-      sockaddr = addr_enum->a->data;
-      addr_enum->a = addr_enum->a->next;
+      sockaddr = addr_enum->next->data;
+      addr_enum->next = addr_enum->next->next;
       return g_object_ref (sockaddr);
     }
 }
@@ -616,5 +965,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;
+}