From 128c413261f60c044aca14895ca2c5d2574d791e Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Sun, 4 Oct 2015 15:24:24 +0100 Subject: [PATCH] gsocketconnectable: Add a to_string() virtual method Add string serialisation functions for GNetworkAddress, GSocketAddress, GUnixSocketAddress, GInetSocketAddress, GNetworkService and GSocketConnectable. These are intended for use in debug output, not for serialisation in network or disc protocols. They are implemented as a new virtual method on GSocketConnectable: g_socket_connectable_to_string(). GInetSocketAddress and GUnixSocketAddress now implement GSocketConnectable directly to implement to_string(). Previously they implemented it via their abstract parent class, GSocketAddress. https://bugzilla.gnome.org/show_bug.cgi?id=737116 --- docs/reference/gio/gio-sections.txt | 1 + gio/ginetsocketaddress.c | 62 ++++++++++++++++++++++++++++- gio/gnetworkaddress.c | 26 ++++++++++++ gio/gnetworkservice.c | 14 +++++++ gio/gsocketaddress.c | 1 + gio/gsocketconnectable.c | 31 +++++++++++++++ gio/gsocketconnectable.h | 6 +++ gio/gunixsocketaddress.c | 52 +++++++++++++++++++++++- gio/tests/inet-address.c | 50 +++++++++++++++++++++++ gio/tests/network-address.c | 38 ++++++++++++++++++ gio/tests/socket-address.c | 40 +++++++++++++++++++ 11 files changed, 319 insertions(+), 2 deletions(-) diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index f0a4c4960..9eb68b2d3 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -1915,6 +1915,7 @@ GSocketConnectable GSocketConnectableIface g_socket_connectable_enumerate g_socket_connectable_proxy_enumerate +g_socket_connectable_to_string GSocketAddressEnumerator g_socket_address_enumerator_next diff --git a/gio/ginetsocketaddress.c b/gio/ginetsocketaddress.c index 5cf26f7fe..c38fa60f5 100644 --- a/gio/ginetsocketaddress.c +++ b/gio/ginetsocketaddress.c @@ -26,6 +26,7 @@ #include "ginetsocketaddress.h" #include "ginetaddress.h" #include "gnetworkingprivate.h" +#include "gsocketconnectable.h" #include "gioerror.h" #include "glibintl.h" @@ -54,7 +55,13 @@ struct _GInetSocketAddressPrivate guint32 scope_id; }; -G_DEFINE_TYPE_WITH_PRIVATE (GInetSocketAddress, g_inet_socket_address, G_TYPE_SOCKET_ADDRESS) +static void g_inet_socket_address_connectable_iface_init (GSocketConnectableIface *iface); +static gchar *g_inet_socket_address_connectable_to_string (GSocketConnectable *connectable); + +G_DEFINE_TYPE_WITH_CODE (GInetSocketAddress, g_inet_socket_address, G_TYPE_SOCKET_ADDRESS, + G_ADD_PRIVATE (GInetSocketAddress) + G_IMPLEMENT_INTERFACE (G_TYPE_SOCKET_CONNECTABLE, + g_inet_socket_address_connectable_iface_init)) enum { PROP_0, @@ -301,6 +308,59 @@ g_inet_socket_address_class_init (GInetSocketAddressClass *klass) G_PARAM_STATIC_STRINGS)); } +static void +g_inet_socket_address_connectable_iface_init (GSocketConnectableIface *iface) +{ + GSocketConnectableIface *parent_iface = g_type_interface_peek_parent (iface); + + iface->enumerate = parent_iface->enumerate; + iface->proxy_enumerate = parent_iface->proxy_enumerate; + iface->to_string = g_inet_socket_address_connectable_to_string; +} + +static gchar * +g_inet_socket_address_connectable_to_string (GSocketConnectable *connectable) +{ + GInetSocketAddress *sa; + GInetAddress *a; + gchar *a_string; + GString *out; + guint16 port; + + sa = G_INET_SOCKET_ADDRESS (connectable); + a = g_inet_socket_address_get_address (sa); + out = g_string_new (""); + + /* Address. */ + a_string = g_inet_address_to_string (a); + g_string_append (out, a_string); + g_free (a_string); + + /* Scope ID (IPv6 only). */ + if (g_inet_address_get_family (a) == G_SOCKET_FAMILY_IPV6 && + g_inet_socket_address_get_scope_id (sa) != 0) + { + g_string_append_printf (out, "%%%u", + g_inet_socket_address_get_scope_id (sa)); + } + + /* Port. */ + port = g_inet_socket_address_get_port (sa); + if (port != 0) + { + /* Disambiguate ports from IPv6 addresses using square brackets. */ + if (g_inet_address_get_family (a) == G_SOCKET_FAMILY_IPV6) + { + g_string_prepend (out, "["); + g_string_append (out, "]"); + } + + g_string_append_printf (out, ":%u", port); + } + + return g_string_free (out, FALSE); +} + static void g_inet_socket_address_init (GInetSocketAddress *address) { diff --git a/gio/gnetworkaddress.c b/gio/gnetworkaddress.c index 144db44ff..99c495af4 100644 --- a/gio/gnetworkaddress.c +++ b/gio/gnetworkaddress.c @@ -86,6 +86,7 @@ static void g_network_address_get_property (GObject *object, 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); +static gchar *g_network_address_connectable_to_string (GSocketConnectable *connectable); G_DEFINE_TYPE_WITH_CODE (GNetworkAddress, g_network_address, G_TYPE_OBJECT, G_ADD_PRIVATE (GNetworkAddress) @@ -145,6 +146,7 @@ g_network_address_connectable_iface_init (GSocketConnectableIface *connectable_i { connectable_iface->enumerate = g_network_address_connectable_enumerate; connectable_iface->proxy_enumerate = g_network_address_connectable_proxy_enumerate; + connectable_iface->to_string = g_network_address_connectable_to_string; } static void @@ -1111,3 +1113,27 @@ g_network_address_connectable_proxy_enumerate (GSocketConnectable *connectable) return proxy_enum; } + +static gchar * +g_network_address_connectable_to_string (GSocketConnectable *connectable) +{ + GNetworkAddress *addr; + const gchar *scheme; + guint16 port; + GString *out; /* owned */ + + addr = G_NETWORK_ADDRESS (connectable); + out = g_string_new (""); + + scheme = g_network_address_get_scheme (addr); + if (scheme != NULL) + g_string_append_printf (out, "%s:", scheme); + + g_string_append (out, g_network_address_get_hostname (addr)); + + port = g_network_address_get_port (addr); + if (port != 0) + g_string_append_printf (out, ":%u", port); + + return g_string_free (out, FALSE); +} diff --git a/gio/gnetworkservice.c b/gio/gnetworkservice.c index 497d6fd35..d75981963 100644 --- a/gio/gnetworkservice.c +++ b/gio/gnetworkservice.c @@ -89,6 +89,7 @@ static void g_network_service_get_property (GObject *object, static void g_network_service_connectable_iface_init (GSocketConnectableIface *iface); static GSocketAddressEnumerator *g_network_service_connectable_enumerate (GSocketConnectable *connectable); static GSocketAddressEnumerator *g_network_service_connectable_proxy_enumerate (GSocketConnectable *connectable); +static gchar *g_network_service_connectable_to_string (GSocketConnectable *connectable); G_DEFINE_TYPE_WITH_CODE (GNetworkService, g_network_service, G_TYPE_OBJECT, G_ADD_PRIVATE (GNetworkService) @@ -159,6 +160,7 @@ g_network_service_connectable_iface_init (GSocketConnectableIface *connectable_i { connectable_iface->enumerate = g_network_service_connectable_enumerate; connectable_iface->proxy_enumerate = g_network_service_connectable_proxy_enumerate; + connectable_iface->to_string = g_network_service_connectable_to_string; } static void @@ -743,3 +745,15 @@ g_network_service_connectable_proxy_enumerate (GSocketConnectable *connectable) return addr_enum; } + +static gchar * +g_network_service_connectable_to_string (GSocketConnectable *connectable) +{ + GNetworkService *service; + + service = G_NETWORK_SERVICE (connectable); + + return g_strdup_printf ("(%s, %s, %s, %s)", service->priv->service, + service->priv->protocol, service->priv->domain, + service->priv->scheme); +} diff --git a/gio/gsocketaddress.c b/gio/gsocketaddress.c index d0caf3385..cefd9e0fc 100644 --- a/gio/gsocketaddress.c +++ b/gio/gsocketaddress.c @@ -129,6 +129,7 @@ g_socket_address_connectable_iface_init (GSocketConnectableIface *connectable_if { connectable_iface->enumerate = g_socket_address_connectable_enumerate; connectable_iface->proxy_enumerate = g_socket_address_connectable_proxy_enumerate; + /* to_string() is implemented by subclasses */ } static void diff --git a/gio/gsocketconnectable.c b/gio/gsocketconnectable.c index 79901aa98..ef9f92dbd 100644 --- a/gio/gsocketconnectable.c +++ b/gio/gsocketconnectable.c @@ -146,3 +146,34 @@ g_socket_connectable_proxy_enumerate (GSocketConnectable *connectable) else return (* iface->enumerate) (connectable); } + +/** + * g_socket_connectable_to_string: + * @connectable: a #GSocketConnectable + * + * Format a #GSocketConnectable as a string. This is a human-readable format for + * use in debugging output, and is not a stable serialization format. It is not + * suitable for use in user interfaces as it exposes too much information for a + * user. + * + * If the #GSocketConnectable implementation does not support string formatting, + * the implementation’s type name will be returned as a fallback. + * + * Returns: (transfer full): the formatted string + * + * Since: 2.48.0 + */ +gchar * +g_socket_connectable_to_string (GSocketConnectable *connectable) +{ + GSocketConnectableIface *iface; + + g_return_val_if_fail (G_IS_SOCKET_CONNECTABLE (connectable), NULL); + + iface = G_SOCKET_CONNECTABLE_GET_IFACE (connectable); + + if (iface->to_string != NULL) + return iface->to_string (connectable); + else + return g_strdup (G_OBJECT_TYPE_NAME (connectable)); +} diff --git a/gio/gsocketconnectable.h b/gio/gsocketconnectable.h index f7db6800b..b562ff1b8 100644 --- a/gio/gsocketconnectable.h +++ b/gio/gsocketconnectable.h @@ -44,6 +44,8 @@ typedef struct _GSocketConnectableIface GSocketConnectableIface; * @g_iface: The parent interface. * @enumerate: Creates a #GSocketAddressEnumerator * @proxy_enumerate: Creates a #GProxyAddressEnumerator + * @to_string: Format the connectable’s address as a string for debugging. + * Implementing this is optional. (Since: 2.48.0.) * * Provides an interface for returning a #GSocketAddressEnumerator * and #GProxyAddressEnumerator @@ -58,6 +60,7 @@ struct _GSocketConnectableIface GSocketAddressEnumerator * (* proxy_enumerate) (GSocketConnectable *connectable); + gchar * (* to_string) (GSocketConnectable *connectable); }; GLIB_AVAILABLE_IN_ALL @@ -69,6 +72,9 @@ GSocketAddressEnumerator *g_socket_connectable_enumerate (GSocketConnectable *co GLIB_AVAILABLE_IN_ALL GSocketAddressEnumerator *g_socket_connectable_proxy_enumerate (GSocketConnectable *connectable); +GLIB_AVAILABLE_IN_2_48 +gchar *g_socket_connectable_to_string (GSocketConnectable *addr); + G_END_DECLS diff --git a/gio/gunixsocketaddress.c b/gio/gunixsocketaddress.c index b1702cb96..f4fc07758 100644 --- a/gio/gunixsocketaddress.c +++ b/gio/gunixsocketaddress.c @@ -24,6 +24,7 @@ #include #include "gunixsocketaddress.h" +#include "gsocketconnectable.h" #include "glibintl.h" #include "gnetworking.h" @@ -76,7 +77,13 @@ struct _GUnixSocketAddressPrivate GUnixSocketAddressType address_type; }; -G_DEFINE_TYPE_WITH_PRIVATE (GUnixSocketAddress, g_unix_socket_address, G_TYPE_SOCKET_ADDRESS) +static void g_unix_socket_address_connectable_iface_init (GSocketConnectableIface *iface); +static gchar *g_unix_socket_address_connectable_to_string (GSocketConnectable *connectable); + +G_DEFINE_TYPE_WITH_CODE (GUnixSocketAddress, g_unix_socket_address, G_TYPE_SOCKET_ADDRESS, + G_ADD_PRIVATE (GUnixSocketAddress) + G_IMPLEMENT_INTERFACE (G_TYPE_SOCKET_CONNECTABLE, + g_unix_socket_address_connectable_iface_init)) static void g_unix_socket_address_set_property (GObject *object, @@ -299,6 +306,49 @@ g_unix_socket_address_class_init (GUnixSocketAddressClass *klass) G_PARAM_STATIC_STRINGS)); } +static void +g_unix_socket_address_connectable_iface_init (GSocketConnectableIface *iface) +{ + GSocketConnectableIface *parent_iface = g_type_interface_peek_parent (iface); + + iface->enumerate = parent_iface->enumerate; + iface->proxy_enumerate = parent_iface->proxy_enumerate; + iface->to_string = g_unix_socket_address_connectable_to_string; +} + +static gchar * +g_unix_socket_address_connectable_to_string (GSocketConnectable *connectable) +{ + GUnixSocketAddress *ua; + GString *out; + const gchar *path; + gsize path_len, i; + + ua = G_UNIX_SOCKET_ADDRESS (connectable); + + /* Anonymous sockets have no path. */ + if (ua->priv->address_type == G_UNIX_SOCKET_ADDRESS_ANONYMOUS) + return g_strdup ("anonymous"); + + path = g_unix_socket_address_get_path (ua); + path_len = g_unix_socket_address_get_path_len (ua); + out = g_string_sized_new (path_len); + + /* Return the #GUnixSocketAddress:path, but with all non-printable characters + * (including nul bytes) escaped to hex. */ + for (i = 0; i < path_len; i++) + { + guint8 c = path[i]; + + if (g_ascii_isprint (path[i])) + g_string_append_c (out, c); + else + g_string_append_printf (out, "\\x%02x", (guint) c); + } + + return g_string_free (out, FALSE); +} + static void g_unix_socket_address_init (GUnixSocketAddress *address) { diff --git a/gio/tests/inet-address.c b/gio/tests/inet-address.c index addcbb424..6e4c4247d 100644 --- a/gio/tests/inet-address.c +++ b/gio/tests/inet-address.c @@ -235,6 +235,55 @@ test_socket_address (void) g_object_unref (saddr); } +static void +test_socket_address_to_string (void) +{ + GSocketAddress *sa = NULL; + GInetAddress *ia = NULL; + gchar *str = NULL; + + /* IPv4. */ + ia = g_inet_address_new_from_string ("123.1.123.1"); + sa = g_inet_socket_address_new (ia, 80); + str = g_socket_connectable_to_string (G_SOCKET_CONNECTABLE (sa)); + g_assert_cmpstr (str, ==, "123.1.123.1:80"); + g_free (str); + g_object_unref (sa); + g_object_unref (ia); + + /* IPv6. */ + ia = g_inet_address_new_from_string ("::80"); + sa = g_inet_socket_address_new (ia, 80); + str = g_socket_connectable_to_string (G_SOCKET_CONNECTABLE (sa)); + g_assert_cmpstr (str, ==, "[::80]:80"); + g_free (str); + g_object_unref (sa); + g_object_unref (ia); + + /* IPv6 without port. */ + ia = g_inet_address_new_from_string ("::80"); + sa = g_inet_socket_address_new (ia, 0); + str = g_socket_connectable_to_string (G_SOCKET_CONNECTABLE (sa)); + g_assert_cmpstr (str, ==, "::80"); + g_free (str); + g_object_unref (sa); + g_object_unref (ia); + + /* IPv6 with scope. */ + ia = g_inet_address_new_from_string ("::1"); + sa = G_SOCKET_ADDRESS (g_object_new (G_TYPE_INET_SOCKET_ADDRESS, + "address", ia, + "port", 123, + "flowinfo", 10, + "scope-id", 25, + NULL)); + str = g_socket_connectable_to_string (G_SOCKET_CONNECTABLE (sa)); + g_assert_cmpstr (str, ==, "[::1%25]:123"); + g_free (str); + g_object_unref (sa); + g_object_unref (ia); +} + static void test_mask_parse (void) { @@ -368,6 +417,7 @@ main (int argc, char *argv[]) g_test_add_func ("/inet-address/bytes", test_bytes); g_test_add_func ("/inet-address/property", test_property); g_test_add_func ("/socket-address/basic", test_socket_address); + g_test_add_func ("/socket-address/to-string", test_socket_address_to_string); g_test_add_func ("/address-mask/parse", test_mask_parse); g_test_add_func ("/address-mask/property", test_mask_property); g_test_add_func ("/address-mask/equal", test_mask_equal); diff --git a/gio/tests/network-address.c b/gio/tests/network-address.c index 73e323c39..622af17b1 100644 --- a/gio/tests/network-address.c +++ b/gio/tests/network-address.c @@ -477,6 +477,43 @@ test_loopback_async (void) g_object_unref (addr); } +static void +test_to_string (void) +{ + GSocketConnectable *addr = NULL; + gchar *str = NULL; + GError *error = NULL; + + /* Without port. */ + addr = g_network_address_new ("some-hostname", 0); + str = g_socket_connectable_to_string (addr); + g_assert_cmpstr (str, ==, "some-hostname"); + g_free (str); + g_object_unref (addr); + + /* With port. */ + addr = g_network_address_new ("some-hostname", 123); + str = g_socket_connectable_to_string (addr); + g_assert_cmpstr (str, ==, "some-hostname:123"); + g_free (str); + g_object_unref (addr); + + /* With scheme and port. */ + addr = g_network_address_parse_uri ("http://some-hostname:123", 80, &error); + g_assert_no_error (error); + str = g_socket_connectable_to_string (addr); + g_assert_cmpstr (str, ==, "http:some-hostname:123"); + g_free (str); + g_object_unref (addr); + + /* Loopback. */ + addr = g_network_address_new ("localhost", 456); + str = g_socket_connectable_to_string (addr); + g_assert_cmpstr (str, ==, "localhost:456"); + g_free (str); + g_object_unref (addr); +} + int main (int argc, char *argv[]) { @@ -520,6 +557,7 @@ main (int argc, char *argv[]) g_test_add_func ("/network-address/loopback/basic", test_loopback_basic); g_test_add_func ("/network-address/loopback/sync", test_loopback_sync); g_test_add_func ("/network-address/loopback/async", test_loopback_async); + g_test_add_func ("/network-address/to-string", test_to_string); return g_test_run (); } diff --git a/gio/tests/socket-address.c b/gio/tests/socket-address.c index c3cd8090c..b3323c249 100644 --- a/gio/tests/socket-address.c +++ b/gio/tests/socket-address.c @@ -67,6 +67,45 @@ test_unix_socket_address_construct (void) g_object_unref (a); } +static void +test_unix_socket_address_to_string (void) +{ + GSocketAddress *addr = NULL; + gchar *str = NULL; + + /* ADDRESS_PATH. */ + addr = g_unix_socket_address_new_with_type ("/some/path", -1, + G_UNIX_SOCKET_ADDRESS_PATH); + str = g_socket_connectable_to_string (G_SOCKET_CONNECTABLE (addr)); + g_assert_cmpstr (str, ==, "/some/path"); + g_free (str); + g_object_unref (addr); + + /* ADDRESS_ANONYMOUS. */ + addr = g_unix_socket_address_new_with_type ("", 0, + G_UNIX_SOCKET_ADDRESS_ANONYMOUS); + str = g_socket_connectable_to_string (G_SOCKET_CONNECTABLE (addr)); + g_assert_cmpstr (str, ==, "anonymous"); + g_free (str); + g_object_unref (addr); + + /* ADDRESS_ABSTRACT. */ + addr = g_unix_socket_address_new_with_type ("abstract-path\0✋", 17, + G_UNIX_SOCKET_ADDRESS_ABSTRACT); + str = g_socket_connectable_to_string (G_SOCKET_CONNECTABLE (addr)); + g_assert_cmpstr (str, ==, "abstract-path\\x00\\xe2\\x9c\\x8b"); + g_free (str); + g_object_unref (addr); + + /* ADDRESS_ABSTRACT_PADDED. */ + addr = g_unix_socket_address_new_with_type ("abstract-path\0✋", 17, + G_UNIX_SOCKET_ADDRESS_ABSTRACT_PADDED); + str = g_socket_connectable_to_string (G_SOCKET_CONNECTABLE (addr)); + g_assert_cmpstr (str, ==, "abstract-path\\x00\\xe2\\x9c\\x8b"); + g_free (str); + g_object_unref (addr); +} + int main (int argc, char **argv) @@ -74,6 +113,7 @@ main (int argc, g_test_init (&argc, &argv, NULL); g_test_add_func ("/socket/address/unix/construct", test_unix_socket_address_construct); + g_test_add_func ("/socket/address/unix/to-string", test_unix_socket_address_to_string); return g_test_run (); }