[tor-commits] [tor/master] kvline: handle empty alues as well as empty keys

dgoulet at torproject.org dgoulet at torproject.org
Tue Apr 30 15:57:51 UTC 2019


commit 73df91bbb55498b05faae16b49ab49545fdffa8f
Author: Nick Mathewson <nickm at torproject.org>
Date:   Fri Apr 5 15:29:37 2019 -0400

    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.
---
 src/lib/encoding/kvline.c | 48 +++++++++++++++++++++++++++++++++++------
 src/lib/encoding/kvline.h |  1 +
 src/test/test_config.c    | 55 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 97 insertions(+), 7 deletions(-)

diff --git a/src/lib/encoding/kvline.c b/src/lib/encoding/kvline.c
index 307adc3f1..806f9d3db 100644
--- a/src/lib/encoding/kvline.c
+++ b/src/lib/encoding/kvline.c
@@ -54,6 +54,15 @@ line_has_no_key(const config_line_t *line)
 }
 
 /**
+ * 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;
   }
diff --git a/src/lib/encoding/kvline.h b/src/lib/encoding/kvline.h
index 4eed30a22..6740f81d5 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 72649dd9b..6cfb7b764 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);





More information about the tor-commits mailing list