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, "", "",