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 (); }