summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2020-04-04 19:53:52 +0200
committerThomas Haller <thaller@redhat.com>2020-04-04 19:53:52 +0200
commit3670aa9bcd182505be1b88c055661ff06e4338e4 (patch)
tree43b401a0a9b65e252fc0b84b72ddec639c357930
parent306414b93d447e915b6de94b9b7410ff7aadc4cf (diff)
parentf28f06bad126a29fb285dd31ad46134dd3a1a48d (diff)
downloadNetworkManager-3670aa9bcd182505be1b88c055661ff06e4338e4.tar.gz
libnm,cli: merge branch 'th/libnm-setting-vpn-cleanup'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/issues/390 https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/453
-rw-r--r--NEWS4
-rw-r--r--clients/common/nm-meta-setting-desc.c161
-rw-r--r--libnm-core/nm-setting-vpn.c373
-rw-r--r--libnm-core/nm-utils.c48
-rw-r--r--libnm-core/tests/test-general.c346
-rw-r--r--shared/nm-glib-aux/nm-macros-internal.h11
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.c311
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.h144
-rw-r--r--shared/nm-utils/nm-test-utils.h63
9 files changed, 1118 insertions, 343 deletions
diff --git a/NEWS b/NEWS
index c503c50237..e90224d254 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,10 @@ The API is subject to change and not guaranteed to be compatible
with the later release.
USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE!
+* Accept empty values for VPN data items and secrets.
+* nmcli: support backslash escape sequences for vpn.data, vpn.secrets,
+ bond.options, and ethernet.s390-options.
+
=============================================
NetworkManager-1.22
Overview of changes since NetworkManager-1.20
diff --git a/clients/common/nm-meta-setting-desc.c b/clients/common/nm-meta-setting-desc.c
index c77c1367b7..27f1d2882b 100644
--- a/clients/common/nm-meta-setting-desc.c
+++ b/clients/common/nm-meta-setting-desc.c
@@ -190,8 +190,6 @@ _value_strsplit (const char *value,
gsize *out_len)
{
gs_free const char **strv = NULL;
- gsize i;
- gsize len;
/* FIXME: some modes should support backslash escaping.
* In particular, to distinguish from _value_str_as_index_list(), which
@@ -200,43 +198,24 @@ _value_strsplit (const char *value,
/* note that all modes remove empty tokens (",", "a,,b", ",,"). */
switch (split_mode) {
case VALUE_STRSPLIT_MODE_OBJLIST:
- strv = nm_utils_strsplit_set (value, ESCAPED_TOKENS_DELIMITERS);
+ strv = nm_utils_strsplit_set_full (value,
+ ESCAPED_TOKENS_DELIMITERS,
+ NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP);
break;
case VALUE_STRSPLIT_MODE_MULTILIST:
- strv = nm_utils_strsplit_set (value, ESCAPED_TOKENS_WITH_SPACES_DELIMTERS);
+ strv = nm_utils_strsplit_set_full (value,
+ ESCAPED_TOKENS_WITH_SPACES_DELIMTERS,
+ NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP);
break;
case VALUE_STRSPLIT_MODE_ESCAPED_TOKENS:
strv = nm_utils_escaped_tokens_split (value, ESCAPED_TOKENS_DELIMITERS);
- NM_SET_OUT (out_len, NM_PTRARRAY_LEN (strv));
- return g_steal_pointer (&strv);
+ break;
case VALUE_STRSPLIT_MODE_ESCAPED_TOKENS_WITH_SPACES:
strv = nm_utils_escaped_tokens_split (value, ESCAPED_TOKENS_WITH_SPACES_DELIMTERS);
- NM_SET_OUT (out_len, NM_PTRARRAY_LEN (strv));
- return g_steal_pointer (&strv);
- default:
- nm_assert_not_reached ();
break;
}
- NM_SET_OUT (out_len, 0);
-
- if (!strv)
- return NULL;
-
- len = 0;
- for (i = 0; strv[i]; i++) {
- const char *s = strv[i];
-
- s = nm_str_skip_leading_spaces (s);
- if (s[0] == '\0')
- continue;
-
- g_strchomp ((char *) s);
- strv[len++] = s;
- }
- strv[len] = NULL;
-
- NM_SET_OUT (out_len, len);
+ NM_SET_OUT (out_len, NM_PTRARRAY_LEN (strv));
return g_steal_pointer (&strv);
}
@@ -638,6 +617,8 @@ _env_warn_fcn (const NMMetaEnvironment *environment,
#define ARGS_SETTING_INIT_FCN \
const NMMetaSettingInfoEditor *setting_info, NMSetting *setting, NMMetaAccessorSettingInitType init_type
+static gboolean _set_fcn_optionlist (ARGS_SET_FCN);
+
static gboolean
_SET_FCN_DO_RESET_DEFAULT (const NMMetaPropertyInfo *property_info, NMMetaAccessorModifier modifier, const char *value)
{
@@ -904,6 +885,10 @@ _get_fcn_gobject_impl (const NMMetaPropertyInfo *property_info,
GString *str;
gsize i;
+ nm_assert ( property_info->setting_info == &nm_meta_setting_infos_editor[NM_META_SETTING_TYPE_WIRED]
+ && NM_IN_STRSET (property_info->property_name, NM_SETTING_WIRED_S390_OPTIONS));
+ nm_assert (property_info->property_type->set_fcn == _set_fcn_optionlist);
+
strdict = g_value_get_boxed (&val);
keys = nm_utils_strdict_get_keys (strdict, TRUE, NULL);
if (!keys)
@@ -911,12 +896,16 @@ _get_fcn_gobject_impl (const NMMetaPropertyInfo *property_info,
str = g_string_new (NULL);
for (i = 0; keys[i]; i++) {
+ const char *key = keys[i];
+ const char *v = g_hash_table_lookup (strdict, key);
+ gs_free char *escaped_key = NULL;
+ gs_free char *escaped_val = NULL;
+
if (str->len > 0)
g_string_append_c (str, ',');
- g_string_append_printf (str,
- "%s=%s",
- keys[i],
- (const char *) g_hash_table_lookup (strdict, keys[i]));
+ g_string_append (str, nm_utils_escaped_tokens_options_escape_key (key, &escaped_key));
+ g_string_append_c (str, '=');
+ g_string_append (str, nm_utils_escaped_tokens_options_escape_val (v, &escaped_val));
}
RETURN_STR_TO_FREE (g_string_free (str, FALSE));
}
@@ -1761,17 +1750,6 @@ secret_flags_to_string (guint32 flags, NMMetaAccessorGetType get_type)
return g_string_free (flag_str, FALSE);
}
-static void
-vpn_data_item (const char *key, const char *value, gpointer user_data)
-{
- GString *ret_str = (GString *) user_data;
-
- if (ret_str->len != 0)
- g_string_append (ret_str, ", ");
-
- g_string_append_printf (ret_str, "%s = %s", key, value);
-}
-
static const char *
_multilist_do_validate (const NMMetaPropertyInfo *property_info,
NMSetting *setting,
@@ -1919,6 +1897,7 @@ _set_fcn_optionlist (ARGS_SET_FCN)
{
gs_free const char **strv = NULL;
gs_free const char **strv_val = NULL;
+ gsize strv_len;
gsize i, nstrv;
nm_assert (!error || !*error);
@@ -1927,24 +1906,16 @@ _set_fcn_optionlist (ARGS_SET_FCN)
return _gobject_property_reset_default (setting, property_info->property_name);
nstrv = 0;
- strv = nm_utils_strsplit_set (value, ",");
+ strv = nm_utils_escaped_tokens_options_split_list (value);
if (strv) {
- strv_val = g_new (const char *, NM_PTRARRAY_LEN (strv));
+ strv_len = NM_PTRARRAY_LEN (strv);
+
+ strv_val = g_new (const char *, strv_len);
for (i = 0; strv[i]; i++) {
const char *opt_name;
const char *opt_value;
- opt_name = nm_str_skip_leading_spaces (strv[i]);
-
- /* FIXME: support backslash escaping for the option list. */
- opt_value = strchr (opt_name, '=');
- if (opt_value) {
- ((char *) opt_value)[0] = '\0';
- opt_value++;
- opt_value = nm_str_skip_leading_spaces (opt_value);
- g_strchomp ((char *) opt_value);
- }
- g_strchomp ((char *) opt_name);
+ nm_utils_escaped_tokens_options_split ((char *) strv[i], &opt_name, &opt_value);
if ( property_info->property_type->values_fcn
|| property_info->property_typ_data->values_static) {
@@ -2315,33 +2286,41 @@ static gconstpointer
_get_fcn_bond_options (ARGS_GET_FCN)
{
NMSettingBond *s_bond = NM_SETTING_BOND (setting);
- GString *bond_options_s;
- int i;
+ GString *str;
+ guint32 i, len;
RETURN_UNSUPPORTED_GET_TYPE ();
- bond_options_s = g_string_new (NULL);
- for (i = 0; i < nm_setting_bond_get_num_options (s_bond); i++) {
- const char *key, *value;
- gs_free char *tmp_value = NULL;
+ str = g_string_new (NULL);
+ len = nm_setting_bond_get_num_options (s_bond);
+ for (i = 0; i < len; i++) {
+ const char *key;
+ const char *val;
+ gs_free char *val_tmp = NULL;
char *p;
+ gs_free char *escaped_key = NULL;
+ gs_free char *escaped_val = NULL;
- nm_setting_bond_get_option (s_bond, i, &key, &value);
+ nm_setting_bond_get_option (s_bond, i, &key, &val);
- if (nm_streq0 (key, NM_SETTING_BOND_OPTION_ARP_IP_TARGET)) {
- value = tmp_value = g_strdup (value);
- for (p = tmp_value; p && *p; p++) {
+ if (nm_streq (key, NM_SETTING_BOND_OPTION_ARP_IP_TARGET)) {
+ val_tmp = g_strdup (val);
+ for (p = val_tmp; p && *p; p++) {
if (*p == ',')
*p = ' ';
}
+ val = val_tmp;
}
- g_string_append_printf (bond_options_s, "%s=%s,", key, value);
+ if (str->len > 0u)
+ g_string_append_c (str, ',');
+ g_string_append (str, nm_utils_escaped_tokens_options_escape_key (key, &escaped_key));
+ g_string_append_c (str, '=');
+ g_string_append (str, nm_utils_escaped_tokens_options_escape_val (val, &escaped_val));
}
- g_string_truncate (bond_options_s, bond_options_s->len-1); /* chop off trailing ',' */
- NM_SET_OUT (out_is_default, bond_options_s->len == 0);
- RETURN_STR_TO_FREE (g_string_free (bond_options_s, FALSE));
+ NM_SET_OUT (out_is_default, str->len == 0);
+ RETURN_STR_TO_FREE (g_string_free (str, FALSE));
}
static gboolean
@@ -3815,32 +3794,36 @@ _set_fcn_vlan_xgress_priority_map (ARGS_SET_FCN)
return TRUE;
}
-static gconstpointer
-_get_fcn_vpn_data (ARGS_GET_FCN)
+static void
+_vpn_options_callback (const char *key, const char *val, gpointer user_data)
{
- NMSettingVpn *s_vpn = NM_SETTING_VPN (setting);
- GString *data_item_str;
+ GString *str = user_data;
+ gs_free char *escaped_key = NULL;
+ gs_free char *escaped_val = NULL;
- RETURN_UNSUPPORTED_GET_TYPE ();
+ if (str->len > 0u)
+ g_string_append (str, ", ");
- data_item_str = g_string_new (NULL);
- nm_setting_vpn_foreach_data_item (s_vpn, &vpn_data_item, data_item_str);
- NM_SET_OUT (out_is_default, data_item_str->len == 0);
- RETURN_STR_TO_FREE (g_string_free (data_item_str, FALSE));
+ g_string_append (str, nm_utils_escaped_tokens_options_escape_key (key, &escaped_key));
+ g_string_append (str, " = ");
+ g_string_append (str, nm_utils_escaped_tokens_options_escape_val (val, &escaped_val));
}
static gconstpointer
-_get_fcn_vpn_secrets (ARGS_GET_FCN)
+_get_fcn_vpn_options (ARGS_GET_FCN)
{
NMSettingVpn *s_vpn = NM_SETTING_VPN (setting);
- GString *secret_str;
+ GString *str;
RETURN_UNSUPPORTED_GET_TYPE ();
- secret_str = g_string_new (NULL);
- nm_setting_vpn_foreach_secret (s_vpn, &vpn_data_item, secret_str);
- NM_SET_OUT (out_is_default, secret_str->len == 0);
- RETURN_STR_TO_FREE (g_string_free (secret_str, FALSE));
+ str = g_string_new (NULL);
+ if (nm_streq (property_info->property_name, NM_SETTING_VPN_SECRETS))
+ nm_setting_vpn_foreach_secret (s_vpn, _vpn_options_callback, str);
+ else
+ nm_setting_vpn_foreach_data_item (s_vpn, _vpn_options_callback, str);
+ NM_SET_OUT (out_is_default, str->len == 0);
+ RETURN_STR_TO_FREE (g_string_free (str, FALSE));
}
static gboolean
@@ -6884,25 +6867,23 @@ static const NMMetaPropertyInfo *const property_infos_VPN[] = {
),
PROPERTY_INFO_WITH_DESC (NM_SETTING_VPN_DATA,
.property_type = DEFINE_PROPERTY_TYPE (
- .get_fcn = _get_fcn_vpn_data,
+ .get_fcn = _get_fcn_vpn_options,
.set_fcn = _set_fcn_optionlist,
.set_supports_remove = TRUE,
),
.property_typ_data = DEFINE_PROPERTY_TYP_DATA_SUBTYPE (optionlist,
.set_fcn = _optionlist_set_fcn_vpn_data,
- .no_empty_value = TRUE,
),
),
PROPERTY_INFO_WITH_DESC (NM_SETTING_VPN_SECRETS,
.is_secret = TRUE,
.property_type = DEFINE_PROPERTY_TYPE (
- .get_fcn = _get_fcn_vpn_secrets,
+ .get_fcn = _get_fcn_vpn_options,
.set_fcn = _set_fcn_optionlist,
.set_supports_remove = TRUE,
),
.property_typ_data = DEFINE_PROPERTY_TYP_DATA_SUBTYPE (optionlist,
.set_fcn = _optionlist_set_fcn_vpn_secrets,
- .no_empty_value = TRUE,
),
),
PROPERTY_INFO_WITH_DESC (NM_SETTING_VPN_PERSISTENT,
diff --git a/libnm-core/nm-setting-vpn.c b/libnm-core/nm-setting-vpn.c
index 05fca918ff..baca3f3222 100644
--- a/libnm-core/nm-setting-vpn.c
+++ b/libnm-core/nm-setting-vpn.c
@@ -81,6 +81,23 @@ G_DEFINE_TYPE (NMSettingVpn, nm_setting_vpn, NM_TYPE_SETTING)
/*****************************************************************************/
+static GHashTable *
+_ensure_strdict (GHashTable **p_hash, gboolean for_secrets)
+{
+ if (!*p_hash) {
+ *p_hash = g_hash_table_new_full (nm_str_hash,
+ g_str_equal,
+ g_free,
+ for_secrets
+ ? (GDestroyNotify) nm_free_secret
+ : g_free);
+ }
+ return *p_hash;
+}
+
+
+/*****************************************************************************/
+
/**
* nm_setting_vpn_get_service_type:
* @setting: the #NMSettingVpn
@@ -139,30 +156,39 @@ nm_setting_vpn_get_num_data_items (NMSettingVpn *setting)
{
g_return_val_if_fail (NM_IS_SETTING_VPN (setting), 0);
- return g_hash_table_size (NM_SETTING_VPN_GET_PRIVATE (setting)->data);
+ return nm_g_hash_table_size (NM_SETTING_VPN_GET_PRIVATE (setting)->data);
}
/**
* nm_setting_vpn_add_data_item:
* @setting: the #NMSettingVpn
* @key: a name that uniquely identifies the given value @item
- * @item: the value to be referenced by @key
+ * @item: (allow-none): the value to be referenced by @key
*
* Establishes a relationship between @key and @item internally in the
* setting which may be retrieved later. Should not be used to store passwords
* or other secrets, which is what nm_setting_vpn_add_secret() is for.
+ *
+ * Before 1.24, @item must not be %NULL and not an empty string. Since 1.24,
+ * @item can be set to an empty string. It can also be set to %NULL to unset
+ * the key. In that case, the behavior is as if calling nm_setting_vpn_remove_data_item().
**/
void
nm_setting_vpn_add_data_item (NMSettingVpn *setting,
const char *key,
const char *item)
{
+ if (!item) {
+ nm_setting_vpn_remove_data_item (setting, key);
+ return;
+ }
+
g_return_if_fail (NM_IS_SETTING_VPN (setting));
g_return_if_fail (key && key[0]);
- g_return_if_fail (item && item[0]);
- g_hash_table_insert (NM_SETTING_VPN_GET_PRIVATE (setting)->data,
- g_strdup (key), g_strdup (item));
+ g_hash_table_insert (_ensure_strdict (&NM_SETTING_VPN_GET_PRIVATE (setting)->data, FALSE),
+ g_strdup (key),
+ g_strdup (item));
_notify (setting, PROP_DATA);
}
@@ -180,8 +206,9 @@ const char *
nm_setting_vpn_get_data_item (NMSettingVpn *setting, const char *key)
{
g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL);
+ g_return_val_if_fail (key && key[0], NULL);
- return (const char *) g_hash_table_lookup (NM_SETTING_VPN_GET_PRIVATE (setting)->data, key);
+ return nm_g_hash_table_lookup (NM_SETTING_VPN_GET_PRIVATE (setting)->data, key);
}
/**
@@ -222,63 +249,58 @@ nm_setting_vpn_get_data_keys (NMSettingVpn *setting,
gboolean
nm_setting_vpn_remove_data_item (NMSettingVpn *setting, const char *key)
{
- gboolean found;
-
g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE);
- g_return_val_if_fail (key, FALSE);
+ g_return_val_if_fail (key && key[0], FALSE);
- found = g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->data, key);
- if (found)
+ if (nm_g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->data, key)) {
_notify (setting, PROP_DATA);
- return found;
+ return TRUE;
+ }
+ return FALSE;
}
static void
foreach_item_helper (NMSettingVpn *self,
- gboolean is_secrets,
+ GHashTable **p_hash,
NMVpnIterFunc func,
gpointer user_data)
{
- NMSettingVpnPrivate *priv;
- guint len, i;
+ gs_unref_object NMSettingVpn *self_keep_alive = NULL;
gs_strfreev char **keys = NULL;
- GHashTable *hash;
+ guint i, len;
nm_assert (NM_IS_SETTING_VPN (self));
nm_assert (func);
- priv = NM_SETTING_VPN_GET_PRIVATE (self);
-
- if (is_secrets) {
- keys = (char **) nm_setting_vpn_get_secret_keys (self, &len);
- hash = priv->secrets;
- } else {
- keys = (char **) nm_setting_vpn_get_data_keys (self, &len);
- hash = priv->data;
- }
-
- if (!len) {
+ keys = nm_utils_strv_make_deep_copied (nm_utils_strdict_get_keys (*p_hash,
+ TRUE,
+ &len));
+ if (len == 0u) {
nm_assert (!keys);
return;
}
- for (i = 0; i < len; i++) {
- nm_assert (keys && keys[i]);
- keys[i] = g_strdup (keys[i]);
- }
- nm_assert (!keys[i]);
+ if (len > 1u)
+ self_keep_alive = g_object_ref (self);
for (i = 0; i < len; i++) {
- const char *value;
-
- value = g_hash_table_lookup (hash, keys[i]);
/* NOTE: note that we call the function with a clone of @key,
* not with the actual key from the dictionary.
*
- * The @value on the other hand, is actually inside our dictionary,
- * it's not a clone. However, it might be %NULL, in case the key was
- * deleted while iterating. */
- func (keys[i], value, user_data);
+ * The @value on the other hand, is not cloned but retrieved before
+ * invoking @func(). That means, if @func() modifies the setting while
+ * being called, the values are as they currently are, but the
+ * keys (and their order) were pre-determined before starting to
+ * invoke the callbacks.
+ *
+ * The idea is to give some sensible, stable behavior in case the user
+ * modifies the settings. Whether this particular behavior is optimal
+ * is unclear. It's probably a bad idea to modify the settings while
+ * iterating the values. But at least, it's a safe thing to do and we
+ * do something sensible. */
+ func (keys[i],
+ nm_g_hash_table_lookup (*p_hash, keys[i]),
+ user_data);
}
}
@@ -300,7 +322,7 @@ nm_setting_vpn_foreach_data_item (NMSettingVpn *setting,
g_return_if_fail (NM_IS_SETTING_VPN (setting));
g_return_if_fail (func);
- foreach_item_helper (setting, FALSE, func, user_data);
+ foreach_item_helper (setting, &NM_SETTING_VPN_GET_PRIVATE (setting)->data, func, user_data);
}
/**
@@ -316,29 +338,38 @@ nm_setting_vpn_get_num_secrets (NMSettingVpn *setting)
{
g_return_val_if_fail (NM_IS_SETTING_VPN (setting), 0);
- return g_hash_table_size (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets);
+ return nm_g_hash_table_size (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets);
}
/**
* nm_setting_vpn_add_secret:
* @setting: the #NMSettingVpn
* @key: a name that uniquely identifies the given secret @secret
- * @secret: the secret to be referenced by @key
+ * @secret: (allow-none): the secret to be referenced by @key
*
* Establishes a relationship between @key and @secret internally in the
* setting which may be retrieved later.
+ *
+ * Before 1.24, @secret must not be %NULL and not an empty string. Since 1.24,
+ * @secret can be set to an empty string. It can also be set to %NULL to unset
+ * the key. In that case, the behavior is as if calling nm_setting_vpn_remove_secret().
**/
void
nm_setting_vpn_add_secret (NMSettingVpn *setting,
const char *key,
const char *secret)
{
+ if (!secret) {
+ nm_setting_vpn_remove_secret (setting, key);
+ return;
+ }
+
g_return_if_fail (NM_IS_SETTING_VPN (setting));
g_return_if_fail (key && key[0]);
- g_return_if_fail (secret && secret[0]);
- g_hash_table_insert (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets,
- g_strdup (key), g_strdup (secret));
+ g_hash_table_insert (_ensure_strdict (&NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, TRUE),
+ g_strdup (key),
+ g_strdup (secret));
_notify (setting, PROP_SECRETS);
}
@@ -356,8 +387,9 @@ const char *
nm_setting_vpn_get_secret (NMSettingVpn *setting, const char *key)
{
g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL);
+ g_return_val_if_fail (key && key[0], NULL);
- return (const char *) g_hash_table_lookup (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, key);
+ return nm_g_hash_table_lookup (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, key);
}
/**
@@ -398,15 +430,14 @@ nm_setting_vpn_get_secret_keys (NMSettingVpn *setting,
gboolean
nm_setting_vpn_remove_secret (NMSettingVpn *setting, const char *key)
{
- gboolean found;
-
g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE);
- g_return_val_if_fail (key, FALSE);
+ g_return_val_if_fail (key && key[0], FALSE);
- found = g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, key);
- if (found)
+ if (nm_g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, key)) {
_notify (setting, PROP_SECRETS);
- return found;
+ return TRUE;
+ }
+ return FALSE;
}
/**
@@ -427,7 +458,7 @@ nm_setting_vpn_foreach_secret (NMSettingVpn *setting,
g_return_if_fail (NM_IS_SETTING_VPN (setting));
g_return_if_fail (func);
- foreach_item_helper (setting, TRUE, func, user_data);
+ foreach_item_helper (setting, &NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, func, user_data);
}
static gboolean
@@ -444,7 +475,7 @@ aggregate (NMSetting *setting,
switch (type) {
case NM_CONNECTION_AGGREGATE_ANY_SECRETS:
- if (g_hash_table_size (priv->secrets) > 0) {
+ if (nm_g_hash_table_size (priv->secrets) > 0u) {
*((gboolean *) arg) = TRUE;
return TRUE;
}
@@ -452,32 +483,36 @@ aggregate (NMSetting *setting,
case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS:
- g_hash_table_iter_init (&iter, priv->secrets);
- while (g_hash_table_iter_next (&iter, (gpointer *) &key_name, NULL)) {
- if (!nm_setting_get_secret_flags (NM_SETTING (setting), key_name, &secret_flags, NULL))
- nm_assert_not_reached ();
- if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) {
- *((gboolean *) arg) = TRUE;
- return TRUE;
+ if (priv->secrets) {
+ g_hash_table_iter_init (&iter, priv->secrets);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &key_name, NULL)) {
+ if (!nm_setting_get_secret_flags (NM_SETTING (setting), key_name, &secret_flags, NULL))
+ nm_assert_not_reached ();
+ if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) {
+ *((gboolean *) arg) = TRUE;
+ return TRUE;
+ }
}
}
- /* Ok, we have no secrets with system-secret flags.
+ /* OK, we have no secrets with system-secret flags.
* But do we have any secret-flags (without secrets) that indicate system secrets? */
- g_hash_table_iter_init (&iter, priv->data);
- while (g_hash_table_iter_next (&iter, (gpointer *) &key_name, NULL)) {
- gs_free char *secret_name = NULL;
+ if (priv->data) {
+ g_hash_table_iter_init (&iter, priv->data);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &key_name, NULL)) {
+ gs_free char *secret_name = NULL;
- if (!g_str_has_suffix (key_name, "-flags"))
- continue;
- secret_name = g_strndup (key_name, strlen (key_name) - NM_STRLEN ("-flags"));
- if (secret_name[0] == '\0')
- continue;
- if (!nm_setting_get_secret_flags (NM_SETTING (setting), secret_name, &secret_flags, NULL))
- nm_assert_not_reached ();
- if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) {
- *((gboolean *) arg) = TRUE;
- return TRUE;
+ if (!NM_STR_HAS_SUFFIX (key_name, "-flags"))
+ continue;
+ secret_name = g_strndup (key_name, strlen (key_name) - NM_STRLEN ("-flags"));
+ if (secret_name[0] == '\0')
+ continue;
+ if (!nm_setting_get_secret_flags (NM_SETTING (setting), secret_name, &secret_flags, NULL))
+ nm_assert_not_reached ();
+ if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) {
+ *((gboolean *) arg) = TRUE;
+ return TRUE;
+ }
}
}
@@ -517,8 +552,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, NM_SETTING_VPN_SERVICE_TYPE);
return FALSE;
}
-
- if (!strlen (priv->service_type)) {
+ if (!priv->service_type[0]) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -528,7 +562,8 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
}
/* default username can be NULL, but can't be zero-length */
- if (priv->user_name && !strlen (priv->user_name)) {
+ if ( priv->user_name
+ && !priv->user_name[0]) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -558,21 +593,15 @@ update_secret_string (NMSetting *setting,
{
NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting);
- g_return_val_if_fail (key != NULL, NM_SETTING_UPDATE_SECRET_ERROR);
- g_return_val_if_fail (value != NULL, NM_SETTING_UPDATE_SECRET_ERROR);
-
- if (!value || !strlen (value)) {
- g_set_error (error, NM_CONNECTION_ERROR,
- NM_CONNECTION_ERROR_INVALID_PROPERTY,
- _("secret was empty"));
- g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, key);
- return NM_SETTING_UPDATE_SECRET_ERROR;
- }
+ g_return_val_if_fail (key && key[0], NM_SETTING_UPDATE_SECRET_ERROR);
+ g_return_val_if_fail (value, NM_SETTING_UPDATE_SECRET_ERROR);
- if (g_strcmp0 (g_hash_table_lookup (priv->secrets, key), value) == 0)
+ if (nm_streq0 (nm_g_hash_table_lookup (priv->secrets, key), value))
return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;
- g_hash_table_insert (priv->secrets, g_strdup (key), g_strdup (value));
+ g_hash_table_insert (_ensure_strdict (&priv->secrets, TRUE),
+ g_strdup (key),
+ g_strdup (value));
return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
}
@@ -591,39 +620,24 @@ update_secret_dict (NMSetting *setting,
/* Make sure the items are valid */
g_variant_iter_init (&iter, secrets);
while (g_variant_iter_next (&iter, "{&s&s}", &name, &value)) {
- if (!name || !strlen (name)) {
+ if (!name[0]) {
g_set_error_literal (error, NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_SETTING,
_("setting contained a secret with an empty name"));
g_prefix_error (error, "%s: ", NM_SETTING_VPN_SETTING_NAME);
return NM_SETTING_UPDATE_SECRET_ERROR;
}
-
- if (!value || !strlen (value)) {
- g_set_error (error, NM_CONNECTION_ERROR,
- NM_CONNECTION_ERROR_INVALID_PROPERTY,
- _("secret value was empty"));
- g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, name);
- return NM_SETTING_UPDATE_SECRET_ERROR;
- }
}
/* Now add the items to the settings' secrets list */
g_variant_iter_init (&iter, secrets);
while (g_variant_iter_next (&iter, "{&s&s}", &name, &value)) {
- if (value == NULL) {
- g_warn_if_fail (value != NULL);
- continue;
- }
- if (strlen (value) == 0) {
- g_warn_if_fail (strlen (value) > 0);
+ if (nm_streq0 (nm_g_hash_table_lookup (priv->secrets, name), value))
continue;
- }
- if (g_strcmp0 (g_hash_table_lookup (priv->secrets, name), value) == 0)
- continue;
-
- g_hash_table_insert (priv->secrets, g_strdup (name), g_strdup (value));
+ g_hash_table_insert (_ensure_strdict (&priv->secrets, TRUE),
+ g_strdup (name),
+ g_strdup (value));
result = NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
}
@@ -646,7 +660,7 @@ update_one_secret (NMSetting *setting, const char *key, GVariant *value, GError
*/
success = update_secret_string (setting, key, g_variant_get_string (value, NULL), error);
} else if (g_variant_is_of_type (value, G_VARIANT_TYPE ("a{ss}"))) {
- if (strcmp (key, NM_SETTING_VPN_SECRETS) != 0) {
+ if (!nm_streq (key, NM_SETTING_VPN_SECRETS)) {
g_set_error_literal (error, NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
_("not a secret property"));
@@ -728,14 +742,23 @@ get_secret_flags (NMSetting *setting,
const char *flags_val;
gint64 i64;
+ nm_assert (secret_name);
+
+ if (!secret_name[0]) {
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+ _("secret name cannot be empty"));
+ return FALSE;
+ }
+
flags_key = nm_construct_name_a ("%s-flags", secret_name, &flags_key_free);
- if (!g_hash_table_lookup_extended (priv->data, flags_key, NULL, (gpointer *) &flags_val)) {
+ if ( !priv->data
+ || !g_hash_table_lookup_extended (priv->data, flags_key, NULL, (gpointer *) &flags_val)) {
NM_SET_OUT (out_flags, NM_SETTING_SECRET_FLAG_NONE);
/* having no secret flag for the secret is fine, as long as there
* is the secret itself... */
- if (!g_hash_table_contains (priv->secrets, secret_name)) {
+ if (!nm_g_hash_table_lookup (priv->secrets, secret_name)) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
@@ -768,7 +791,15 @@ set_secret_flags (NMSetting *setting,
NMSettingSecretFlags flags,
GError **error)
{
- g_hash_table_insert (NM_SETTING_VPN_GET_PRIVATE (setting)->data,
+ nm_assert (secret_name);
+
+ if (!secret_name[0]) {
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+ _("secret name cannot be empty"));
+ return FALSE;
+ }
+
+ g_hash_table_insert (_ensure_strdict (&NM_SETTING_VPN_GET_PRIVATE (setting)->data, FALSE),
g_strdup_printf ("%s-flags", secret_name),
g_strdup_printf ("%u", flags));
_notify (NM_SETTING_VPN (setting), PROP_SECRETS);
@@ -802,8 +833,12 @@ compare_property_secrets (NMSettingVpn *a,
for (run = 0; run < 2; run++) {
NMSettingVpn *current_a = (run == 0) ? a : b;
NMSettingVpn *current_b = (run == 0) ? b : a;
+ NMSettingVpnPrivate *priv_a = NM_SETTING_VPN_GET_PRIVATE (current_a);
+
+ if (!priv_a->secrets)
+ continue;
- g_hash_table_iter_init (&iter, NM_SETTING_VPN_GET_PRIVATE (current_a)->secrets);
+ g_hash_table_iter_init (&iter, priv_a->secrets);
while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &val)) {
if (nm_streq0 (val, nm_setting_vpn_get_secret (current_b, key)))
@@ -899,11 +934,26 @@ vpn_secrets_from_dbus (NMSetting *setting,
NMSettingParseFlags parse_flags,
GError **error)
{
- nm_auto_unset_gvalue GValue object_value = G_VALUE_INIT;
+ NMSettingVpn *self = NM_SETTING_VPN (setting);
+ NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (self);
+ gs_unref_hashtable GHashTable *hash_free = NULL;
+ GVariantIter iter;
+ const char *key;
+ const char *val;
+
+ hash_free = g_steal_pointer (&priv->secrets);
+
+ g_variant_iter_init (&iter, value);
+ while (g_variant_iter_next (&iter, "{&s&s}", &key, &val)) {
+ if (!key[0])
+ continue;
+ g_hash_table_insert (_ensure_strdict (&priv->secrets, TRUE),
+ g_strdup (key),
+ g_strdup (val));
+ }
- g_value_init (&object_value, G_TYPE_HASH_TABLE);
- _nm_utils_strdict_from_dbus (value, &object_value);
- return nm_g_object_set_property (G_OBJECT (setting), property, &object_value, error);
+ _notify (self, PROP_SECRETS);
+ return TRUE;
}
static GVariant *
@@ -914,29 +964,30 @@ vpn_secrets_to_dbus (const NMSettInfoSetting *sett_info,
NMConnectionSerializationFlags flags,
const NMConnectionSerializationOptions *options)
{
- gs_unref_hashtable GHashTable *secrets = NULL;
- const char *property_name = sett_info->property_infos[property_idx].name;
+ NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting);
GVariantBuilder builder;
- GHashTableIter iter;
- const char *key, *value;
- NMSettingSecretFlags secret_flags;
+ gs_free const char **keys = NULL;
+ guint i, len;
if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_NO_SECRETS))
return NULL;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
- g_object_get (setting, property_name, &secrets, NULL);
-
- if (secrets) {
- g_hash_table_iter_init (&iter, secrets);
- while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) {
- if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED)) {
- if ( !nm_setting_get_secret_flags (setting, key, &secret_flags, NULL)
- || !NM_FLAGS_HAS (secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED))
- continue;
- }
- g_variant_builder_add (&builder, "{ss}", key, value);
+
+ keys = nm_utils_strdict_get_keys (priv->secrets, TRUE, &len);
+ for (i = 0; i < len; i++) {
+ const char *key = keys[i];
+ NMSettingSecretFlags secret_flags;
+
+ if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED)) {
+ if ( !nm_setting_get_secret_flags (setting, key, &secret_flags, NULL)
+ || !NM_FLAGS_HAS (secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED))
+ continue;
}
+ g_variant_builder_add (&builder,
+ "{ss}",
+ key,
+ g_hash_table_lookup (priv->secrets, key));
}
return g_variant_builder_end (&builder);
@@ -995,12 +1046,42 @@ set_property (GObject *object, guint prop_id,
priv->persistent = g_value_get_boolean (value);
break;
case PROP_DATA:
- g_hash_table_unref (priv->data);
- priv->data = _nm_utils_copy_strdict (g_value_get_boxed (value));
- break;
- case PROP_SECRETS:
- g_hash_table_unref (priv->secrets);
- priv->secrets = _nm_utils_copy_strdict (g_value_get_boxed (value));
+ case PROP_SECRETS: {
+ gs_unref_hashtable GHashTable *hash_free = NULL;
+ GHashTable *src_hash = g_value_get_boxed (value);
+ GHashTable **p_hash;
+ const gboolean is_secrets = (prop_id == PROP_SECRETS);
+
+ if (is_secrets)
+ p_hash = &priv->secrets;
+ else
+ p_hash = &priv->data;
+
+ hash_free = g_steal_pointer (p_hash);
+
+ if ( src_hash
+ && g_hash_table_size (src_hash) > 0) {
+ GHashTableIter iter;
+ const char *key;
+ const char *val;
+
+ g_hash_table_iter_init (&iter, src_hash);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) {
+ if ( !key
+ || !key[0]
+ || !val) {
+ /* NULL keys/values and empty key are not allowed. Usually, we would reject them in verify(), but
+ * then our nm_setting_vpn_remove_data_item() also doesn't allow empty keys. So, if we failed
+ * it in verify(), it would be only fixable by setting PROP_DATA again. Instead,
+ * silently ignore them. */
+ continue;
+ }
+ g_hash_table_insert (_ensure_strdict (p_hash, is_secrets),
+ g_strdup (key),
+ g_strdup (val));
+ }
+ }
+ }
break;
case PROP_TIMEOUT:
priv->timeout = g_value_get_uint (value);
@@ -1016,10 +1097,6 @@ set_property (GObject *object, guint prop_id,
static void
nm_setting_vpn_init (NMSettingVpn *setting)
{
- NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting);
-
- priv->data = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
- priv->secrets = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) nm_free_secret);
}
/**
@@ -1042,8 +1119,10 @@ finalize (GObject *object)
g_free (priv->service_type);
g_free (priv->user_name);
- g_hash_table_destroy (priv->data);
- g_hash_table_destroy (priv->secrets);
+ if (priv->data)
+ g_hash_table_unref (priv->data);
+ if (priv->secrets)
+ g_hash_table_unref (priv->secrets);
G_OBJECT_CLASS (nm_setting_vpn_parent_class)->finalize (object);
}
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c
index 24d9dfcb43..2cfaea6dd4 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -802,53 +802,7 @@ _nm_utils_hash_values_to_slist (GHashTable *hash)
static GVariant *
_nm_utils_strdict_to_dbus (const GValue *prop_value)
{
- GHashTable *hash;
- GHashTableIter iter;
- const char *key, *value;
- GVariantBuilder builder;
- guint i, len;
-
- g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
-
- hash = g_value_get_boxed (prop_value);
- if (!hash)
- goto out;
- len = g_hash_table_size (hash);
- if (!len)
- goto out;
-
- g_hash_table_iter_init (&iter, hash);
- if (!g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value))
- nm_assert_not_reached ();
-
- if (len == 1)
- g_variant_builder_add (&builder, "{ss}", key, value);
- else {
- gs_free NMUtilsNamedValue *idx_free = NULL;
- NMUtilsNamedValue *idx;
-
- if (len > 300 / sizeof (NMUtilsNamedValue)) {
- idx_free = g_new (NMUtilsNamedValue, len);
- idx = idx_free;
- } else
- idx = g_alloca (sizeof (NMUtilsNamedValue) * len);
-
- i = 0;
- do {
- idx[i].name = key;
- idx[i].value_str = value;
- i++;
- } while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value));
- nm_assert (i == len);
-
- nm_utils_named_value_list_sort (idx, len, NULL, NULL);
-
- for (i = 0; i < len; i++)
- g_variant_builder_add (&builder, "{ss}", idx[i].name, idx[i].value_str);
- }
-
-out:
- return g_variant_builder_end (&builder);
+ return nm_utils_strdict_to_variant_ass (g_value_get_boxed (prop_value));
}
void
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index e0d0d55fa2..138702e4ca 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -659,6 +659,333 @@ test_nm_utils_strsplit_set (void)
/*****************************************************************************/
+static char *
+_escaped_tokens_create_random_word_full (const char *const*tokens,
+ gsize n_tokens,
+ gsize len)
+{
+ GString *gstr = g_string_new (NULL);
+ gsize i;
+ char random_token[2] = { 0 };
+
+ for (i = 0; i < len; i++) {
+ const char *token = tokens[nmtst_get_rand_uint32 () % n_tokens];
+
+ if (!token[0]) {
+ do {
+ random_token[0] = nmtst_get_rand_uint32 ();
+ } while (random_token[0] == '\0');
+ token = random_token;
+ }
+ g_string_append (gstr, token);
+ }
+
+ /* reallocate the string, so that we don't have any excess memory from
+ * the GString buffer. This is so that valgrind may better detect an out
+ * or range access. */
+ return nm_str_realloc (g_string_free (gstr, FALSE));
+}
+
+/* set to 1 to exclude characters that are annoying to see in the debugger
+ * and printf() output. */
+#define ESCAPED_TOKENS_ONLY_NICE_CHARS 0
+
+static char *
+_escaped_tokens_create_random_whitespace (void)
+{
+ static const char *tokens[] = {
+ " ",
+#if !ESCAPED_TOKENS_ONLY_NICE_CHARS
+ "\n",
+ "\t",
+ "\r",
+ "\f",
+#endif
+ };
+
+ return _escaped_tokens_create_random_word_full (tokens, G_N_ELEMENTS (tokens), nmtst_get_rand_word_length (NULL) / 4u);
+}
+
+static char *
+_escaped_tokens_create_random_word (void)
+{
+ static const char *tokens[] = {
+ "a",
+ "b",
+ "c",
+ " ",
+ ",",
+ "=",
+ "\\",
+#if !ESCAPED_TOKENS_ONLY_NICE_CHARS
+ "\n",
+ "\f",
+ ":",
+ "",
+#endif
+ };
+
+ return _escaped_tokens_create_random_word_full (tokens, G_N_ELEMENTS (tokens), nmtst_get_rand_word_length (NULL));
+}
+
+static void
+_escaped_tokens_str_append_delimiter (GString *str,
+ gboolean strict,
+ gboolean needs_delimiter)
+{
+ guint len = nmtst_get_rand_word_length (NULL) / 10u;
+ char *s;
+
+again:
+ if (!strict) {
+ g_string_append (str, (s = _escaped_tokens_create_random_whitespace ()));
+ nm_clear_g_free (&s);
+ }
+
+ if (needs_delimiter)
+ g_string_append_c (str, ',');
+
+ if (!strict) {
+ g_string_append (str, (s = _escaped_tokens_create_random_whitespace ()));
+ nm_clear_g_free (&s);
+ if (len-- > 0) {
+ needs_delimiter = TRUE;
+ goto again;
+ }
+ }
+}
+
+static void
+_escaped_tokens_split (char *str, const char **out_key, const char **out_val)
+{
+ const char *key;
+ const char *val;
+ gsize len = strlen (str);
+
+ g_assert (str);
+
+ nm_utils_escaped_tokens_options_split (str, &key, &val);
+ g_assert (key);
+ g_assert (key == str);
+ if (val) {
+ g_assert (val > str);
+ g_assert (val > key);
+ g_assert (val <= &str[len]);
+ }
+ NM_SET_OUT (out_key, key);
+ NM_SET_OUT (out_val, val);
+}
+
+static void
+_escaped_tokens_combine (GString *combined,
+ const char *key,
+ const char *val,
+ gboolean strict,
+ gboolean allow_append_delimiter_before,
+ gboolean needs_delimiter_after)
+{
+ gs_free char *escaped_key = NULL;
+ gs_free char *escaped_val = NULL;
+
+ if (allow_append_delimiter_before)
+ _escaped_tokens_str_append_delimiter (combined, strict, FALSE);
+ g_string_append (combined, nm_utils_escaped_tokens_options_escape_key (key, &escaped_key));
+ if (val) {
+ char *s;
+
+ if (!strict) {
+ g_string_append (combined, (s = _escaped_tokens_create_random_whitespace ()));
+ nm_clear_g_free (&s);
+ }
+ g_string_append_c (combined, '=');
+ if (!strict) {
+ g_string_append (combined, (s = _escaped_tokens_create_random_whitespace ()));
+ nm_clear_g_free (&s);
+ }
+ g_string_append (combined, nm_utils_escaped_tokens_options_escape_val (val, &escaped_val));
+ }
+ _escaped_tokens_str_append_delimiter (combined, strict, needs_delimiter_after);
+}
+
+static void
+_escaped_tokens_check_one_impl (const char *expected_key,
+ const char *expected_val,
+ const char *expected_combination,
+ const char *const*other,
+ gsize n_other)
+{
+ nm_auto_free_gstring GString *combined = g_string_new (NULL);
+ gsize i;
+
+ g_assert (expected_key);
+ g_assert (expected_combination);
+ g_assert (other);
+
+ _escaped_tokens_combine (combined,
+ expected_key,
+ expected_val,
+ TRUE,
+ TRUE,
+ FALSE);
+
+ g_assert_cmpstr (combined->str, ==, expected_combination);
+
+ for (i = 0; i < n_other + 2u; i++) {
+ nm_auto_free_gstring GString *str0 = NULL;
+ gs_free const char **strv_split = NULL;
+ gs_free char *strv_split0 = NULL;
+ const char *comb;
+ const char *key;
+ const char *val;
+
+ if (i == 0)
+ comb = expected_combination;
+ else if (i == 1) {
+ _escaped_tokens_combine (nm_gstring_prepare (&str0),
+ expected_key,
+ expected_val,
+ FALSE,
+ TRUE,
+ FALSE);
+ comb = str0->str;
+ } else
+ comb = other[i - 2];
+
+ strv_split = nm_utils_escaped_tokens_options_split_list (comb);
+ if (!strv_split) {
+ g_assert_cmpstr (expected_key, ==, "");
+ g_assert_cmpstr (expected_val, ==, NULL);
+ continue;
+ }
+ g_assert (expected_val || expected_key[0]);
+
+ g_assert_cmpuint (NM_PTRARRAY_LEN (strv_split), ==, 1u);
+
+ strv_split0 = g_strdup (strv_split[0]);
+
+ _escaped_tokens_split (strv_split0, &key, &val);
+ g_assert_cmpstr (key, ==, expected_key);
+ g_assert_cmpstr (val, ==, expected_val);
+ }
+}
+
+#define _escaped_tokens_check_one(expected_key, expected_val, expected_combination, ...) \
+ _escaped_tokens_check_one_impl (expected_key, expected_val, expected_combination, NM_MAKE_STRV (__VA_ARGS__), NM_NARG (__VA_ARGS__))
+
+static void
+test_nm_utils_escaped_tokens (void)
+{
+ int i_run;
+
+ for (i_run = 0; i_run < 1000; i_run++) {
+ const guint num_options = nmtst_get_rand_word_length (NULL);
+ gs_unref_ptrarray GPtrArray *options = g_ptr_array_new_with_free_func (g_free);
+ nm_auto_free_gstring GString *combined = g_string_new (NULL);
+ gs_free const char **strv_split = NULL;
+ guint i_option;
+ guint i;
+
+ /* Generate a list of random words for option key-value pairs. */
+ for (i_option = 0; i_option < 2u * num_options; i_option++) {
+ char *word = NULL;
+
+ if ( i_option % 2u == 1
+ && nmtst_get_rand_uint32 () % 5 == 0
+ && strlen (options->pdata[options->len - 1]) > 0u) {
+ /* For some options, leave the value unset and only generate a key.
+ *
+ * If key is "", then we cannot do that, because the test below would try
+ * to append "" to the combined list, which the parser then would drop.
+ * Only test omitting the value, if strlen() of the key is positive. */
+ } else
+ word = _escaped_tokens_create_random_word ();
+ g_ptr_array_add (options, word);
+ }
+
+ /* Combine the options in one comma separated list, with proper escaping. */
+ for (i_option = 0; i_option < num_options; i_option++) {
+ _escaped_tokens_combine (combined,
+ options->pdata[2u*i_option + 0u],
+ options->pdata[2u*i_option + 1u],
+ FALSE,
+ i_option == 0,
+ i_option != num_options - 1);
+ }
+
+ /* ensure that we can split and parse the options without difference. */
+ strv_split = nm_utils_escaped_tokens_options_split_list (combined->str);
+ for (i_option = 0; i_option < num_options; i_option++) {
+ const char *expected_key = options->pdata[2u*i_option + 0u];
+ const char *expected_val = options->pdata[2u*i_option + 1u];
+ gs_free char *s_split = i_option < NM_PTRARRAY_LEN (strv_split) ? g_strdup (strv_split[i_option]) : NULL;
+ const char *key = NULL;
+ const char *val = NULL;
+
+ if (s_split)
+ _escaped_tokens_split (s_split, &key, &val);
+
+ if ( !nm_streq0 (key, expected_key)
+ || !nm_streq0 (val, expected_val)) {
+ g_print (">>> ASSERTION IS ABOUT TO FAIL for item %5d of %5d\n", i_option, num_options);
+ g_print (">>> combined = \"%s\"\n", combined->str);
+ g_print (">>> %c parsed[%5d].key = \"%s\"\n", nm_streq (key, expected_key) ? ' ' : 'X', i_option, key);
+ g_print (">>> %c parsed[%5d].val = %s%s%s\n", nm_streq0 (val, expected_val) ? ' ' : 'X', i_option, NM_PRINT_FMT_QUOTE_STRING (val));
+ for (i = 0; i < num_options; i++) {
+ g_print (">>> %c original[%5d].key = \"%s\"\n", i == i_option ? '*' : ' ', i, (char *) options->pdata[2u*i + 0u]);
+ g_print (">>> %c original[%5d].val = %s%s%s\n", i == i_option ? '*' : ' ', i, NM_PRINT_FMT_QUOTE_STRING ((char *) options->pdata[2u*i + 1u]));
+ }
+ for (i = 0; i < NM_PTRARRAY_LEN (strv_split); i++)
+ g_print (">>> split[%5d] = \"%s\"\n", i, strv_split[i]);
+ }
+
+ g_assert_cmpstr (key, ==, expected_key);
+ g_assert_cmpstr (val, ==, expected_val);
+ }
+ g_assert_cmpint (NM_PTRARRAY_LEN (strv_split), ==, num_options);
+
+ /* Above we show a full round-trip of random option key-value pairs, that they can
+ * without loss escape, concatenate, split-list, and split. This proofed that every
+ * option key-value pair can be represented as a combined string and parsed back.
+ *
+ * Now, just check that we can also parse arbitrary random words in nm_utils_escaped_tokens_options_split().
+ * split() is a non-injective surjective function. As we check the round-trip above for random words, where
+ * options-split() is the last step, we show that every random word can be the output of the function
+ * (which shows, the surjective part).
+ *
+ * But multiple random input arguments, may map to the same output argument (non-injective).
+ * Just test whether we can handle random input words without crashing. For that, just use the
+ * above generate list of random words.
+ */
+ for (i = 0; i < 1u + 2u * i_option; i++) {
+ gs_free char *str = NULL;
+ const char *cstr;
+
+ if (i == 0)
+ cstr = combined->str;
+ else
+ cstr = options->pdata[i - 1u];
+ if (!cstr)
+ continue;
+
+ str = g_strdup (cstr);
+ _escaped_tokens_split (str, NULL, NULL);
+ }
+ }
+
+ _escaped_tokens_check_one ("", NULL, "");
+ _escaped_tokens_check_one ("", "", "=", " =");
+ _escaped_tokens_check_one ("a", "b", "a=b", "a = b");
+ _escaped_tokens_check_one ("a\\=", "b\\=", "a\\\\\\==b\\\\=", "a\\\\\\==b\\\\\\=");
+ _escaped_tokens_check_one ("\\=", "\\=", "\\\\\\==\\\\=", "\\\\\\==\\\\\\=");
+ _escaped_tokens_check_one (" ", "bb=", "\\ =bb=", "\\ =bb\\=");
+ _escaped_tokens_check_one (" ", "bb\\=", "\\ =bb\\\\=", "\\ =bb\\\\\\=");
+ _escaped_tokens_check_one ("a b", "a b", "a b=a b");
+ _escaped_tokens_check_one ("a b", "a b", "a b=a b");
+ _escaped_tokens_check_one ("a = b", "a = b", "a \\= b=a = b", "a \\= b=a \\= b");
+}
+
+/*****************************************************************************/
+
typedef struct {
int val;
CList lst;
@@ -1172,13 +1499,11 @@ test_setting_vpn_items (void)
nm_setting_vpn_add_data_item (s_vpn, "", "");
g_test_assert_expected_messages ();
- NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (item && item[0]));
- nm_setting_vpn_add_data_item (s_vpn, "foobar1", NULL);
- g_test_assert_expected_messages ();
-
- NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (item && item[0]));
nm_setting_vpn_add_data_item (s_vpn, "foobar1", "");
- g_test_assert_expected_messages ();
+ g_assert_cmpstr (nm_setting_vpn_get_data_item (s_vpn, "foobar1"), ==, "");
+
+ nm_setting_vpn_add_data_item (s_vpn, "foobar1", NULL);
+ g_assert_cmpstr (nm_setting_vpn_get_data_item (s_vpn, "foobar1"), ==, NULL);
NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0]));
nm_setting_vpn_add_data_item (s_vpn, NULL, "blahblah1");
@@ -1199,13 +1524,9 @@ test_setting_vpn_items (void)
nm_setting_vpn_add_secret (s_vpn, "", "");
g_test_assert_expected_messages ();
- NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (secret && secret[0]));
- nm_setting_vpn_add_secret (s_vpn, "foobar1", NULL);
- g_test_assert_expected_messages ();
-
- NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (secret && secret[0]));
nm_setting_vpn_add_secret (s_vpn, "foobar1", "");
- g_test_assert_expected_messages ();
+
+ nm_setting_vpn_add_secret (s_vpn, "foobar1", NULL);
NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0]));
nm_setting_vpn_add_secret (s_vpn, NULL, "blahblah1");
@@ -8559,6 +8880,7 @@ int main (int argc, char **argv)
g_test_add_func ("/core/general/test_dedup_multi", test_dedup_multi);
g_test_add_func ("/core/general/test_utils_str_utf8safe", test_utils_str_utf8safe);
g_test_add_func ("/core/general/test_nm_utils_strsplit_set", test_nm_utils_strsplit_set);
+ g_test_add_func ("/core/general/test_nm_utils_escaped_tokens", test_nm_utils_escaped_tokens);
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-glib-aux/nm-macros-internal.h b/shared/nm-glib-aux/nm-macros-internal.h
index 758a4ec9aa..9e3735d477 100644
--- a/shared/nm-glib-aux/nm-macros-internal.h
+++ b/shared/nm-glib-aux/nm-macros-internal.h
@@ -1571,6 +1571,17 @@ nm_strstrip_avoid_copy (const char *str, char **str_free)
_str_ssac; \
})
+static inline gboolean
+nm_str_is_stripped (const char *str)
+{
+ if (str && str[0]) {
+ if ( g_ascii_isspace (str[0])
+ || g_ascii_isspace (str[strlen (str) - 1]))
+ return FALSE;
+ }
+ return TRUE;
+}
+
/* g_ptr_array_sort()'s compare function takes pointers to the
* value. Thus, you cannot use strcmp directly. You can use
* nm_strcmp_p().
diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c
index 14e32956db..0e971a7399 100644
--- a/shared/nm-glib-aux/nm-shared-utils.c
+++ b/shared/nm-glib-aux/nm-shared-utils.c
@@ -444,6 +444,62 @@ nm_utils_gbytes_to_variant_ay (GBytes *bytes)
/*****************************************************************************/
+/* Convert a hash table with "char *" keys and values to an "a{ss}" GVariant.
+ * The keys will be sorted asciibetically.
+ * Returns a floating reference.
+ */
+GVariant *
+nm_utils_strdict_to_variant_ass (GHashTable *strdict)
+{
+ GHashTableIter iter;
+ const char *key, *value;
+ GVariantBuilder builder;
+ guint i, len;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
+
+ if (!strdict)
+ goto out;
+ len = g_hash_table_size (strdict);
+ if (!len)
+ goto out;
+
+ g_hash_table_iter_init (&iter, strdict);
+ if (!g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value))
+ nm_assert_not_reached ();
+
+ if (len == 1)
+ g_variant_builder_add (&builder, "{ss}", key, value);
+ else {
+ gs_free NMUtilsNamedValue *idx_free = NULL;
+ NMUtilsNamedValue *idx;
+
+ if (len > 300 / sizeof (NMUtilsNamedValue)) {
+ idx_free = g_new (NMUtilsNamedValue, len);
+ idx = idx_free;
+ } else
+ idx = g_alloca (sizeof (NMUtilsNamedValue) * len);
+
+ i = 0;
+ do {
+ idx[i].name = key;
+ idx[i].value_str = value;
+ i++;
+ } while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value));
+ nm_assert (i == len);
+
+ nm_utils_named_value_list_sort (idx, len, NULL, NULL);
+
+ for (i = 0; i < len; i++)
+ g_variant_builder_add (&builder, "{ss}", idx[i].name, idx[i].value_str);
+ }
+
+out:
+ return g_variant_builder_end (&builder);
+}
+
+/*****************************************************************************/
+
/**
* nm_strquote:
* @buf: the output buffer of where to write the quoted @str argument.
@@ -1441,12 +1497,27 @@ comp_l:
/*****************************************************************************/
static void
+_char_lookup_table_set_one (guint8 lookup[static 256],
+ char ch)
+{
+ lookup[(guint8) ch] = 1;
+}
+
+static void
+_char_lookup_table_set_all (guint8 lookup[static 256],
+ const char *candidates)
+{
+ while (candidates[0] != '\0')
+ _char_lookup_table_set_one (lookup, (candidates++)[0]);
+}
+
+static void
_char_lookup_table_init (guint8 lookup[static 256],
const char *candidates)
{
memset (lookup, 0, 256);
- while (candidates[0] != '\0')
- lookup[(guint8) ((candidates++)[0])] = 1;
+ if (candidates)
+ _char_lookup_table_set_all (lookup, candidates);
}
static gboolean
@@ -1457,6 +1528,19 @@ _char_lookup_has (const guint8 lookup[static 256],
return lookup[(guint8) ch] != 0;
}
+static gboolean
+_char_lookup_has_all (const guint8 lookup[static 256],
+ const char *candidates)
+{
+ if (candidates) {
+ while (candidates[0] != '\0') {
+ if (!_char_lookup_has (lookup, (candidates++)[0]))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
/**
* nm_utils_strsplit_set_full:
* @str: the string to split.
@@ -1469,18 +1553,7 @@ _char_lookup_has (const guint8 lookup[static 256],
*
* Note that for @str %NULL and "", this always returns %NULL too. That differs
* from g_strsplit_set(), which would return an empty strv array for "".
- *
- * Note that g_strsplit_set() returns empty words as well. By default,
- * nm_utils_strsplit_set_full() strips all empty tokens (that is, repeated
- * delimiters. With %NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, empty tokens
- * are not removed.
- *
- * If @flags has %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING, delimiters prefixed
- * by a backslash are not treated as a separator. Such delimiters and their escape
- * character are copied to the current word without unescaping them. In general,
- * nm_utils_strsplit_set_full() does not remove any backslash escape characters
- * and does not unescaping. It only considers them for skipping to split at
- * an escaped delimiter.
+ * This never returns an empty array.
*
* Returns: %NULL if @str is %NULL or "".
* If @str only contains delimiters and %NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY
@@ -1490,7 +1563,7 @@ _char_lookup_has (const guint8 lookup[static 256],
* The strings to which the result strv array points to are allocated
* after the returned result itself. Don't free the strings themself,
* but free everything with g_free().
- * It is however safe and allowed to modify the indiviual strings,
+ * It is however safe and allowed to modify the individual strings in-place,
* like "g_strstrip((char *) iter[0])".
*/
const char **
@@ -1672,11 +1745,9 @@ done2:
/* We no longer need ch_lookup for its original purpose. Modify it, so it
* can detect the delimiters, '\\', and (optionally) whitespaces. */
- ch_lookup[((guint8) '\\')] = 1;
- if (f_strstrip) {
- for (i = 0; NM_ASCII_SPACES[i]; i++)
- ch_lookup[((guint8) (NM_ASCII_SPACES[i]))] = 1;
- }
+ _char_lookup_table_set_one (ch_lookup, '\\');
+ if (f_strstrip)
+ _char_lookup_table_set_all (ch_lookup, NM_ASCII_SPACES);
for (i_token = 0; ptr[i_token]; i_token++) {
s = (char *) ptr[i_token];
@@ -1697,65 +1768,131 @@ done2:
/*****************************************************************************/
const char *
-nm_utils_escaped_tokens_escape (const char *str,
- const char *delimiters,
- char **out_to_free)
+nm_utils_escaped_tokens_escape_full (const char *str,
+ const char *delimiters,
+ const char *delimiters_as_needed,
+ NMUtilsEscapedTokensEscapeFlags flags,
+ char **out_to_free)
{
guint8 ch_lookup[256];
+ guint8 ch_lookup_as_needed[256];
+ gboolean has_ch_lookup_as_needed = FALSE;
char *ret;
gsize str_len;
gsize alloc_len;
gsize n_escapes;
gsize i, j;
+ gboolean escape_leading_space;
gboolean escape_trailing_space;
+ gboolean escape_backslash_as_needed;
- if (!delimiters) {
- nm_assert (delimiters);
- delimiters = NM_ASCII_SPACES;
- }
+ nm_assert ( !delimiters_as_needed
+ || ( delimiters_as_needed[0]
+ && NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED)));
if (!str || str[0] == '\0') {
*out_to_free = NULL;
return str;
}
+ str_len = strlen (str);
+
_char_lookup_table_init (ch_lookup, delimiters);
+ if ( !delimiters
+ || NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES)) {
+ flags &= ~( NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE
+ | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE);
+ _char_lookup_table_set_all (ch_lookup, NM_ASCII_SPACES);
+ }
+
+ if (NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS)) {
+ _char_lookup_table_set_one (ch_lookup, '\\');
+ escape_backslash_as_needed = FALSE;
+ } else if (_char_lookup_has (ch_lookup, '\\'))
+ escape_backslash_as_needed = FALSE;
+ else {
+ escape_backslash_as_needed = NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED);
+ if (escape_backslash_as_needed) {
+ if ( NM_FLAGS_ANY (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE
+ | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE)
+ && !_char_lookup_has_all (ch_lookup, NM_ASCII_SPACES)) {
+ /* ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE implies that we escape backslash
+ * before whitespaces. */
+ if (!has_ch_lookup_as_needed) {
+ has_ch_lookup_as_needed = TRUE;
+ _char_lookup_table_init (ch_lookup_as_needed, NULL);
+ }
+ _char_lookup_table_set_all (ch_lookup_as_needed, NM_ASCII_SPACES);
+ }
+ if ( delimiters_as_needed
+ && !_char_lookup_has_all (ch_lookup, delimiters_as_needed)) {
+ if (!has_ch_lookup_as_needed) {
+ has_ch_lookup_as_needed = TRUE;
+ _char_lookup_table_init (ch_lookup_as_needed, NULL);
+ }
+ _char_lookup_table_set_all (ch_lookup_as_needed, delimiters_as_needed);
+ }
+ }
+ }
- /* also mark '\\' as requiring escaping. */
- ch_lookup[((guint8) '\\')] = 1;
+ escape_leading_space = NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE)
+ && g_ascii_isspace (str[0])
+ && !_char_lookup_has (ch_lookup, str[0]);
+ if (str_len == 1)
+ escape_trailing_space = FALSE;
+ else {
+ escape_trailing_space = NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE)
+ && g_ascii_isspace (str[str_len - 1])
+ && !_char_lookup_has (ch_lookup, str[str_len - 1]);
+ }
n_escapes = 0;
for (i = 0; str[i] != '\0'; i++) {
if (_char_lookup_has (ch_lookup, str[i]))
n_escapes++;
+ else if ( str[i] == '\\'
+ && escape_backslash_as_needed
+ && ( _char_lookup_has (ch_lookup, str[i + 1])
+ || NM_IN_SET (str[i + 1], '\0', '\\')
+ || ( has_ch_lookup_as_needed
+ && _char_lookup_has (ch_lookup_as_needed, str[i + 1]))))
+ n_escapes++;
}
+ if (escape_leading_space)
+ n_escapes++;
+ if (escape_trailing_space)
+ n_escapes++;
- str_len = i;
- nm_assert (str_len > 0 && strlen (str) == str_len);
-
- escape_trailing_space = !_char_lookup_has (ch_lookup, str[str_len - 1])
- && g_ascii_isspace (str[str_len - 1]);
-
- if ( n_escapes == 0
- && !escape_trailing_space) {
+ if (n_escapes == 0u) {
*out_to_free = NULL;
return str;
}
- alloc_len = str_len + n_escapes + ((gsize) escape_trailing_space) + 1;
+ alloc_len = str_len + n_escapes + 1u;
ret = g_new (char, alloc_len);
j = 0;
- for (i = 0; str[i] != '\0'; i++) {
- if (_char_lookup_has (ch_lookup, str[i])) {
- nm_assert (j < alloc_len);
+ i = 0;
+
+ if (escape_leading_space) {
+ ret[j++] = '\\';
+ ret[j++] = str[i++];
+ }
+ for (; str[i] != '\0'; i++) {
+ if (_char_lookup_has (ch_lookup, str[i]))
+ ret[j++] = '\\';
+ else if ( str[i] == '\\'
+ && escape_backslash_as_needed
+ && ( _char_lookup_has (ch_lookup, str[i + 1])
+ || NM_IN_SET (str[i + 1], '\0', '\\')
+ || ( has_ch_lookup_as_needed
+ && _char_lookup_has (ch_lookup_as_needed, str[i + 1]))))
ret[j++] = '\\';
- }
- nm_assert (j < alloc_len);
ret[j++] = str[i];
}
if (escape_trailing_space) {
- nm_assert (!_char_lookup_has (ch_lookup, ret[j - 1]) && g_ascii_isspace (ret[j - 1]));
+ nm_assert ( !_char_lookup_has (ch_lookup, ret[j - 1])
+ && g_ascii_isspace (ret[j - 1]));
ret[j] = ret[j - 1];
ret[j - 1] = '\\';
j++;
@@ -1763,11 +1900,97 @@ nm_utils_escaped_tokens_escape (const char *str,
nm_assert (j == alloc_len - 1);
ret[j] = '\0';
+ nm_assert (strlen (ret) == j);
*out_to_free = ret;
return ret;
}
+/**
+ * nm_utils_escaped_tokens_options_split:
+ * @str: the src string. This string will be modified in-place.
+ * The output values will point into @str.
+ * @out_key: (allow-none): the returned output key. This will always be set to @str
+ * itself. @str will be modified to contain only the unescaped, truncated
+ * key name.
+ * @out_val: returns the parsed (and unescaped) value or %NULL, if @str contains
+ * no '=' delimiter.
+ *
+ * Honors backslash escaping to parse @str as "key=value" pairs. Optionally, if no '='
+ * is present, @out_val will be returned as %NULL. Backslash can be used to escape
+ * '=', ',', '\\', and ascii whitespace. Other backslash sequences are taken verbatim.
+ *
+ * For keys, '=' obviously must be escaped. For values, that is optional because an
+ * unescaped '=' is just taken verbatim. For example, in a key, the sequence "\\="
+ * must be escaped as "\\\\\\=". For the value, that works too, but "\\\\=" is also
+ * accepted.
+ *
+ * Unescaped Space around the key and value are also removed. Space in general must
+ * not be escaped, unless they are at the beginning or the end of key/value.
+ */
+void
+nm_utils_escaped_tokens_options_split (char *str,
+ const char **out_key,
+ const char **out_val)
+{
+ const char *val = NULL;
+ gsize i;
+ gsize j;
+ gsize last_space_idx;
+ gboolean last_space_has;
+
+ nm_assert (str);
+
+ i = 0;
+ while (g_ascii_isspace (str[i]))
+ i++;
+
+ j = 0;
+ last_space_idx = 0;
+ last_space_has = FALSE;
+ while (str[i] != '\0') {
+ if (g_ascii_isspace (str[i])) {
+ if (!last_space_has) {
+ last_space_has = TRUE;
+ last_space_idx = j;
+ }
+ } else {
+ if (str[i] == '\\') {
+ if ( NM_IN_SET (str[i + 1u], '\\', ',', '=')
+ || g_ascii_isspace (str[i + 1u]))
+ i++;
+ } else if (str[i] == '=') {
+ /* Encounter an unescaped '=' character. When we still parse the key, this
+ * is the separator we were waiting for. If we are parsing the value,
+ * we take the character verbatim. */
+ if (!val) {
+ if (last_space_has) {
+ str[last_space_idx] = '\0';
+ j = last_space_idx + 1;
+ last_space_has = FALSE;
+ } else
+ str[j++] = '\0';
+ val = &str[j];
+ i++;
+ while (g_ascii_isspace (str[i]))
+ i++;
+ continue;
+ }
+ }
+ last_space_has = FALSE;
+ }
+ str[j++] = str[i++];
+ }
+
+ if (last_space_has)
+ str[last_space_idx] = '\0';
+ else
+ str[j] = '\0';
+
+ *out_key = str;
+ *out_val = val;
+}
+
/*****************************************************************************/
/**
diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h
index 1d066451b3..172eaa7d12 100644
--- a/shared/nm-glib-aux/nm-shared-utils.h
+++ b/shared/nm-glib-aux/nm-shared-utils.h
@@ -393,6 +393,8 @@ gboolean nm_utils_gbytes_equal_mem (GBytes *bytes,
GVariant *nm_utils_gbytes_to_variant_ay (GBytes *bytes);
+GVariant *nm_utils_strdict_to_variant_ass (GHashTable *strdict);
+
/*****************************************************************************/
GVariant *nm_utils_gvariant_vardict_filter (GVariant *src,
@@ -438,7 +440,25 @@ int nm_utils_dbus_path_cmp (const char *dbus_path_a, const char *dbus_path_b);
typedef enum {
NM_UTILS_STRSPLIT_SET_FLAGS_NONE = 0,
+
+ /* by default, strsplit will coalesce consecutive delimiters and remove
+ * them from the result. If this flag is present, empty values are preserved
+ * and returned.
+ *
+ * When combined with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, if a value gets
+ * empty after strstrip(), it also gets removed. */
NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY = (1u << 0),
+
+ /* %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING means that delimiters prefixed
+ * by a backslash are not treated as a separator. Such delimiters and their escape
+ * character are copied to the current word without unescaping them. In general,
+ * nm_utils_strsplit_set_full() does not remove any backslash escape characters
+ * and does no unescaping. It only considers them for skipping to split at
+ * an escaped delimiter.
+ *
+ * If this is combined with (or implied by %NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED), then
+ * the backslash escapes are removed from the result.
+ */
NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING = (1u << 1),
/* If flag is set, does the same as g_strstrip() on the returned tokens.
@@ -478,6 +498,7 @@ typedef enum {
* need extra care, and then only if they proceed one of the relevant characters.
*/
NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED = (1u << 3),
+
} NMUtilsStrsplitSetFlags;
const char **nm_utils_strsplit_set_full (const char *str,
@@ -521,9 +542,65 @@ nm_utils_escaped_tokens_split (const char *str,
| NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP);
}
-const char *nm_utils_escaped_tokens_escape (const char *str,
- const char *delimiters,
- char **out_to_free);
+typedef enum {
+ NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_NONE = 0,
+ NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES = (1ull << 0),
+ NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE = (1ull << 1),
+ NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE = (1ull << 2),
+
+ /* Backslash characters will be escaped as "\\\\" if they precede another
+ * character that makes it necessary. Such characters are:
+ *
+ * 1) before another '\\' backslash.
+ * 2) before any delimiter in @delimiters.
+ * 3) before any delimiter in @delimiters_as_needed.
+ * 4) before a white space, if ESCAPE_LEADING_SPACE or ESCAPE_TRAILING_SPACE is set.
+ * 5) before the end of the word
+ *
+ * Rule 4) is an extension. It's not immediately clear why with ESCAPE_LEADING_SPACE
+ * and ESCAPE_TRAILING_SPACE we want *all* backslashes before a white space escaped.
+ * The reason is, that we obviously want to use ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE
+ * in cases, where we later parse the backslash escaped strings back, but allowing to strip
+ * unescaped white spaces. That means, we want that " a " gets escaped as "\\ a\\ ".
+ * On the other hand, we also want that " a\\ b " gets escaped as "\\ a\\\\ b\\ ",
+ * and not "\\ a\\ b\\ ". Because otherwise, the parser would need to treat "\\ "
+ * differently depending on whether the sequence is at the beginning, end or middle
+ * of the word.
+ *
+ * Rule 5) is also not immediately obvious. When used with ESCAPE_TRAILING_SPACE,
+ * we clearly want to allow that an escaped word can have arbitrary
+ * whitespace suffixes. That's why this mode exists. So we must escape "a\\" as
+ * "a\\\\", so that appending " " does not change the meaning.
+ * Also without ESCAPE_TRAILING_SPACE, we want in general that we can concatenate
+ * two escaped words without changing their meaning. If the words would be "a\\"
+ * and "," (with ',' being a delimiter), then the result must be "a\\\\" and "\\,"
+ * so that the concatenated word ("a\\\\\\,") is still the same. If we would escape
+ * them instead as "a\\" + "\\,", then the concatenated word would be "a\\\\," and
+ * different.
+ * */
+ NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED = (1ull << 3),
+
+ NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS = (1ull << 4),
+} NMUtilsEscapedTokensEscapeFlags;
+
+const char *nm_utils_escaped_tokens_escape_full (const char *str,
+ const char *delimiters,
+ const char *delimiters_as_needed,
+ NMUtilsEscapedTokensEscapeFlags flags,
+ char **out_to_free);
+
+static inline const char *
+nm_utils_escaped_tokens_escape (const char *str,
+ const char *delimiters,
+ char **out_to_free)
+{
+ return nm_utils_escaped_tokens_escape_full (str,
+ delimiters,
+ NULL,
+ NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS
+ | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE,
+ out_to_free);
+}
static inline GString *
nm_utils_escaped_tokens_escape_gstr_assert (const char *str,
@@ -575,6 +652,47 @@ nm_utils_escaped_tokens_escape_gstr (const char *str,
/*****************************************************************************/
+static inline const char **
+nm_utils_escaped_tokens_options_split_list (const char *str)
+{
+ return nm_utils_strsplit_set_full (str,
+ ",",
+ NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP
+ | NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING);
+}
+
+void nm_utils_escaped_tokens_options_split (char *str,
+ const char **out_key,
+ const char **out_val);
+
+static inline const char *
+nm_utils_escaped_tokens_options_escape_key (const char *key,
+ char **out_to_free)
+{
+ return nm_utils_escaped_tokens_escape_full (key,
+ ",=",
+ NULL,
+ NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED
+ | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE
+ | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE,
+ out_to_free);
+}
+
+static inline const char *
+nm_utils_escaped_tokens_options_escape_val (const char *val,
+ char **out_to_free)
+{
+ return nm_utils_escaped_tokens_escape_full (val,
+ ",",
+ "=",
+ NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED
+ | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE
+ | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE,
+ out_to_free);
+}
+
+/*****************************************************************************/
+
#define NM_UTILS_CHECKSUM_LENGTH_MD5 16
#define NM_UTILS_CHECKSUM_LENGTH_SHA1 20
#define NM_UTILS_CHECKSUM_LENGTH_SHA256 32
@@ -1307,6 +1425,26 @@ char *nm_utils_g_slist_strlist_join (const GSList *a, const char *separator);
/*****************************************************************************/
+static inline guint
+nm_g_hash_table_size (GHashTable *hash)
+{
+ return hash ? g_hash_table_size (hash) : 0u;
+}
+
+static inline gpointer
+nm_g_hash_table_lookup (GHashTable *hash, gconstpointer key)
+{
+ return hash ? g_hash_table_lookup (hash, key) : NULL;
+}
+
+static inline gboolean
+nm_g_hash_table_remove (GHashTable *hash, gconstpointer key)
+{
+ return hash ? g_hash_table_remove (hash, key) : FALSE;
+}
+
+/*****************************************************************************/
+
gssize nm_utils_ptrarray_find_binary_search (gconstpointer *list,
gsize len,
gconstpointer needle,
diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h
index 75af679965..0d03456319 100644
--- a/shared/nm-utils/nm-test-utils.h
+++ b/shared/nm-utils/nm-test-utils.h
@@ -986,6 +986,69 @@ nmtst_rand_perm_gslist (GRand *rand, GSList *list)
/*****************************************************************************/
+/**
+ * nmtst_get_rand_word_length:
+ * @rand: (allow-none): #GRand instance or %NULL to use the singleton.
+ *
+ * Returns: a random integer >= 0, that most frequently is somewhere between
+ * 0 and 16, but (with decreasing) probability, it can be larger. This can
+ * be used when we generate random input for unit tests.
+ */
+static inline guint
+nmtst_get_rand_word_length (GRand *rand)
+{
+ guint n;
+
+ if (!rand)
+ rand = nmtst_get_rand ();
+
+ n = 0;
+ while (TRUE) {
+ guint32 rnd = g_rand_int (rand);
+ guint probability;
+
+ /* The following python code implements a random sample with this
+ * distribution:
+ *
+ * def random_histogram(n_tries, scale = None):
+ * def probability(n_tok):
+ * import math
+ * return max(2, math.floor(100 / (2*(n_tok+1))))
+ * def n_tokens():
+ * import random
+ * n_tok = 0
+ * while True:
+ * if random.randint(0, 0xFFFFFFFF) % probability(n_tok) == 0:
+ * return n_tok
+ * n_tok += 1
+ * hist = []
+ * i = 0;
+ * while i < n_tries:
+ * n_tok = n_tokens()
+ * while n_tok >= len(hist):
+ * hist.append(0)
+ * hist[n_tok] = hist[n_tok] + 1
+ * i += 1
+ * if scale is not None:
+ * hist = list([round(x / n_tries * scale) for x in hist])
+ * return hist
+ *
+ * For example, random_histogram(n_tries = 1000000, scale = 1000) may give
+ *
+ * IDX: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
+ * SEEN: [20, 39, 59, 73, 80, 91, 92, 90, 91, 73, 73, 54, 55, 36, 24, 16, 16, 8, 4, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
+ *
+ * which give a sense of the probability with this individual results are returned.
+ */
+ probability = NM_MAX (2u, (100u / (2u * (n + 1u))));
+ if ((rnd % probability) == 0)
+ return n;
+ n++;
+ }
+}
+
+/*****************************************************************************/
+
static inline gboolean
nmtst_g_source_assert_not_called (gpointer user_data)
{