From 58de695f9062576f74d3713e8dcb66e8b8a48a09 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 2 Jan 2008 06:59:15 +0000 Subject: [PATCH] r15787@tombo: nickm | 2008-01-02 01:59:07 -0500 Allow config values in quotes to contain special characters, with full C escape syntax. With tests. Addresses bug 557. svn:r13021 --- ChangeLog | 5 ++ doc/tor.1.in | 5 +- src/common/util.c | 122 +++++++++++++++++++++++++++++++++++++++++----- src/or/test.c | 21 ++++++-- 4 files changed, 135 insertions(+), 18 deletions(-) diff --git a/ChangeLog b/ChangeLog index f763836f1a..6d656cc864 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,11 @@ Changes in version 0.2.0.16-alpha - 2008-01-?? implementation also avoids realloc();realloc(); patterns that can contribute to memory fragmentation. + o Minor features: + - Configuration files now accept C-style strings as values. This + helps encode characters not allowed in the current configuration + file format, such as newline or #. Addresses bug 557. + o Minor performance improvements: - Reference-count and share copies of address policy entries; only 5% of them were actually distinct. diff --git a/doc/tor.1.in b/doc/tor.1.in index 16dab5528b..3860d7e881 100644 --- a/doc/tor.1.in +++ b/doc/tor.1.in @@ -53,8 +53,9 @@ Display Tor version. .LP .TP Other options can be specified either on the command-line (\fI--option -value\fR), or in the configuration file (\fIoption value\fR). -Options are case-insensitive. +value\fR), or in the configuration file (\fIoption value\fR or +\fIoption "value"\fR). Options are case-insensitive. C-style escaped +characters are allowed inside quoted values. .LP .TP \fBBandwidthRate \fR\fIN\fR \fBbytes\fR|\fBKB\fR|\fBMB\fR|\fBGB\fR|\fBTB\fP diff --git a/src/common/util.c b/src/common/util.c index 5ff97273a8..23e1bc639b 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -1924,6 +1924,95 @@ read_file_to_str(const char *filename, int flags, struct stat *stat_out) return string; } +#define TOR_ISODIGIT(c) ('0' <= (c) && (c) <= '7') + +/* DOCDOC */ +static const char * +unescape_string(const char *s, char **result, size_t *size_out) +{ + const char *cp; + char *out; + tor_assert(s[0] == '\"'); + 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') + && TOR_ISXDIGIT(cp[2]) && TOR_ISXDIGIT(cp[3])) { + cp += 4; + } else if (TOR_ISODIGIT(cp[1])) { + cp += 2; + if (TOR_ISODIGIT(*cp)) ++cp; + if (TOR_ISODIGIT(*cp)) ++cp; + } else if (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; + case '\0': + tor_fragile_assert(); + tor_free(*result); + return NULL; + 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': + *out++ = ((hex_decode_digit(cp[2])<<4) + + hex_decode_digit(cp[3])); + 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; + default: + tor_free(*result); return NULL; + } + break; + default: + *out++ = *cp++; + } + } +} + /** 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 *key_out to a new string holding the @@ -1965,33 +2054,40 @@ parse_config_line_from_str(const char *line, char **key_out, char **value_out) ++line; *key_out = tor_strndup(key, line-key); - /* Skip until the value, writing nuls so key will be nul-terminated */ + /* Skip until the value. */ while (*line == ' ' || *line == '\t') ++line; val = line; /* Find the end of the line. */ - while (*line && *line != '\n' && *line != '#') - ++line; - if (*line == '\n') { - cp = line++; + if (*line == '\"') { + line = unescape_string(line, value_out, NULL); + while (*line == ' ' || *line == '\t') + ++line; + if (*line && *line != '#' && *line != '\n') + return NULL; } else { - cp = line; - } - while (cp>val && TOR_ISSPACE(*(cp-1))) - --cp; + while (*line && *line != '\n' && *line != '#') + ++line; + if (*line == '\n') { + cp = line++; + } else { + cp = line; + } + while (cp>val && TOR_ISSPACE(*(cp-1))) + --cp; - tor_assert(cp >= val); - *value_out = tor_strndup(val, cp-val); + tor_assert(cp >= val); + *value_out = tor_strndup(val, cp-val); + } if (*line == '#') { do { ++line; } while (*line && *line != '\n'); - if (*line == '\n') - ++line; } + while(TOR_ISSPACE(*line)) ++line; return line; } diff --git a/src/or/test.c b/src/or/test.c index 20bbc49102..5a1d317ef0 100644 --- a/src/or/test.c +++ b/src/or/test.c @@ -826,14 +826,17 @@ test_util(void) strlcpy(buf, "k v\n" " key value with spaces \n" "keykey val\n" "k2\n" "k3 \n" "\n" " \n" "#comment\n" - "k4#a\n" "k5#abc\n" "k6 val #with comment\n", sizeof(buf)); + "k4#a\n" "k5#abc\n" "k6 val #with comment\n" + "kseven \"a quoted 'string\"\n" + "k8 \"a \\x71uoted\\n\\\"str\\\\ing\\t\\001\\01\\1\\\"\"\n" + , sizeof(buf)); str = buf; str = parse_config_line_from_str(str, &k, &v); test_streq(k, "k"); test_streq(v, "v"); tor_free(k); tor_free(v); - test_assert(!strcmpstart(str, " key value with")); + test_assert(!strcmpstart(str, "key value with")); str = parse_config_line_from_str(str, &k, &v); test_streq(k, "key"); @@ -857,7 +860,7 @@ test_util(void) test_streq(k, "k3"); test_streq(v, ""); tor_free(k); tor_free(v); - test_assert(!strcmpstart(str, "\n \n")); + test_assert(!strcmpstart(str, "#comment")); str = parse_config_line_from_str(str, &k, &v); test_streq(k, "k4"); @@ -875,6 +878,18 @@ test_util(void) test_streq(k, "k6"); test_streq(v, "val"); tor_free(k); tor_free(v); + test_assert(!strcmpstart(str, "kseven")); + + str = parse_config_line_from_str(str, &k, &v); + test_streq(k, "kseven"); + test_streq(v, "a quoted 'string"); + tor_free(k); tor_free(v); + test_assert(!strcmpstart(str, "k8 ")); + + str = parse_config_line_from_str(str, &k, &v); + test_streq(k, "k8"); + test_streq(v, "a quoted\n\"str\\ing\t\x01\x01\x01\""); + tor_free(k); tor_free(v); test_streq(str, ""); /* Test for strcmpstart and strcmpend. */