summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2017-05-16 18:50:21 +0200
committerThomas Haller <thaller@redhat.com>2017-05-17 10:56:45 +0200
commit1d3a56825e6b9c0bb0ee9629e3380b87f483c5a2 (patch)
tree500afe929d6ed71dc4ac6c2b5e39426419511c69
parent1e78f50b8e5e24d13547b478165170117c1ac8ae (diff)
downloadNetworkManager-1d3a56825e6b9c0bb0ee9629e3380b87f483c5a2.tar.gz
shared: add nm_utils_str_utf8safe_*() API to sanitize UTF-8 strings
Use C-style backslash escaping to sanitize non-UTF-8 strings. They are also compatible with glib's g_strcompress() and g_strescape(). The difference is only that g_strescape() escapes all non-printable, non ASCII character as well, while nm_utils_str_utf8safe_escape() preserves every valid UTF-8 sequence -- except backslash.
-rw-r--r--libnm-core/tests/test-general.c95
-rw-r--r--shared/nm-utils/nm-shared-utils.c125
-rw-r--r--shared/nm-utils/nm-shared-utils.h14
3 files changed, 234 insertions, 0 deletions
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index 30f80303b6..137cfdfa60 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -5323,6 +5323,100 @@ static void test_nm_utils_enum (void)
/*****************************************************************************/
+static void
+do_test_utils_str_utf8safe (const char *str, const char *expected, NMUtilsStrUtf8SafeFlags flags)
+{
+ const char *str_safe, *s;
+ gs_free char *str2 = NULL;
+ gs_free char *str3 = NULL;
+
+ str_safe = nm_utils_str_utf8safe_escape_c (str, flags, &str2);
+
+ str3 = nm_utils_str_utf8safe_escape (str, flags);
+ g_assert_cmpstr (str3, ==, str_safe);
+ g_assert ((!str && !str3) || (str != str3));
+ g_clear_pointer (&str3, g_free);
+
+ if (expected == NULL) {
+ g_assert (str_safe == str);
+ g_assert (!str2);
+ if (str) {
+ g_assert (!strchr (str, '\\'));
+ g_assert (g_utf8_validate (str, -1, NULL));
+ }
+
+ g_assert (str == nm_utils_str_utf8safe_unescape_c (str_safe, &str3));
+ g_assert (!str3);
+
+ str3 = nm_utils_str_utf8safe_unescape (str_safe);
+ if (str) {
+ g_assert (str3 != str);
+ g_assert_cmpstr (str3, ==, str);
+ } else
+ g_assert (!str3);
+ g_clear_pointer (&str3, g_free);
+ return;
+ }
+
+ g_assert (str);
+ g_assert (str_safe != str);
+ g_assert (str_safe == str2);
+ g_assert ( strchr (str, '\\')
+ || !g_utf8_validate (str, -1, NULL)
+ || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII)
+ && NM_STRCHAR_ANY (str, ch, (guchar) ch >= 127))
+ || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)
+ && NM_STRCHAR_ANY (str, ch, (guchar) ch < ' ')));
+ g_assert (g_utf8_validate (str_safe, -1, NULL));
+
+ str3 = g_strcompress (str_safe);
+ g_assert_cmpstr (str, ==, str3);
+ g_clear_pointer (&str3, g_free);
+
+ str3 = nm_utils_str_utf8safe_unescape (str_safe);
+ g_assert (str3 != str);
+ g_assert_cmpstr (str3, ==, str);
+ g_clear_pointer (&str3, g_free);
+
+ s = nm_utils_str_utf8safe_unescape_c (str_safe, &str3);
+ g_assert (str3 != str);
+ g_assert (s == str3);
+ g_assert_cmpstr (str3, ==, str);
+ g_clear_pointer (&str3, g_free);
+
+ g_assert_cmpstr (str_safe, ==, expected);
+}
+
+static void
+test_utils_str_utf8safe (void)
+{
+ do_test_utils_str_utf8safe (NULL, NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("\314", "\\314", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("\314\315x\315\315x", "\\314\\315x\\315\\315x", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("\314\315xx", "\\314\\315xx", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("\314xx", "\\314xx", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("\xa0", "\\240", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("\xe2\x91\xa0", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("\xe2\xe2\x91\xa0", "\\342\xe2\x91\xa0", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("\xe2\xe2\x91\xa0\xa0", "\\342\xe2\x91\xa0\\240", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("a", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("ab", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("ab\314", "ab\\314", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("ab\314adsf", "ab\\314adsf", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("abadsf", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("abäb", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("x\xa0", "x\\240", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("Ä\304ab\\äb", "Ä\\304ab\\\\äb", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("Äab\\äb", "Äab\\\\äb", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("ÄÄab\\äb", "ÄÄab\\\\äb", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("㈞abä㈞b", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+ do_test_utils_str_utf8safe ("abäb", "ab\\303\\244b", NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII);
+ do_test_utils_str_utf8safe ("ab\ab", "ab\\007b", NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL);
+}
+
+/*****************************************************************************/
+
static int
_test_nm_in_set_get (int *call_counter, gboolean allow_called, int value)
{
@@ -5680,6 +5774,7 @@ int main (int argc, char **argv)
nmtst_init (&argc, &argv, TRUE);
/* The tests */
+ g_test_add_func ("/core/general/test_utils_str_utf8safe", test_utils_str_utf8safe);
g_test_add_func ("/core/general/test_nm_in_set", test_nm_in_set);
g_test_add_func ("/core/general/test_nm_in_strset", test_nm_in_strset);
g_test_add_func ("/core/general/test_setting_vpn_items", test_setting_vpn_items);
diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c
index 8bd81dafda..e17291d50e 100644
--- a/shared/nm-utils/nm-shared-utils.c
+++ b/shared/nm-utils/nm-shared-utils.c
@@ -397,3 +397,128 @@ nm_g_object_set_property (GObject *object,
}
/*****************************************************************************/
+
+/**
+ * nm_utils_str_utf8safe_escape:
+ * @str: NUL terminated input string, possibly in utf-8 encoding
+ * @flags: #NMUtilsStrUtf8SafeFlags flags
+ *
+ * Does something similar like g_strescape(), where the operation
+ * can be reverted by g_strcompress(). However, the UTF-8 characters
+ * are not escaped at all (except the escape character '\\'). It only
+ * escapes non-UTF-8 characters. This way it is possible to transfer
+ * the string as UTF-8 via D-Bus.
+ * Also, it can be directly displayed to the user and will show as
+ * UTF-8, with exception of the escape character and characters in
+ * different encodings.
+ *
+ * Returns: the escaped input string in UTF-8 encoding. The returned
+ * value should be freed with g_free().
+ * The escaping can be reverted by g_strcompress().
+ **/
+char *
+nm_utils_str_utf8safe_escape (const char *str, NMUtilsStrUtf8SafeFlags flags)
+{
+ char *s = NULL;
+
+ nm_utils_str_utf8safe_escape_c (str, flags, &s);
+ return s ? : g_strdup (str);
+}
+
+static void
+_str_append_escape (GString *s, char ch)
+{
+ g_string_append_c (s, '\\');
+ g_string_append_c (s, '0' + ((((guchar) ch) >> 6) & 07));
+ g_string_append_c (s, '0' + ((((guchar) ch) >> 3) & 07));
+ g_string_append_c (s, '0' + ( ((guchar) ch) & 07));
+}
+
+/**
+ * nm_utils_str_utf8safe_escape_c:
+ * @str: NUL terminated input string, possibly in utf-8 encoding
+ * @flags: #NMUtilsStrUtf8SafeFlags flags
+ * @to_free: (out): return the pointer location of the string
+ * if a copying was necessary.
+ *
+ * Like nm_utils_str_utf8safe_escape(), except that the string
+ * is only copied if it is actually necessary. In that case,
+ * @to_free will contain the allocated string which must be
+ * freed with g_free().
+ * Otherwise, @to_free is %NULL and the input string is returned.
+ *
+ * Returns: the escaped input string. If no escaping is necessary,
+ * it returns @str. Otherwise, an allocated string @to_free is
+ * returned.
+ * The escaping can be reverted by g_strcompress().
+ **/
+const char *
+nm_utils_str_utf8safe_escape_c (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free)
+{
+ const char *p = NULL;
+ GString *s;
+
+ g_return_val_if_fail (to_free, NULL);
+
+ *to_free = NULL;
+ if (!str || !str[0])
+ return str;
+
+ if ( g_utf8_validate (str, -1, &p)
+ && !NM_STRCHAR_ANY (str, ch,
+ ( (ch) == '\\' \
+ || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \
+ && (ch) < ' ') \
+ || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \
+ && (guchar) (ch) >= 127))))
+ return str;
+
+ s = g_string_sized_new ((p - str) + strlen (p) + 5);
+
+ do {
+ for (; str < p; str++) {
+ char ch = str[0];
+
+ if (ch == '\\')
+ g_string_append (s, "\\\\");
+ else if ( ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \
+ && (ch) < ' ') \
+ || ( NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \
+ && (guchar) (ch) >= 127))
+ _str_append_escape (s, ch);
+ else
+ g_string_append_c (s, ch);
+ }
+
+ if (p[0] == '\0')
+ break;
+ _str_append_escape (s, p[0]);
+
+ str = &p[1];
+ g_utf8_validate (str, -1, &p);
+ } while (TRUE);
+
+ *to_free = g_string_free (s, FALSE);
+ return *to_free;
+}
+
+char *
+nm_utils_str_utf8safe_unescape (const char *str)
+{
+ if (!str)
+ return NULL;
+ return g_strcompress (str);
+}
+
+const char *
+nm_utils_str_utf8safe_unescape_c (const char *str, char **to_free)
+{
+ g_return_val_if_fail (to_free, NULL);
+
+ if (!str || !strchr (str, '\\')) {
+ *to_free = NULL;
+ return str;
+ }
+ return (*to_free = g_strcompress (str));
+}
+
diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h
index 3776c1590e..4e71c72719 100644
--- a/shared/nm-utils/nm-shared-utils.h
+++ b/shared/nm-utils/nm-shared-utils.h
@@ -97,4 +97,18 @@ gboolean nm_g_object_set_property (GObject *object,
/*****************************************************************************/
+typedef enum {
+ NM_UTILS_STR_UTF8_SAFE_FLAG_NONE = 0,
+ NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL = 0x0001,
+ NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII = 0x0002,
+} NMUtilsStrUtf8SafeFlags;
+
+char *nm_utils_str_utf8safe_escape (const char *str, NMUtilsStrUtf8SafeFlags flags);
+char *nm_utils_str_utf8safe_unescape (const char *str);
+
+const char *nm_utils_str_utf8safe_escape_c (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free);
+const char *nm_utils_str_utf8safe_unescape_c (const char *str, char **to_free);
+
+/*****************************************************************************/
+
#endif /* __NM_SHARED_UTILS_H__ */