diff --git a/introspection/org.freedesktop.NetworkManager.Device.xml b/introspection/org.freedesktop.NetworkManager.Device.xml index ee424101e6..be0a612cc4 100644 --- a/introspection/org.freedesktop.NetworkManager.Device.xml +++ b/introspection/org.freedesktop.NetworkManager.Device.xml @@ -21,6 +21,9 @@ each device in your application, use the object path. If you're looking for a way to track a specific piece of hardware across reboot or hotplug, use a MAC address or USB serial number. + + Note that non-UTF-8 characters are backslash escaped. Use g_strcompress() + to obtain the true (non-UTF-8) string. --> @@ -28,6 +31,9 @@ Interface: The name of the device's control (and often data) interface. + Note that non UTF-8 characters are backslash escaped, so the + resulting name may be longer then 15 characters. Use g_strcompress() + to revert the escaping. --> @@ -38,6 +44,9 @@ not refer to the actual data interface until the device has successfully established a data connection, indicated by the device's State becoming ACTIVATED. + Note that non UTF-8 characters are backslash escaped, so the + resulting name may be longer then 15 characters. Use g_strcompress() + to revert the escaping. --> @@ -45,6 +54,8 @@ Driver: The driver handling the device. + Non-UTF-8 sequences are backslash escaped. Use g_strcompress() + to revert. --> @@ -52,6 +63,8 @@ DriverVersion: The version of the driver handling the device. + Non-UTF-8 sequences are backslash escaped. Use g_strcompress() + to revert. --> @@ -59,6 +72,8 @@ FirmwareVersion: The firmware version for the device. + Non-UTF-8 sequences are backslash escaped. Use g_strcompress() + to revert. --> diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c index 30f80303b6..7ec63f9a80 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 (str, flags, &str2); + + str3 = nm_utils_str_utf8safe_escape_cp (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 (str_safe, &str3)); + g_assert (!str3); + + str3 = nm_utils_str_utf8safe_unescape_cp (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_cp (str_safe); + g_assert (str3 != str); + g_assert_cmpstr (str3, ==, str); + g_clear_pointer (&str3, g_free); + + s = nm_utils_str_utf8safe_unescape (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/libnm/nm-device.c b/libnm/nm-device.c index 9cbdbd0069..7e8feb1cd5 100644 --- a/libnm/nm-device.c +++ b/libnm/nm-device.c @@ -81,7 +81,7 @@ typedef struct { GPtrArray *available_connections; struct udev *udev; - char *product, *short_product; + char *product; char *vendor, *short_vendor; char *description, *bus_name; @@ -320,7 +320,6 @@ finalize (GObject *object) g_free (priv->driver_version); g_free (priv->firmware_version); g_free (priv->product); - g_free (priv->short_product); g_free (priv->vendor); g_free (priv->short_vendor); g_free (priv->description); @@ -1357,6 +1356,17 @@ _get_udev_property (NMDevice *device, return db_value; } +static char * +_get_udev_property_utf8safe (NMDevice *device, + const char *enc_prop, /* ID_XXX_ENC */ + const char *db_prop) /* ID_XXX_FROM_DATABASE */ +{ + return nm_utils_str_utf8safe_escape_take (_get_udev_property (device, + enc_prop, + db_prop), + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL); +} + /** * nm_device_get_product: * @device: a #NMDevice @@ -1365,6 +1375,9 @@ _get_udev_property (NMDevice *device, * * Returns: the product name of the device. This is the internal string used by the * device, and must not be modified. + * + * The string is backslash escaped (C escaping) for invalid characters. The escaping + * can be reverted with g_strcompress(), however the result may not be valid UTF-8. **/ const char * nm_device_get_product (NMDevice *device) @@ -1374,15 +1387,16 @@ nm_device_get_product (NMDevice *device) g_return_val_if_fail (NM_IS_DEVICE (device), NULL); priv = NM_DEVICE_GET_PRIVATE (device); - if (!priv->product) - priv->product = _get_udev_property (device, "ID_MODEL_ENC", "ID_MODEL_FROM_DATABASE"); + if (!priv->product) { + priv->product = _get_udev_property_utf8safe (device, "ID_MODEL_ENC", "ID_MODEL_FROM_DATABASE"); - /* Sometimes ID_PRODUCT_FROM_DATABASE is used? */ - if (!priv->product) - priv->product = _get_udev_property (device, "ID_MODEL_ENC", "ID_PRODUCT_FROM_DATABASE"); + /* Sometimes ID_PRODUCT_FROM_DATABASE is used? */ + if (!priv->product) + priv->product = _get_udev_property_utf8safe (device, "ID_MODEL_ENC", "ID_PRODUCT_FROM_DATABASE"); - if (!priv->product) - priv->product = g_strdup (""); + if (!priv->product) + priv->product = g_strdup (""); + } return priv->product; } @@ -1395,6 +1409,9 @@ nm_device_get_product (NMDevice *device) * * Returns: the vendor name of the device. This is the internal string used by the * device, and must not be modified. + * + * The string is backslash escaped (C escaping) for invalid characters. The escaping + * can be reverted with g_strcompress(), however the result may not be valid UTF-8. **/ const char * nm_device_get_vendor (NMDevice *device) @@ -1406,7 +1423,7 @@ nm_device_get_vendor (NMDevice *device) priv = NM_DEVICE_GET_PRIVATE (device); if (!priv->vendor) - priv->vendor = _get_udev_property (device, "ID_VENDOR_ENC", "ID_VENDOR_FROM_DATABASE"); + priv->vendor = _get_udev_property_utf8safe (device, "ID_VENDOR_ENC", "ID_VENDOR_FROM_DATABASE"); if (!priv->vendor) priv->vendor = g_strdup (""); @@ -1414,128 +1431,146 @@ nm_device_get_vendor (NMDevice *device) return priv->vendor; } -static const char * const ignored_words[] = { - "Semiconductor", - "Components", - "Corporation", - "Communications", - "Company", - "Corp.", - "Corp", - "Co.", - "Inc.", - "Inc", - "Incorporated", - "Ltd.", - "Limited.", - "Intel?", - "chipset", - "adapter", - "[hex]", - "NDIS", - "Module", - NULL -}; - -static const char * const ignored_phrases[] = { - "Multiprotocol MAC/baseband processor", - "Wireless LAN Controller", - "Wireless LAN Adapter", - "Wireless Adapter", - "Network Connection", - "Wireless Cardbus Adapter", - "Wireless CardBus Adapter", - "54 Mbps Wireless PC Card", - "Wireless PC Card", - "Wireless PC", - "PC Card with XJACK(r) Antenna", - "Wireless cardbus", - "Wireless LAN PC Card", - "Technology Group Ltd.", - "Communication S.p.A.", - "Business Mobile Networks BV", - "Mobile Broadband Minicard Composite Device", - "Mobile Communications AB", - "(PC-Suite Mode)", - NULL -}; - static char * fixup_desc_string (const char *desc) { - char *p, *temp; - char **words, **item; - GString *str; + static const char *const IGNORED_PHRASES[] = { + "Multiprotocol MAC/baseband processor", + "Wireless LAN Controller", + "Wireless LAN Adapter", + "Wireless Adapter", + "Network Connection", + "Wireless Cardbus Adapter", + "Wireless CardBus Adapter", + "54 Mbps Wireless PC Card", + "Wireless PC Card", + "Wireless PC", + "PC Card with XJACK(r) Antenna", + "Wireless cardbus", + "Wireless LAN PC Card", + "Technology Group Ltd.", + "Communication S.p.A.", + "Business Mobile Networks BV", + "Mobile Broadband Minicard Composite Device", + "Mobile Communications AB", + "(PC-Suite Mode)", + }; + static const char *const IGNORED_WORDS[] = { + "Semiconductor", + "Components", + "Corporation", + "Communications", + "Company", + "Corp.", + "Corp", + "Co.", + "Inc.", + "Inc", + "Incorporated", + "Ltd.", + "Limited.", + "Intel?", + "chipset", + "adapter", + "[hex]", + "NDIS", + "Module", + }; + char *desc_full; + char *p, *q; int i; - if (!desc) + if (!desc || !desc[0]) return NULL; - p = temp = g_strdup (desc); - while (*p) { - if (*p == '_' || *p == ',') + /* restore original non-UTF-8-safe text. */ + desc_full = nm_utils_str_utf8safe_unescape_cp (desc); + + /* replace all invalid UTF-8 bytes with space. */ + p = desc_full; + while (!g_utf8_validate (p, -1, (const char **) &q)) { + /* the byte is invalid UTF-8. Replace it with space and proceed. */ + *q = ' '; + p = q + 1; + } + + /* replace '_', ',', and ASCII controll characters with space. */ + for (p = desc_full; p[0]; p++) { + if ( NM_IN_SET (*p, '_', ',') + || *p < ' ') *p = ' '; - p++; } /* Attempt to shorten ID by ignoring certain phrases */ - for (i = 0; ignored_phrases[i]; i++) { - p = strstr (temp, ignored_phrases[i]); + for (i = 0; i < G_N_ELEMENTS (IGNORED_PHRASES); i++) { + p = strstr (desc_full, IGNORED_PHRASES[i]); if (p) { - guint32 ignored_len = strlen (ignored_phrases[i]); + const char *eow = &p[strlen (IGNORED_PHRASES[i])]; - memmove (p, p + ignored_len, strlen (p + ignored_len) + 1); /* +1 for the \0 */ + memmove (p, eow, strlen (eow) + 1); /* +1 for the \0 */ } } - /* Attempt to shorten ID by ignoring certain individual words */ - words = g_strsplit (temp, " ", 0); - str = g_string_new_len (NULL, strlen (temp)); - g_free (temp); + /* Attempt to shorten ID by ignoring certain individual words. + * - word-split the description at spaces + * - coalesce multiple spaces + * - skip over IGNORED_WORDS */ + p = desc_full; + q = desc_full; + for (;;) { + char *eow; + gsize l; - for (item = words; *item; item++) { - gboolean ignore = FALSE; + /* skip leading spaces. */ + while (p[0] == ' ') + p++; - if (**item == '\0') - continue; + if (!p[0]) + break; - for (i = 0; ignored_words[i]; i++) { - if (!strcmp (*item, ignored_words[i])) { - ignore = TRUE; - break; - } + /* split leading word on first space */ + eow = strchr (p, ' '); + if (eow) + *eow = '\0'; + + if (nm_utils_strv_find_first ((char **) IGNORED_WORDS, + G_N_ELEMENTS (IGNORED_WORDS), + p) < 0) + goto next; + + l = strlen (p); + if (q != p) { + if (q != desc_full) + *q++ = ' '; + memmove (q, p, l); } + q += l; - if (!ignore) { - if (str->len) - g_string_append_c (str, ' '); - g_string_append (str, *item); - } +next: + if (!eow) + break; + p = eow + 1; } - g_strfreev (words); - temp = str->str; - g_string_free (str, FALSE); + *q++ = '\0'; - return temp; + if (!desc_full[0]) { + g_free (desc_full); + return NULL; + } + + nm_assert (g_utf8_validate (desc_full, -1, NULL)); + return desc_full; } static void -get_description (NMDevice *device) +ensure_description (NMDevice *device) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); - const char *dev_product; - const char *dev_vendor; - char *pdown; - char *vdown; - GString *str; GParamSpec *name_prop; + gs_free char *short_product = NULL; - dev_product = nm_device_get_product (device); - priv->short_product = fixup_desc_string (dev_product); - - dev_vendor = nm_device_get_vendor (device); - priv->short_vendor = fixup_desc_string (dev_vendor); + priv->short_vendor = nm_str_realloc (fixup_desc_string (nm_device_get_vendor (device))); /* Grab device's preferred name, if any */ name_prop = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (device)), "name"); @@ -1546,28 +1581,24 @@ get_description (NMDevice *device) g_clear_pointer (&priv->description, g_free); } - if (!dev_product || !dev_vendor) { - priv->description = g_strdup (nm_device_get_iface (device)); + if ( !priv->short_vendor + || !(short_product = fixup_desc_string (nm_device_get_product (device)))) { + priv->description = g_strdup (nm_device_get_iface (device) ?: ""); return; } - str = g_string_new_len (NULL, strlen (priv->short_vendor) + strlen (priv->short_product) + 1); - /* Another quick hack; if all of the fixed up vendor string * is found in product, ignore the vendor. */ - pdown = g_ascii_strdown (priv->short_product, -1); - vdown = g_ascii_strdown (priv->short_vendor, -1); - if (!strstr (pdown, vdown)) { - g_string_append (str, priv->short_vendor); - g_string_append_c (str, ' '); + { + gs_free char *pdown = g_ascii_strdown (short_product, -1); + gs_free char *vdown = g_ascii_strdown (priv->short_vendor, -1); + + if (!strstr (pdown, vdown)) + priv->description = g_strconcat (priv->short_vendor, " ", short_product, NULL); + else + priv->description = g_steal_pointer (&short_product); } - g_free (pdown); - g_free (vdown); - - g_string_append (str, priv->short_product); - - priv->description = g_string_free (str, FALSE); } static const char * @@ -1580,7 +1611,7 @@ get_short_vendor (NMDevice *device) priv = NM_DEVICE_GET_PRIVATE (device); if (!priv->description) - get_description (device); + ensure_description (device); return priv->short_vendor; } @@ -1604,7 +1635,7 @@ nm_device_get_description (NMDevice *device) priv = NM_DEVICE_GET_PRIVATE (device); if (!priv->description) - get_description (device); + ensure_description (device); return priv->description; } diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c index 8bd81dafda..9a87536e53 100644 --- a/shared/nm-utils/nm-shared-utils.c +++ b/shared/nm-utils/nm-shared-utils.c @@ -397,3 +397,141 @@ nm_g_object_set_property (GObject *object, } /*****************************************************************************/ + +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: + * @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. + * + * Returns the possible non-UTF-8 NUL terminated string @str + * and uses backslash escaping (C escaping, like g_strescape()) + * to sanitize non UTF-8 characters. The result is valid + * UTF-8. + * + * The operation can be reverted with g_strcompress() or + * nm_utils_str_utf8safe_unescape(). + * + * Depending on @flags, valid UTF-8 characters are not escaped at all + * (except the escape character '\\'). This is the difference to g_strescape(), + * which escapes all non-ASCII characters. This allows to pass on + * valid UTF-8 characters as-is and can be directly shown to the user + * as UTF-8 -- with exception of the backslash escape character, + * invalid UTF-8 sequences, and other (depending on @flags). + * + * Returns: the escaped input string, as valid UTF-8. If no escaping + * is necessary, it returns the input @str. Otherwise, an allocated + * string @to_free is returned which must be freed by the caller + * with g_free. The escaping can be reverted by g_strcompress(). + **/ +const char * +nm_utils_str_utf8safe_escape (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; +} + +const char * +nm_utils_str_utf8safe_unescape (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)); +} + +/** + * nm_utils_str_utf8safe_escape_cp: + * @str: NUL terminated input string, possibly in utf-8 encoding + * @flags: #NMUtilsStrUtf8SafeFlags flags + * + * Like nm_utils_str_utf8safe_escape(), except the returned value + * is always a copy of the input and must be freed by the caller. + * + * 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_cp (const char *str, NMUtilsStrUtf8SafeFlags flags) +{ + char *s; + + nm_utils_str_utf8safe_escape (str, flags, &s); + return s ?: g_strdup (str); +} + +char * +nm_utils_str_utf8safe_unescape_cp (const char *str) +{ + return str ? g_strcompress (str) : NULL; +} + +char * +nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags) +{ + char *str_to_free; + + nm_utils_str_utf8safe_escape (str, flags, &str_to_free); + if (str_to_free) { + g_free (str); + return str_to_free; + } + return str; +} diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h index 3776c1590e..438a7a9e7c 100644 --- a/shared/nm-utils/nm-shared-utils.h +++ b/shared/nm-utils/nm-shared-utils.h @@ -97,4 +97,20 @@ 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; + +const char *nm_utils_str_utf8safe_escape (const char *str, NMUtilsStrUtf8SafeFlags flags, char **to_free); +const char *nm_utils_str_utf8safe_unescape (const char *str, char **to_free); + +char *nm_utils_str_utf8safe_escape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags); +char *nm_utils_str_utf8safe_unescape_cp (const char *str); + +char *nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags); + +/*****************************************************************************/ + #endif /* __NM_SHARED_UTILS_H__ */ diff --git a/shared/nm-utils/nm-udev-utils.c b/shared/nm-utils/nm-udev-utils.c index bf0ad5b931..79d4426de9 100644 --- a/shared/nm-utils/nm-udev-utils.c +++ b/shared/nm-utils/nm-udev-utils.c @@ -67,8 +67,9 @@ nm_udev_utils_property_decode (const char *uproperty, char **to_free) if ( p[0] == '\\' && p[1] == 'x' && (a = g_ascii_xdigit_value (p[2])) >= 0 - && (b = g_ascii_xdigit_value (p[3])) >= 0) { - if (!unescaped) { + && (b = g_ascii_xdigit_value (p[3])) >= 0 + && (a || b)) { + if (!n) { gssize l = p - uproperty; unescaped = g_malloc (l + strlen (p) + 1 - 3); @@ -84,11 +85,12 @@ nm_udev_utils_property_decode (const char *uproperty, char **to_free) } } - if (!unescaped) { + if (!n) { *to_free = NULL; return uproperty; } + *n++ = '\0'; return (*to_free = unescaped); } diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 39106de9a0..af06a5d86a 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -2463,7 +2463,7 @@ device_link_changed (NMDevice *self) info = *pllink; udi = nm_platform_link_get_udi (nm_device_get_platform (self), info.ifindex); - if (udi && g_strcmp0 (udi, priv->udi)) { + if (udi && !nm_streq0 (udi, priv->udi)) { /* Update UDI to what udev gives us */ g_free (priv->udi); priv->udi = g_strdup (udi); @@ -2844,7 +2844,7 @@ update_device_from_platform_link (NMDevice *self, const NMPlatformLink *plink) g_return_if_fail (plink != NULL); udi = nm_platform_link_get_udi (nm_device_get_platform (self), plink->ifindex); - if (udi && !g_strcmp0 (udi, priv->udi)) { + if (udi && !nm_streq0 (udi, priv->udi)) { g_free (priv->udi); priv->udi = g_strdup (udi); _notify (self, PROP_UDI); @@ -13839,14 +13839,11 @@ set_property (GObject *object, guint prop_id, switch (prop_id) { case PROP_UDI: - if (g_value_get_string (value)) { - g_free (priv->udi); - priv->udi = g_value_dup_string (value); - } + /* construct-only */ + priv->udi = g_value_dup_string (value); break; case PROP_IFACE: /* construct-only */ - g_return_if_fail (!priv->iface); priv->iface = g_value_dup_string (value); break; case PROP_DRIVER: @@ -13941,28 +13938,43 @@ get_property (GObject *object, guint prop_id, switch (prop_id) { case PROP_UDI: - g_value_set_string (value, priv->udi); + /* UDI is (depending on the device type) a path to sysfs and can contain + * non-UTF-8. + * ip link add name $'d\xccf\\c' type dummy */ + g_value_take_string (value, + nm_utils_str_utf8safe_escape_cp (priv->udi, + NM_UTILS_STR_UTF8_SAFE_FLAG_NONE)); break; case PROP_IFACE: - g_value_set_string (value, priv->iface); + g_value_take_string (value, + nm_utils_str_utf8safe_escape_cp (priv->iface, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)); break; case PROP_IP_IFACE: - if (ip_config_valid (priv->state)) - g_value_set_string (value, nm_device_get_ip_iface (self)); - else + if (ip_config_valid (priv->state)) { + g_value_take_string (value, + nm_utils_str_utf8safe_escape_cp (nm_device_get_ip_iface (self), + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)); + } else g_value_set_string (value, NULL); break; case PROP_IFINDEX: g_value_set_int (value, priv->ifindex); break; case PROP_DRIVER: - g_value_set_string (value, priv->driver); + g_value_take_string (value, + nm_utils_str_utf8safe_escape_cp (priv->driver, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)); break; case PROP_DRIVER_VERSION: - g_value_set_string (value, priv->driver_version); + g_value_take_string (value, + nm_utils_str_utf8safe_escape_cp (priv->driver_version, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)); break; case PROP_FIRMWARE_VERSION: - g_value_set_string (value, priv->firmware_version); + g_value_take_string (value, + nm_utils_str_utf8safe_escape_cp (priv->firmware_version, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)); break; case PROP_CAPABILITIES: g_value_set_uint (value, (priv->capabilities & ~NM_DEVICE_CAP_INTERNAL_MASK)); @@ -14159,7 +14171,7 @@ nm_device_class_init (NMDeviceClass *klass) obj_properties[PROP_UDI] = g_param_spec_string (NM_DEVICE_UDI, "", "", NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT | + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); obj_properties[PROP_IFACE] = g_param_spec_string (NM_DEVICE_IFACE, "", "",