diff --git a/src/lib/encoding/kvline.c b/src/lib/encoding/kvline.c
index 307adc3f12..806f9d3db0 100644
--- a/src/lib/encoding/kvline.c
+++ b/src/lib/encoding/kvline.c
@@ -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 line 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 line can be encoded
* using flags.
@@ -98,6 +107,10 @@ kvline_can_encode_lines(const config_line_t *line, unsigned flags)
* If KV_OMIT_KEYS is set in flags, 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 flags, 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 flags, then values without keys are
* allowed. Otherwise, such values are not allowed.
+ *
+ * If KV_OMIT_VALS is set in flags, 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;
}
diff --git a/src/lib/encoding/kvline.h b/src/lib/encoding/kvline.h
index 4eed30a223..6740f81d54 100644
--- a/src/lib/encoding/kvline.h
+++ b/src/lib/encoding/kvline.h
@@ -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);
diff --git a/src/test/test_config.c b/src/test/test_config.c
index 72649dd9b1..6cfb7b764b 100644
--- a/src/test/test_config.c
+++ b/src/test/test_config.c
@@ -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);