kvline: handle empty alues as well as empty keys

The two options are mutually exclusive, since otherwise an entry
like "Foo" would be ambiguous.  We want to have the ability to treat
entries like this as keys, though, since some controller commands
interpret them as flags.
This commit is contained in:
Nick Mathewson 2019-04-05 15:29:37 -04:00
parent 01b07c548b
commit 73df91bbb5
3 changed files with 97 additions and 7 deletions

View File

@ -53,6 +53,15 @@ line_has_no_key(const config_line_t *line)
return line->key == NULL || strlen(line->key) == 0;
}
/**
* Return true iff the value in <b>line</b> is not set.
**/
static bool
line_has_no_val(const config_line_t *line)
{
return line->value == NULL || strlen(line->value) == 0;
}
/**
* Return true iff the all the lines in <b>line</b> can be encoded
* using <b>flags</b>.
@ -98,6 +107,10 @@ kvline_can_encode_lines(const config_line_t *line, unsigned flags)
* If KV_OMIT_KEYS is set in <b>flags</b>, then pairs with empty keys are
* allowed, and are encoded as 'Value'. Otherwise, such pairs are not
* allowed.
*
* If KV_OMIT_VALS is set in <b>flags</b>, then an empty value is
* encoded as 'Key', not as 'Key=' or 'Key=""'. Mutually exclusive with
* KV_OMIT_KEYS.
*/
char *
kvline_encode(const config_line_t *line,
@ -106,6 +119,9 @@ kvline_encode(const config_line_t *line,
if (!kvline_can_encode_lines(line, flags))
return NULL;
tor_assert((flags & (KV_OMIT_KEYS|KV_OMIT_VALS)) !=
(KV_OMIT_KEYS|KV_OMIT_VALS));
smartlist_t *elements = smartlist_new();
for (; line; line = line->next) {
@ -126,7 +142,10 @@ kvline_encode(const config_line_t *line,
}
}
if (esc) {
if ((flags & KV_OMIT_VALS) && line_has_no_val(line)) {
eq = "";
v = "";
} else if (esc) {
tmp = esc_for_log(line->value);
v = tmp;
} else {
@ -155,13 +174,21 @@ kvline_encode(const config_line_t *line,
*
* If KV_OMIT_KEYS is set in <b>flags</b>, then values without keys are
* allowed. Otherwise, such values are not allowed.
*
* If KV_OMIT_VALS is set in <b>flags</b>, then keys without values are
* allowed. Otherwise, such keys are not allowed. Mutually exclusive with
* KV_OMIT_KEYS.
*/
config_line_t *
kvline_parse(const char *line, unsigned flags)
{
tor_assert((flags & (KV_OMIT_KEYS|KV_OMIT_VALS)) !=
(KV_OMIT_KEYS|KV_OMIT_VALS));
const char *cp = line, *cplast = NULL;
bool omit_keys = (flags & KV_OMIT_KEYS) != 0;
bool quoted = (flags & KV_QUOTED) != 0;
const bool omit_keys = (flags & KV_OMIT_KEYS) != 0;
const bool omit_vals = (flags & KV_OMIT_VALS) != 0;
const bool quoted = (flags & KV_QUOTED) != 0;
config_line_t *result = NULL;
config_line_t **next_line = &result;
@ -171,27 +198,33 @@ kvline_parse(const char *line, unsigned flags)
while (*cp) {
key = val = NULL;
/* skip all spaces */
{
size_t idx = strspn(cp, " \t\r\v\n");
cp += idx;
}
if (BUG(cp == cplast)) {
/* If we didn't parse anything, this code is broken. */
/* If we didn't parse anything since the last loop, this code is
* broken. */
goto err; // LCOV_EXCL_LINE
}
cplast = cp;
if (! *cp)
break; /* End of string; we're done. */
/* Possible formats are K=V, K="V", V, and "V", depending on flags. */
/* Possible formats are K=V, K="V", K, V, and "V", depending on flags. */
/* Find the key. */
/* Find where the key ends */
if (*cp != '\"') {
size_t idx = strcspn(cp, " \t\r\v\n=");
if (cp[idx] == '=') {
key = tor_memdup_nulterm(cp, idx);
cp += idx + 1;
} else if (omit_vals) {
key = tor_memdup_nulterm(cp, idx);
cp += idx;
goto commit;
} else {
if (!omit_keys)
goto err;
@ -214,6 +247,7 @@ kvline_parse(const char *line, unsigned flags)
cp += idx;
}
commit:
if (key && strlen(key) == 0) {
/* We don't allow empty keys. */
goto err;
@ -221,7 +255,7 @@ kvline_parse(const char *line, unsigned flags)
*next_line = tor_malloc_zero(sizeof(config_line_t));
(*next_line)->key = key ? key : tor_strdup("");
(*next_line)->value = val;
(*next_line)->value = val ? val : tor_strdup("");
next_line = &(*next_line)->next;
key = val = NULL;
}

View File

@ -17,6 +17,7 @@ struct config_line_t;
#define KV_QUOTED (1u<<0)
#define KV_OMIT_KEYS (1u<<1)
#define KV_OMIT_VALS (1u<<2)
struct config_line_t *kvline_parse(const char *line, unsigned flags);
char *kvline_encode(const struct config_line_t *line, unsigned flags);

View File

@ -5886,6 +5886,61 @@ test_config_kvline_parse(void *arg)
tt_assert(lines);
tt_str_op(lines->key, OP_EQ, "AB");
tt_str_op(lines->value, OP_EQ, "");
config_free_lines(lines);
lines = kvline_parse("AB=", KV_OMIT_VALS);
tt_assert(lines);
tt_str_op(lines->key, OP_EQ, "AB");
tt_str_op(lines->value, OP_EQ, "");
config_free_lines(lines);
lines = kvline_parse(" AB ", KV_OMIT_VALS);
tt_assert(lines);
tt_str_op(lines->key, OP_EQ, "AB");
tt_str_op(lines->value, OP_EQ, "");
config_free_lines(lines);
lines = kvline_parse("AB", KV_OMIT_VALS);
tt_assert(lines);
tt_str_op(lines->key, OP_EQ, "AB");
tt_str_op(lines->value, OP_EQ, "");
enc = kvline_encode(lines, KV_OMIT_VALS);
tt_str_op(enc, OP_EQ, "AB");
tor_free(enc);
config_free_lines(lines);
lines = kvline_parse("AB=CD", KV_OMIT_VALS);
tt_assert(lines);
tt_str_op(lines->key, OP_EQ, "AB");
tt_str_op(lines->value, OP_EQ, "CD");
enc = kvline_encode(lines, KV_OMIT_VALS);
tt_str_op(enc, OP_EQ, "AB=CD");
tor_free(enc);
config_free_lines(lines);
lines = kvline_parse("AB=CD DE FGH=I", KV_OMIT_VALS);
tt_assert(lines);
tt_str_op(lines->key, OP_EQ, "AB");
tt_str_op(lines->value, OP_EQ, "CD");
tt_str_op(lines->next->key, OP_EQ, "DE");
tt_str_op(lines->next->value, OP_EQ, "");
tt_str_op(lines->next->next->key, OP_EQ, "FGH");
tt_str_op(lines->next->next->value, OP_EQ, "I");
enc = kvline_encode(lines, KV_OMIT_VALS);
tt_str_op(enc, OP_EQ, "AB=CD DE FGH=I");
tor_free(enc);
config_free_lines(lines);
lines = kvline_parse("AB=\"CD E\" DE FGH=\"I\"", KV_OMIT_VALS|KV_QUOTED);
tt_assert(lines);
tt_str_op(lines->key, OP_EQ, "AB");
tt_str_op(lines->value, OP_EQ, "CD E");
tt_str_op(lines->next->key, OP_EQ, "DE");
tt_str_op(lines->next->value, OP_EQ, "");
tt_str_op(lines->next->next->key, OP_EQ, "FGH");
tt_str_op(lines->next->next->value, OP_EQ, "I");
enc = kvline_encode(lines, KV_OMIT_VALS|KV_QUOTED);
tt_str_op(enc, OP_EQ, "AB=\"CD E\" DE FGH=I");
done:
config_free_lines(lines);