[tor-commits] [tor/master] Add code to parse K=V lines into config_line_t format.
nickm at torproject.org
nickm at torproject.org
Wed Dec 12 16:08:02 UTC 2018
commit f0a8664677b8e4a3503172d6e7564da33496be8f
Author: Nick Mathewson <nickm at torproject.org>
Date: Thu Dec 6 14:13:29 2018 -0500
Add code to parse K=V lines into config_line_t format.
Closes ticket 28755
---
src/lib/encoding/.may_include | 1 +
src/lib/encoding/include.am | 2 +
src/lib/encoding/kvline.c | 239 ++++++++++++++++++++++++++++++++++++++++++
src/lib/encoding/kvline.h | 24 +++++
src/test/test_config.c | 78 ++++++++++++++
5 files changed, 344 insertions(+)
diff --git a/src/lib/encoding/.may_include b/src/lib/encoding/.may_include
index 7c2ef3692..c9bf4b178 100644
--- a/src/lib/encoding/.may_include
+++ b/src/lib/encoding/.may_include
@@ -1,5 +1,6 @@
orconfig.h
lib/cc/*.h
+lib/container/*.h
lib/ctime/*.h
lib/encoding/*.h
lib/intmath/*.h
diff --git a/src/lib/encoding/include.am b/src/lib/encoding/include.am
index 2d2aa3988..83e9211b6 100644
--- a/src/lib/encoding/include.am
+++ b/src/lib/encoding/include.am
@@ -9,6 +9,7 @@ src_lib_libtor_encoding_a_SOURCES = \
src/lib/encoding/confline.c \
src/lib/encoding/cstring.c \
src/lib/encoding/keyval.c \
+ src/lib/encoding/kvline.c \
src/lib/encoding/pem.c \
src/lib/encoding/time_fmt.c
@@ -22,5 +23,6 @@ noinst_HEADERS += \
src/lib/encoding/confline.h \
src/lib/encoding/cstring.h \
src/lib/encoding/keyval.h \
+ src/lib/encoding/kvline.h \
src/lib/encoding/pem.h \
src/lib/encoding/time_fmt.h
diff --git a/src/lib/encoding/kvline.c b/src/lib/encoding/kvline.c
new file mode 100644
index 000000000..11ff4f0f9
--- /dev/null
+++ b/src/lib/encoding/kvline.c
@@ -0,0 +1,239 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file kvline.c
+ *
+ * \brief Manipulating lines of key-value pairs.
+ **/
+
+#include "orconfig.h"
+
+#include "lib/container/smartlist.h"
+#include "lib/encoding/confline.h"
+#include "lib/encoding/cstring.h"
+#include "lib/encoding/kvline.h"
+#include "lib/malloc/malloc.h"
+#include "lib/string/compat_ctype.h"
+#include "lib/string/printf.h"
+#include "lib/string/util_string.h"
+#include "lib/log/escape.h"
+#include "lib/log/util_bug.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+
+/** Return true iff we need to quote and escape the string <b>s</b> to encode
+ * it. */
+static bool
+needs_escape(const char *s, bool as_keyless_val)
+{
+ if (as_keyless_val && *s == 0)
+ return true;
+
+ for (; *s; ++s) {
+ if (*s >= 127 || TOR_ISSPACE(*s) || ! TOR_ISPRINT(*s) ||
+ *s == '\'' || *s == '\"') {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Return true iff the key in <b>line</b> is not set.
+ **/
+static bool
+line_has_no_key(const config_line_t *line)
+{
+ return line->key == NULL || strlen(line->key) == 0;
+}
+
+/**
+ * Return true iff the all the lines in <b>line</b> can be encoded
+ * using <b>flags</b>.
+ **/
+static bool
+kvline_can_encode_lines(const config_line_t *line, unsigned flags)
+{
+ for ( ; line; line = line->next) {
+ const bool keyless = line_has_no_key(line);
+ if (keyless) {
+ if (! (flags & KV_OMIT_KEYS)) {
+ /* If KV_OMIT_KEYS is not set, we can't encode a line with no key. */
+ return false;
+ }
+ if (strchr(line->value, '=') && !( flags & KV_QUOTED)) {
+ /* We can't have a keyless value with = without quoting it. */
+ return false;
+ }
+ }
+
+ if (needs_escape(line->value, keyless) && ! (flags & KV_QUOTED)) {
+ /* If KV_QUOTED is false, we can't encode a value that needs quotes. */
+ return false;
+ }
+ if (line->key && strlen(line->key) &&
+ (needs_escape(line->key, false) || strchr(line->key, '='))) {
+ /* We can't handle keys that need quoting. */
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Encode a linked list of lines in <b>line</b> as a series of 'Key=Value'
+ * pairs, using the provided <b>flags</b> to encode it. Return a newly
+ * allocated string on success, or NULL on failure.
+ *
+ * If KV_QUOTED is set in <b>flags</b>, then all values that contain
+ * spaces or unusual characters are escaped and quoted. Otherwise, such
+ * values are not allowed.
+ *
+ * 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.
+ */
+char *
+kvline_encode(const config_line_t *line,
+ unsigned flags)
+{
+ if (!kvline_can_encode_lines(line, flags))
+ return NULL;
+
+ smartlist_t *elements = smartlist_new();
+
+ for (; line; line = line->next) {
+
+ const char *k = "";
+ const char *eq = "=";
+ const char *v = "";
+ const bool keyless = line_has_no_key(line);
+ bool esc = needs_escape(line->value, keyless);
+ char *tmp = NULL;
+
+ if (! keyless) {
+ k = line->key;
+ } else {
+ eq = "";
+ if (strchr(line->value, '=')) {
+ esc = true;
+ }
+ }
+
+ if (esc) {
+ tmp = esc_for_log(line->value);
+ v = tmp;
+ } else {
+ v = line->value;
+ }
+
+ smartlist_add_asprintf(elements, "%s%s%s", k, eq, v);
+ tor_free(tmp);
+ }
+
+ char *result = smartlist_join_strings(elements, " ", 0, NULL);
+
+ SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp));
+ smartlist_free(elements);
+
+ return result;
+}
+
+/**
+ * Decode a <b>line</b> containing a series of space-separated 'Key=Value'
+ * pairs, using the provided <b>flags</b> to decode it. Return a newly
+ * allocated list of pairs on success, or NULL on failure.
+ *
+ * If KV_QUOTED is set in <b>flags</b>, then (double-)quoted values are
+ * allowed. Otherwise, such values are not allowed.
+ *
+ * If KV_OMIT_KEYS is set in <b>flags</b>, then values without keys are
+ * allowed. Otherwise, such values are not allowed.
+ */
+config_line_t *
+kvline_parse(const char *line, unsigned flags)
+{
+ const char *cp = line, *cplast = NULL;
+ bool omit_keys = (flags & KV_OMIT_KEYS) != 0;
+ bool quoted = (flags & KV_QUOTED) != 0;
+
+ config_line_t *result = NULL;
+ config_line_t **next_line = &result;
+
+ char *key = NULL;
+ char *val = NULL;
+
+ while (*cp) {
+ key = val = NULL;
+ {
+ 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. */
+ 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. */
+
+ /* Find the key. */
+ 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_keys)
+ goto err;
+ }
+ }
+
+ if (*cp == '\"') {
+ /* The type is "V". */
+ if (!quoted)
+ goto err;
+ size_t len=0;
+ cp = unescape_string(cp, &val, &len);
+ if (cp == NULL || len != strlen(val)) {
+ // The string contains a NUL or is badly coded.
+ goto err;
+ }
+ } else {
+ size_t idx = strcspn(cp, " \t\r\v\n");
+ val = tor_memdup_nulterm(cp, idx);
+ cp += idx;
+ }
+
+ if (key && strlen(key) == 0) {
+ /* We don't allow empty keys. */
+ goto err;
+ }
+
+ *next_line = tor_malloc_zero(sizeof(config_line_t));
+ (*next_line)->key = key ? key : tor_strdup("");
+ (*next_line)->value = val;
+ next_line = &(*next_line)->next;
+ key = val = NULL;
+ }
+
+ if (!kvline_can_encode_lines(result, flags)) {
+ goto err;
+ }
+ return result;
+
+ err:
+ tor_free(key);
+ tor_free(val);
+ config_free_lines(result);
+ return NULL;
+}
diff --git a/src/lib/encoding/kvline.h b/src/lib/encoding/kvline.h
new file mode 100644
index 000000000..3272cc175
--- /dev/null
+++ b/src/lib/encoding/kvline.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file kvline.h
+ *
+ * \brief Header for kvline.c
+ **/
+
+#ifndef TOR_KVLINE_H
+#define TOR_KVLINE_H
+
+struct config_line_t;
+
+#define KV_QUOTED (1u<<0)
+#define KV_OMIT_KEYS (1u<<1)
+
+struct config_line_t *kvline_parse(const char *line, unsigned flags);
+char *kvline_encode(const struct config_line_t *line, unsigned flags);
+
+#endif /* !defined(TOR_KVLINE_H) */
diff --git a/src/test/test_config.c b/src/test/test_config.c
index dae4d8376..5140c3c1a 100644
--- a/src/test/test_config.c
+++ b/src/test/test_config.c
@@ -54,6 +54,7 @@
#include "lib/meminfo/meminfo.h"
#include "lib/net/gethostname.h"
#include "lib/encoding/confline.h"
+#include "lib/encoding/kvline.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
@@ -5813,6 +5814,82 @@ test_config_extended_fmt(void *arg)
config_free_lines(lines);
}
+static void
+test_config_kvline_parse(void *arg)
+{
+ (void)arg;
+
+ config_line_t *lines = NULL;
+ char *enc = NULL;
+
+ lines = kvline_parse("A=B CD=EF", 0);
+ tt_assert(lines);
+ tt_str_op(lines->key, OP_EQ, "A");
+ tt_str_op(lines->value, OP_EQ, "B");
+ tt_str_op(lines->next->key, OP_EQ, "CD");
+ tt_str_op(lines->next->value, OP_EQ, "EF");
+ enc = kvline_encode(lines, 0);
+ tt_str_op(enc, OP_EQ, "A=B CD=EF");
+ tor_free(enc);
+ enc = kvline_encode(lines, KV_QUOTED|KV_OMIT_KEYS);
+ tt_str_op(enc, OP_EQ, "A=B CD=EF");
+ tor_free(enc);
+ config_free_lines(lines);
+
+ lines = kvline_parse("AB CDE=F", 0);
+ tt_assert(! lines);
+
+ lines = kvline_parse("AB CDE=F", KV_OMIT_KEYS);
+ tt_assert(lines);
+ tt_str_op(lines->key, OP_EQ, "");
+ tt_str_op(lines->value, OP_EQ, "AB");
+ tt_str_op(lines->next->key, OP_EQ, "CDE");
+ tt_str_op(lines->next->value, OP_EQ, "F");
+ tt_assert(lines);
+ enc = kvline_encode(lines, 0);
+ tt_assert(!enc);
+ enc = kvline_encode(lines, KV_QUOTED|KV_OMIT_KEYS);
+ tt_str_op(enc, OP_EQ, "AB CDE=F");
+ tor_free(enc);
+ config_free_lines(lines);
+
+ lines = kvline_parse("AB=C CDE=\"F G\"", 0);
+ tt_assert(!lines);
+
+ lines = kvline_parse("AB=C CDE=\"F G\" \"GHI\" ", KV_QUOTED|KV_OMIT_KEYS);
+ tt_assert(lines);
+ tt_str_op(lines->key, OP_EQ, "AB");
+ tt_str_op(lines->value, OP_EQ, "C");
+ tt_str_op(lines->next->key, OP_EQ, "CDE");
+ tt_str_op(lines->next->value, OP_EQ, "F G");
+ tt_str_op(lines->next->next->key, OP_EQ, "");
+ tt_str_op(lines->next->next->value, OP_EQ, "GHI");
+ enc = kvline_encode(lines, 0);
+ tt_assert(!enc);
+ enc = kvline_encode(lines, KV_QUOTED|KV_OMIT_KEYS);
+ tt_str_op(enc, OP_EQ, "AB=C CDE=\"F G\" GHI");
+ tor_free(enc);
+ config_free_lines(lines);
+
+ lines = kvline_parse("A\"B=C CDE=\"F\" \"GHI\" ", KV_QUOTED|KV_OMIT_KEYS);
+ tt_assert(! lines);
+
+ lines = kvline_parse("AB=", KV_QUOTED);
+ 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=", 0);
+ tt_assert(lines);
+ tt_str_op(lines->key, OP_EQ, "AB");
+ tt_str_op(lines->value, OP_EQ, "");
+
+ done:
+ config_free_lines(lines);
+ tor_free(enc);
+}
+
#define CONFIG_TEST(name, flags) \
{ #name, test_config_ ## name, flags, NULL, NULL }
@@ -5864,5 +5941,6 @@ struct testcase_t config_tests[] = {
CONFIG_TEST(include_opened_file_list, 0),
CONFIG_TEST(compute_max_mem_in_queues, 0),
CONFIG_TEST(extended_fmt, 0),
+ CONFIG_TEST(kvline_parse, 0),
END_OF_TESTCASES
};
More information about the tor-commits
mailing list