shared: add nm_utils_escaped_tokens_escape()

This escapes strings so that they can be concatenated with a delimiter
and without loss tokenized with nm_utils_escaped_tokens_split().

Note that this is similar to _nm_utils_escape_plain() and
_nm_utils_escape_spaces(). The difference is that
nm_utils_escaped_tokens_escape() also escapes the last trailing
whitespace. That means, if delimiters contains all NM_ASCII_SPACES, then
it is identical to _nm_utils_escape_spaces(). Otherwise, the trailing
space is treated specially. That is, because
nm_utils_escaped_tokens_split() uses NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP,
to strip leading and trailing whitespace. To still express a trailing
whitespace, the last whitespace must be escaped. Note that
NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP also honors escaping any whitespace
(not only at the last position), but when escaping we don't need to
escape them, because unescaped (non-trailing) whitespace are taken just
fine.

The pair nm_utils_escaped_tokens_split() and
nm_utils_escaped_tokens_escape() are proposed as default way of
tokenizing a list of items. For example, with

  $ nmcli connection modify "$PROFILE" +ipv4.routing-rules 'priority 5 from 192.168.7.5/32 table 5, priority 6 iif a\, from 192.168.7.5/32 table 6'

Here we implement a to/from string function to handle one item
(nm_ip_routing_rule_{from,to}_string()). When such elements are combined with ',',
then we need to support an additional layer of escaping on top of that.
The advantage is that the indvidual to/from string functions are agnostic
to this second layer of escaping/tokenizing that nmcli employs to handle
a list of these items.
The disadvantage is that we possibly get multiple layers of backslash
escapings. That is only mitigated by the fact that nm_utils_escaped_tokens_*()
supports a syntax for which *most* characters don't need any special escaping.
Only delimiters, backslash, and the trailing space needs escaping, and
these are cases are expected to be few.

(cherry picked from commit e206e3d4d8)
This commit is contained in:
Thomas Haller 2019-04-11 21:20:11 +02:00
parent 4181ed86ac
commit 0cc5fe78b3
2 changed files with 107 additions and 0 deletions

View file

@ -1216,6 +1216,82 @@ done2:
return ptr;
}
/*****************************************************************************/
const char *
nm_utils_escaped_tokens_escape (const char *str,
const char *delimiters,
char **out_to_free)
{
guint8 ch_lookup[256];
char *ret;
gsize str_len;
gsize alloc_len;
gsize n_escapes;
gsize i, j;
gboolean escape_trailing_space;
if (!delimiters) {
nm_assert (delimiters);
delimiters = NM_ASCII_SPACES;
}
if (!str || str[0] == '\0') {
*out_to_free = NULL;
return str;
}
_char_lookup_table_init (ch_lookup, delimiters);
/* also mark '\\' as requiring escaping. */
ch_lookup[((guint8) '\\')] = 1;
n_escapes = 0;
for (i = 0; str[i] != '\0'; i++) {
if (_char_lookup_has (ch_lookup, str[i]))
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) {
*out_to_free = NULL;
return str;
}
alloc_len = str_len + n_escapes + ((gsize) escape_trailing_space) + 1;
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);
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]));
ret[j] = ret[j - 1];
ret[j - 1] = '\\';
j++;
}
nm_assert (j == alloc_len - 1);
ret[j] = '\0';
*out_to_free = ret;
return ret;
}
/*****************************************************************************/
/**
* nm_utils_strv_find_first:
* @list: the strv list to search

View file

@ -409,6 +409,37 @@ char **_nm_utils_strv_cleanup (char **strv,
/*****************************************************************************/
static inline const char **
nm_utils_escaped_tokens_split (const char *str,
const char *delimiters)
{
return nm_utils_strsplit_set_full (str,
delimiters,
NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED
| NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP);
}
const char *nm_utils_escaped_tokens_escape (const char *str,
const char *delimiters,
char **out_to_free);
static inline GString *
nm_utils_escaped_tokens_escape_gstr (const char *str,
const char *delimiters,
GString *gstring)
{
gs_free char *str_to_free = NULL;
nm_assert (str);
nm_assert (gstring);
g_string_append (gstring,
nm_utils_escaped_tokens_escape (str, delimiters, &str_to_free));
return gstring;
}
/*****************************************************************************/
#define NM_UTILS_CHECKSUM_LENGTH_MD5 16
#define NM_UTILS_CHECKSUM_LENGTH_SHA1 20
#define NM_UTILS_CHECKSUM_LENGTH_SHA256 32