summaryrefslogtreecommitdiff
path: root/src/lib/encoding
diff options
context:
space:
mode:
authorNick Mathewson <nickm@torproject.org>2018-06-27 16:52:39 -0400
committerNick Mathewson <nickm@torproject.org>2018-06-27 16:59:56 -0400
commitb9b44bf000735135e1913fd9c22cd6c1547dbdb3 (patch)
treed986c9001e83e3d4c079fd0c4f749101b82cc6e2 /src/lib/encoding
parent696f6f15697260255146d634e1529202cc4c2b77 (diff)
downloadtor-b9b44bf000735135e1913fd9c22cd6c1547dbdb3.tar.gz
tor-b9b44bf000735135e1913fd9c22cd6c1547dbdb3.zip
Move confline.c to lib/encoding: it is about encoding key-value pairs
Also, move "unescape_string()" to encoding too, since it's about encoding data as C strings.
Diffstat (limited to 'src/lib/encoding')
-rw-r--r--src/lib/encoding/confline.c388
-rw-r--r--src/lib/encoding/confline.h72
-rw-r--r--src/lib/encoding/cstring.c132
-rw-r--r--src/lib/encoding/cstring.h13
-rw-r--r--src/lib/encoding/include.am4
5 files changed, 609 insertions, 0 deletions
diff --git a/src/lib/encoding/confline.c b/src/lib/encoding/confline.c
new file mode 100644
index 0000000000..7f535b321a
--- /dev/null
+++ b/src/lib/encoding/confline.c
@@ -0,0 +1,388 @@
+/* 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 */
+
+#include "lib/encoding/confline.h"
+#include "lib/encoding/cstring.h"
+#include "lib/log/torlog.h"
+#include "lib/log/util_bug.h"
+#include "lib/malloc/util_malloc.h"
+#include "lib/string/compat_ctype.h"
+#include "lib/string/compat_string.h"
+#include "lib/string/util_string.h"
+
+#include <string.h>
+
+/** Helper: allocate a new configuration option mapping 'key' to 'val',
+ * append it to *<b>lst</b>. */
+void
+config_line_append(config_line_t **lst,
+ const char *key,
+ const char *val)
+{
+ tor_assert(lst);
+
+ config_line_t *newline;
+
+ newline = tor_malloc_zero(sizeof(config_line_t));
+ newline->key = tor_strdup(key);
+ newline->value = tor_strdup(val);
+ newline->next = NULL;
+ while (*lst)
+ lst = &((*lst)->next);
+
+ (*lst) = newline;
+}
+
+/** Helper: allocate a new configuration option mapping 'key' to 'val',
+ * and prepend it to *<b>lst</b> */
+void
+config_line_prepend(config_line_t **lst,
+ const char *key,
+ const char *val)
+{
+ tor_assert(lst);
+
+ config_line_t *newline;
+
+ newline = tor_malloc_zero(sizeof(config_line_t));
+ newline->key = tor_strdup(key);
+ newline->value = tor_strdup(val);
+ newline->next = *lst;
+ *lst = newline;
+}
+
+/** Return the first line in <b>lines</b> whose key is exactly <b>key</b>, or
+ * NULL if no such key exists.
+ *
+ * (In options parsing, this is for handling commandline-only options only;
+ * other options should be looked up in the appropriate data structure.) */
+const config_line_t *
+config_line_find(const config_line_t *lines,
+ const char *key)
+{
+ const config_line_t *cl;
+ for (cl = lines; cl; cl = cl->next) {
+ if (!strcmp(cl->key, key))
+ return cl;
+ }
+ return NULL;
+}
+
+/** Auxiliary function that does all the work of config_get_lines.
+ * <b>recursion_level</b> is the count of how many nested %includes we have.
+ * <b>opened_lst</b> will have a list of opened files if provided.
+ * Returns the a pointer to the last element of the <b>result</b> in
+ * <b>last</b>. */
+int
+config_get_lines_aux(const char *string, config_line_t **result, int extended,
+ int allow_include, int *has_include,
+ struct smartlist_t *opened_lst, int recursion_level,
+ config_line_t **last,
+ include_handler_fn handle_include)
+{
+ config_line_t *list = NULL, **next, *list_last = NULL;
+ char *k, *v;
+ const char *parse_err;
+ int include_used = 0;
+
+ if (recursion_level > MAX_INCLUDE_RECURSION_LEVEL) {
+ log_warn(LD_CONFIG, "Error while parsing configuration: more than %d "
+ "nested %%includes.", MAX_INCLUDE_RECURSION_LEVEL);
+ return -1;
+ }
+
+ next = &list;
+ do {
+ k = v = NULL;
+ string = parse_config_line_from_str_verbose(string, &k, &v, &parse_err);
+ if (!string) {
+ log_warn(LD_CONFIG, "Error while parsing configuration: %s",
+ parse_err?parse_err:"<unknown>");
+ config_free_lines(list);
+ tor_free(k);
+ tor_free(v);
+ return -1;
+ }
+ if (k && v) {
+ unsigned command = CONFIG_LINE_NORMAL;
+ if (extended) {
+ if (k[0] == '+') {
+ char *k_new = tor_strdup(k+1);
+ tor_free(k);
+ k = k_new;
+ command = CONFIG_LINE_APPEND;
+ } else if (k[0] == '/') {
+ char *k_new = tor_strdup(k+1);
+ tor_free(k);
+ k = k_new;
+ tor_free(v);
+ v = tor_strdup("");
+ command = CONFIG_LINE_CLEAR;
+ }
+ }
+
+ if (allow_include && !strcmp(k, "%include") && handle_include) {
+ tor_free(k);
+ include_used = 1;
+
+ config_line_t *include_list;
+ if (handle_include(v, recursion_level, extended, &include_list,
+ &list_last, opened_lst) < 0) {
+ log_warn(LD_CONFIG, "Error reading included configuration "
+ "file or directory: \"%s\".", v);
+ config_free_lines(list);
+ tor_free(v);
+ return -1;
+ }
+ *next = include_list;
+ if (list_last)
+ next = &list_last->next;
+ tor_free(v);
+ } else {
+ /* This list can get long, so we keep a pointer to the end of it
+ * rather than using config_line_append over and over and getting
+ * n^2 performance. */
+ *next = tor_malloc_zero(sizeof(**next));
+ (*next)->key = k;
+ (*next)->value = v;
+ (*next)->next = NULL;
+ (*next)->command = command;
+ list_last = *next;
+ next = &((*next)->next);
+ }
+ } else {
+ tor_free(k);
+ tor_free(v);
+ }
+ } while (*string);
+
+ if (last) {
+ *last = list_last;
+ }
+ if (has_include) {
+ *has_include = include_used;
+ }
+ *result = list;
+ return 0;
+}
+
+/** Same as config_get_lines_include but does not allow %include */
+int
+config_get_lines(const char *string, config_line_t **result, int extended)
+{
+ return config_get_lines_aux(string, result, extended, 0, NULL, NULL, 1,
+ NULL, NULL);
+}
+
+/**
+ * Free all the configuration lines on the linked list <b>front</b>.
+ */
+void
+config_free_lines_(config_line_t *front)
+{
+ config_line_t *tmp;
+
+ while (front) {
+ tmp = front;
+ front = tmp->next;
+
+ tor_free(tmp->key);
+ tor_free(tmp->value);
+ tor_free(tmp);
+ }
+}
+
+/** Return a newly allocated deep copy of the lines in <b>inp</b>. */
+config_line_t *
+config_lines_dup(const config_line_t *inp)
+{
+ return config_lines_dup_and_filter(inp, NULL);
+}
+
+/** Return a newly allocated deep copy of the lines in <b>inp</b>,
+ * but only the ones whose keys begin with <b>key</b> (case-insensitive).
+ * If <b>key</b> is NULL, do not filter. */
+config_line_t *
+config_lines_dup_and_filter(const config_line_t *inp,
+ const char *key)
+{
+ config_line_t *result = NULL;
+ config_line_t **next_out = &result;
+ while (inp) {
+ if (key && strcasecmpstart(inp->key, key)) {
+ inp = inp->next;
+ continue;
+ }
+ *next_out = tor_malloc_zero(sizeof(config_line_t));
+ (*next_out)->key = tor_strdup(inp->key);
+ (*next_out)->value = tor_strdup(inp->value);
+ inp = inp->next;
+ next_out = &((*next_out)->next);
+ }
+ (*next_out) = NULL;
+ return result;
+}
+
+/** Return true iff a and b contain identical keys and values in identical
+ * order. */
+int
+config_lines_eq(config_line_t *a, config_line_t *b)
+{
+ while (a && b) {
+ if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value))
+ return 0;
+ a = a->next;
+ b = b->next;
+ }
+ if (a || b)
+ return 0;
+ return 1;
+}
+
+/** Return the number of lines in <b>a</b> whose key is <b>key</b>. */
+int
+config_count_key(const config_line_t *a, const char *key)
+{
+ int n = 0;
+ while (a) {
+ if (!strcasecmp(a->key, key)) {
+ ++n;
+ }
+ a = a->next;
+ }
+ return n;
+}
+
+/** Given a string containing part of a configuration file or similar format,
+ * advance past comments and whitespace and try to parse a single line. If we
+ * parse a line successfully, set *<b>key_out</b> to a new string holding the
+ * key portion and *<b>value_out</b> to a new string holding the value portion
+ * of the line, and return a pointer to the start of the next line. If we run
+ * out of data, return a pointer to the end of the string. If we encounter an
+ * error, return NULL and set *<b>err_out</b> (if provided) to an error
+ * message.
+ */
+const char *
+parse_config_line_from_str_verbose(const char *line, char **key_out,
+ char **value_out,
+ const char **err_out)
+{
+ /*
+ See torrc_format.txt for a description of the (silly) format this parses.
+ */
+ const char *key, *val, *cp;
+ int continuation = 0;
+
+ tor_assert(key_out);
+ tor_assert(value_out);
+
+ *key_out = *value_out = NULL;
+ key = val = NULL;
+ /* Skip until the first keyword. */
+ while (1) {
+ while (TOR_ISSPACE(*line))
+ ++line;
+ if (*line == '#') {
+ while (*line && *line != '\n')
+ ++line;
+ } else {
+ break;
+ }
+ }
+
+ if (!*line) { /* End of string? */
+ *key_out = *value_out = NULL;
+ return line;
+ }
+
+ /* Skip until the next space or \ followed by newline. */
+ key = line;
+ while (*line && !TOR_ISSPACE(*line) && *line != '#' &&
+ ! (line[0] == '\\' && line[1] == '\n'))
+ ++line;
+ *key_out = tor_strndup(key, line-key);
+
+ /* Skip until the value. */
+ while (*line == ' ' || *line == '\t')
+ ++line;
+
+ val = line;
+
+ /* Find the end of the line. */
+ if (*line == '\"') { // XXX No continuation handling is done here
+ if (!(line = unescape_string(line, value_out, NULL))) {
+ if (err_out)
+ *err_out = "Invalid escape sequence in quoted string";
+ return NULL;
+ }
+ while (*line == ' ' || *line == '\t')
+ ++line;
+ if (*line == '\r' && *(++line) == '\n')
+ ++line;
+ if (*line && *line != '#' && *line != '\n') {
+ if (err_out)
+ *err_out = "Excess data after quoted string";
+ return NULL;
+ }
+ } else {
+ /* Look for the end of the line. */
+ while (*line && *line != '\n' && (*line != '#' || continuation)) {
+ if (*line == '\\' && line[1] == '\n') {
+ continuation = 1;
+ line += 2;
+ } else if (*line == '#') {
+ do {
+ ++line;
+ } while (*line && *line != '\n');
+ if (*line == '\n')
+ ++line;
+ } else {
+ ++line;
+ }
+ }
+
+ if (*line == '\n') {
+ cp = line++;
+ } else {
+ cp = line;
+ }
+ /* Now back cp up to be the last nonspace character */
+ while (cp>val && TOR_ISSPACE(*(cp-1)))
+ --cp;
+
+ tor_assert(cp >= val);
+
+ /* Now copy out and decode the value. */
+ *value_out = tor_strndup(val, cp-val);
+ if (continuation) {
+ char *v_out, *v_in;
+ v_out = v_in = *value_out;
+ while (*v_in) {
+ if (*v_in == '#') {
+ do {
+ ++v_in;
+ } while (*v_in && *v_in != '\n');
+ if (*v_in == '\n')
+ ++v_in;
+ } else if (v_in[0] == '\\' && v_in[1] == '\n') {
+ v_in += 2;
+ } else {
+ *v_out++ = *v_in++;
+ }
+ }
+ *v_out = '\0';
+ }
+ }
+
+ if (*line == '#') {
+ do {
+ ++line;
+ } while (*line && *line != '\n');
+ }
+ while (TOR_ISSPACE(*line)) ++line;
+
+ return line;
+}
diff --git a/src/lib/encoding/confline.h b/src/lib/encoding/confline.h
new file mode 100644
index 0000000000..f03faed5eb
--- /dev/null
+++ b/src/lib/encoding/confline.h
@@ -0,0 +1,72 @@
+/* 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 */
+
+#ifndef TOR_CONFLINE_H
+#define TOR_CONFLINE_H
+
+struct smartlist_t;
+
+/** Ordinary configuration line. */
+#define CONFIG_LINE_NORMAL 0
+/** Appends to previous configuration for the same option, even if we
+ * would ordinary replace it. */
+#define CONFIG_LINE_APPEND 1
+/* Removes all previous configuration for an option. */
+#define CONFIG_LINE_CLEAR 2
+
+#define MAX_INCLUDE_RECURSION_LEVEL 31
+
+/** A linked list of lines in a config file, or elsewhere */
+typedef struct config_line_t {
+ char *key;
+ char *value;
+ struct config_line_t *next;
+
+ /** What special treatment (if any) does this line require? */
+ unsigned int command:2;
+ /** If true, subsequent assignments to this linelist should replace
+ * it, not extend it. Set only on the first item in a linelist in an
+ * or_options_t. */
+ unsigned int fragile:1;
+} config_line_t;
+
+void config_line_append(config_line_t **lst,
+ const char *key, const char *val);
+void config_line_prepend(config_line_t **lst,
+ const char *key, const char *val);
+config_line_t *config_lines_dup(const config_line_t *inp);
+config_line_t *config_lines_dup_and_filter(const config_line_t *inp,
+ const char *key);
+const config_line_t *config_line_find(const config_line_t *lines,
+ const char *key);
+int config_lines_eq(config_line_t *a, config_line_t *b);
+int config_count_key(const config_line_t *a, const char *key);
+void config_free_lines_(config_line_t *front);
+#define config_free_lines(front) \
+ do { \
+ config_free_lines_(front); \
+ (front) = NULL; \
+ } while (0)
+const char *parse_config_line_from_str_verbose(const char *line,
+ char **key_out, char **value_out,
+ const char **err_out);
+
+int config_get_lines(const char *string, struct config_line_t **result,
+ int extended);
+
+typedef int (*include_handler_fn)(const char *, int, int,
+ struct config_line_t **,
+ struct config_line_t **,
+ struct smartlist_t *);
+
+int config_get_lines_aux(const char *string, struct config_line_t **result,
+ int extended,
+ int allow_include, int *has_include,
+ struct smartlist_t *opened_lst, int recursion_level,
+ config_line_t **last,
+ include_handler_fn handle_include);
+
+#endif /* !defined(TOR_CONFLINE_H) */
diff --git a/src/lib/encoding/cstring.c b/src/lib/encoding/cstring.c
new file mode 100644
index 0000000000..86c17f0d2c
--- /dev/null
+++ b/src/lib/encoding/cstring.c
@@ -0,0 +1,132 @@
+/* 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 */
+
+#include "lib/encoding/cstring.h"
+#include "lib/log/torlog.h"
+#include "lib/log/util_bug.h"
+#include "lib/malloc/util_malloc.h"
+#include "lib/string/compat_ctype.h"
+
+#include <string.h>
+
+#define TOR_ISODIGIT(c) ('0' <= (c) && (c) <= '7')
+
+/** Given a c-style double-quoted escaped string in <b>s</b>, extract and
+ * decode its contents into a newly allocated string. On success, assign this
+ * string to *<b>result</b>, assign its length to <b>size_out</b> (if
+ * provided), and return a pointer to the position in <b>s</b> immediately
+ * after the string. On failure, return NULL.
+ */
+const char *
+unescape_string(const char *s, char **result, size_t *size_out)
+{
+ const char *cp;
+ char *out;
+ if (s[0] != '\"')
+ return NULL;
+ cp = s+1;
+ while (1) {
+ switch (*cp) {
+ case '\0':
+ case '\n':
+ return NULL;
+ case '\"':
+ goto end_of_loop;
+ case '\\':
+ if (cp[1] == 'x' || cp[1] == 'X') {
+ if (!(TOR_ISXDIGIT(cp[2]) && TOR_ISXDIGIT(cp[3])))
+ return NULL;
+ cp += 4;
+ } else if (TOR_ISODIGIT(cp[1])) {
+ cp += 2;
+ if (TOR_ISODIGIT(*cp)) ++cp;
+ if (TOR_ISODIGIT(*cp)) ++cp;
+ } else if (cp[1] == 'n' || cp[1] == 'r' || cp[1] == 't' || cp[1] == '"'
+ || cp[1] == '\\' || cp[1] == '\'') {
+ cp += 2;
+ } else {
+ return NULL;
+ }
+ break;
+ default:
+ ++cp;
+ break;
+ }
+ }
+ end_of_loop:
+ out = *result = tor_malloc(cp-s + 1);
+ cp = s+1;
+ while (1) {
+ switch (*cp)
+ {
+ case '\"':
+ *out = '\0';
+ if (size_out) *size_out = out - *result;
+ return cp+1;
+
+ /* LCOV_EXCL_START -- we caught this in parse_config_from_line. */
+ case '\0':
+ tor_fragile_assert();
+ tor_free(*result);
+ return NULL;
+ /* LCOV_EXCL_STOP */
+ case '\\':
+ switch (cp[1])
+ {
+ case 'n': *out++ = '\n'; cp += 2; break;
+ case 'r': *out++ = '\r'; cp += 2; break;
+ case 't': *out++ = '\t'; cp += 2; break;
+ case 'x': case 'X':
+ {
+ int x1, x2;
+
+ x1 = hex_decode_digit(cp[2]);
+ x2 = hex_decode_digit(cp[3]);
+ if (x1 == -1 || x2 == -1) {
+ /* LCOV_EXCL_START */
+ /* we caught this above in the initial loop. */
+ tor_assert_nonfatal_unreached();
+ tor_free(*result);
+ return NULL;
+ /* LCOV_EXCL_STOP */
+ }
+
+ *out++ = ((x1<<4) + x2);
+ cp += 4;
+ }
+ break;
+ case '0': case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7':
+ {
+ int n = cp[1]-'0';
+ cp += 2;
+ if (TOR_ISODIGIT(*cp)) { n = n*8 + *cp-'0'; cp++; }
+ if (TOR_ISODIGIT(*cp)) { n = n*8 + *cp-'0'; cp++; }
+ if (n > 255) { tor_free(*result); return NULL; }
+ *out++ = (char)n;
+ }
+ break;
+ case '\'':
+ case '\"':
+ case '\\':
+ case '\?':
+ *out++ = cp[1];
+ cp += 2;
+ break;
+
+ /* LCOV_EXCL_START */
+ default:
+ /* we caught this above in the initial loop. */
+ tor_assert_nonfatal_unreached();
+ tor_free(*result); return NULL;
+ /* LCOV_EXCL_STOP */
+ }
+ break;
+ default:
+ *out++ = *cp++;
+ }
+ }
+}
diff --git a/src/lib/encoding/cstring.h b/src/lib/encoding/cstring.h
new file mode 100644
index 0000000000..3dff5e7f7f
--- /dev/null
+++ b/src/lib/encoding/cstring.h
@@ -0,0 +1,13 @@
+/* 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 */
+
+#ifndef TOR_CSTRING_H
+#define TOR_CSTRING_H
+
+#include <stddef.h>
+const char *unescape_string(const char *s, char **result, size_t *size_out);
+
+#endif /* !defined(TOR_CSTRING_H) */
diff --git a/src/lib/encoding/include.am b/src/lib/encoding/include.am
index 345fe14e1c..93f515dd45 100644
--- a/src/lib/encoding/include.am
+++ b/src/lib/encoding/include.am
@@ -6,6 +6,8 @@ endif
src_lib_libtor_encoding_a_SOURCES = \
src/lib/encoding/binascii.c \
+ src/lib/encoding/confline.c \
+ src/lib/encoding/cstring.c \
src/lib/encoding/time_fmt.c
src_lib_libtor_encoding_testing_a_SOURCES = \
@@ -15,4 +17,6 @@ src_lib_libtor_encoding_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
noinst_HEADERS += \
src/lib/encoding/binascii.h \
+ src/lib/encoding/confline.h \
+ src/lib/encoding/cstring.h \
src/lib/encoding/time_fmt.h