diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c
index 8c1d31c76..cc9703c96 100644
--- a/gio/gdbusaddress.c
+++ b/gio/gdbusaddress.c
@@ -1586,3 +1586,50 @@ g_dbus_address_get_for_bus_sync (GBusType bus_type,
return ret;
}
+
+/**
+ * g_dbus_address_escape_value:
+ * @string: an unescaped string to be included in a D-Bus address
+ * as the value in a key-value pair
+ *
+ * Escape @string so it can appear in a D-Bus address as the value
+ * part of a key-value pair.
+ *
+ * For instance, if @string is /run/bus-for-:0
,
+ * this function would return /run/bus-for-%3A0
,
+ * which could be used in a D-Bus address like
+ * unix:nonce-tcp:host=127.0.0.1,port=42,noncefile=/run/bus-for-%3A0
.
+ *
+ * Returns: (transfer full): a copy of @string with all
+ * non-optionally-escaped bytes escaped
+ *
+ * Since: 2.36
+ */
+gchar *
+g_dbus_address_escape_value (const gchar *string)
+{
+ GString *s;
+ gsize i;
+
+ g_return_val_if_fail (string != NULL, NULL);
+
+ /* There will often not be anything needing escaping at all. */
+ s = g_string_sized_new (strlen (string));
+
+ /* D-Bus address escaping is mostly the same as URI escaping... */
+ g_string_append_uri_escaped (s, string, "\\/", FALSE);
+
+ /* ... but '~' is an unreserved character in URIs, but a
+ * non-optionally-escaped character in D-Bus addresses. */
+ for (i = 0; i < s->len; i++)
+ {
+ if (G_UNLIKELY (s->str[i] == '~'))
+ {
+ s->str[i] = '%';
+ g_string_insert (s, i + 1, "7E");
+ i += 2;
+ }
+ }
+
+ return g_string_free (s, FALSE);
+}
diff --git a/gio/gdbusaddress.h b/gio/gdbusaddress.h
index 55896b8f2..08773aa4c 100644
--- a/gio/gdbusaddress.h
+++ b/gio/gdbusaddress.h
@@ -31,6 +31,9 @@
G_BEGIN_DECLS
+GLIB_AVAILABLE_IN_2_36
+gchar *g_dbus_address_escape_value (const gchar *string);
+
GLIB_AVAILABLE_IN_ALL
gboolean g_dbus_is_address (const gchar *string);
GLIB_AVAILABLE_IN_ALL
diff --git a/gio/tests/gdbus-addresses.c b/gio/tests/gdbus-addresses.c
index 72388c5aa..c5a237d0a 100644
--- a/gio/tests/gdbus-addresses.c
+++ b/gio/tests/gdbus-addresses.c
@@ -102,6 +102,29 @@ test_mixed_address (void)
g_assert (!g_dbus_is_supported_address ("tcp:host=localhost,port=42;tcp:family=bla", NULL));
}
+static const struct { const char *before; const char *after; } escaping[] = {
+ { "foo", "foo" },
+ { "/.\\-_", "/.\\-_" },
+ { "<=>", "%3C%3D%3E" },
+ { "/foo~1", "/foo%7E1" },
+ { "\xe2\x98\xad\xff", "%E2%98%AD%FF" },
+ { NULL, NULL }
+};
+
+static void
+test_escape_address (void)
+{
+ gsize i;
+
+ for (i = 0; escaping[i].before != NULL; i++)
+ {
+ gchar *s = g_dbus_address_escape_value (escaping[i].before);
+
+ g_assert_cmpstr (s, ==, escaping[i].after);
+ g_free (s);
+ }
+}
+
int
main (int argc,
char *argv[])
@@ -116,6 +139,7 @@ main (int argc,
g_test_add_func ("/gdbus/tcp-address", test_tcp_address);
g_test_add_func ("/gdbus/autolaunch-address", test_autolaunch_address);
g_test_add_func ("/gdbus/mixed-address", test_mixed_address);
+ g_test_add_func ("/gdbus/escape-address", test_escape_address);
return g_test_run();
}