diff options
-rw-r--r-- | src/common/util.c | 139 | ||||
-rw-r--r-- | src/common/util.h | 6 | ||||
-rw-r--r-- | src/or/test.c | 84 |
3 files changed, 229 insertions, 0 deletions
diff --git a/src/common/util.c b/src/common/util.c index 1ca3f8d859..828b3ca038 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -2185,6 +2185,145 @@ expand_filename(const char *filename) } } +#define MAX_SCANF_WIDTH 9999 + +/** DOCDOC */ +static int +digit_to_num(char d) +{ + int num = ((int)d) - (int)'0'; + tor_assert(num <= 9 && num >= 0); + return num; +} + +/** DOCDOC */ +static int +scan_unsigned(const char **bufp, unsigned *out, int width) +{ + unsigned result = 0; + int scanned_so_far = 0; + if (!bufp || !*bufp) + return -1; + if (width<0) + width=MAX_SCANF_WIDTH; + + while (**bufp && TOR_ISDIGIT(**bufp) && scanned_so_far < width) { + int digit = digit_to_num(*(*bufp)++); + unsigned new_result = result * 10 + digit; + if (new_result > UINT32_MAX || new_result < result) + return -1; /* over/underflow. */ + result = new_result; + ++scanned_so_far; + } + + if (!scanned_so_far) /* No actual digits scanned */ + return -1; + + *out = result; + return 0; +} + +/** DOCDOC */ +static int +scan_string(const char **bufp, char *out, int width) +{ + int scanned_so_far = 0; + if (!bufp || width < 0) + return -1; + while (**bufp && ! TOR_ISSPACE(**bufp) && scanned_so_far < width) { + *out++ = *(*bufp)++; + ++scanned_so_far; + } + *out = '\0'; + return 0; +} + +/** Locale-independent, minimal, no-surprises scanf variant, accepting only a + * restricted pattern format. For more info on what it supports, see + * tor_sscanf() documentation. */ +int +tor_vsscanf(const char *buf, const char *pattern, va_list ap) +{ + int n_matched = 0; + + while (*pattern) { + if (*pattern != '%') { + if (*buf == *pattern) { + ++buf; + ++pattern; + continue; + } else { + return n_matched; + } + } else { + int width = -1; + ++pattern; + if (TOR_ISDIGIT(*pattern)) { + width = digit_to_num(*pattern++); + while (TOR_ISDIGIT(*pattern)) { + width *= 10; + width += digit_to_num(*pattern++); + if (width > MAX_SCANF_WIDTH) + return -1; + } + if (!width) /* No zero-width things. */ + return -1; + } + if (*pattern == 'u') { + unsigned *u = va_arg(ap, unsigned *); + if (!*buf) + return n_matched; + if (scan_unsigned(&buf, u, width)<0) + return n_matched; + ++pattern; + ++n_matched; + } else if (*pattern == 's') { + char *s = va_arg(ap, char *); + if (width < 0) + return -1; + if (scan_string(&buf, s, width)<0) + return n_matched; + ++pattern; + ++n_matched; + } else if (*pattern == 'c') { + char *ch = va_arg(ap, char *); + if (width != -1) + return -1; + if (!*buf) + return n_matched; + *ch = *buf++; + ++pattern; + ++n_matched; + } else if (*pattern == '%') { + if (*buf != '%') + return -1; + ++buf; + ++pattern; + } else { + return -1; /* Unrecognized pattern component. */ + } + } + } + + return n_matched; +} + +/** Minimal sscanf replacement: parse <b>buf</b> according to <b>pattern</b> + * and store the results in the corresponding argument fields. Differs from + * sscanf in that it: Only handles %u and %Ns. Does not handle arbitrarily + * long widths. %u does not consume any space. Is locale-independent. + * Returns -1 on malformed */ +int +tor_sscanf(const char *buf, const char *pattern, ...) +{ + int r; + va_list ap; + va_start(ap, pattern); + r = tor_vsscanf(buf, pattern, ap); + va_end(ap); + return r; +} + /** Return a new list containing the filenames in the directory <b>dirname</b>. * Return NULL on error or if <b>dirname</b> is not a directory. */ diff --git a/src/common/util.h b/src/common/util.h index 93db417f11..cbf5334c15 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -195,6 +195,12 @@ const char *escaped(const char *string); struct smartlist_t; void wrap_string(struct smartlist_t *out, const char *string, size_t width, const char *prefix0, const char *prefixRest); +int tor_vsscanf(const char *buf, const char *pattern, va_list ap); +int tor_sscanf(const char *buf, const char *pattern, ...) +#ifdef __GNUC__ + __attribute__((format(scanf, 2, 3))) +#endif + ; int hex_decode_digit(char c); void base16_encode(char *dest, size_t destlen, const char *src, size_t srclen); diff --git a/src/or/test.c b/src/or/test.c index e86db9a003..46f717e893 100644 --- a/src/or/test.c +++ b/src/or/test.c @@ -2747,6 +2747,89 @@ test_util_control_formats(void) tor_free(out); } +static void +test_util_sscanf(void) +{ + unsigned u1, u2, u3; + char s1[10], s2[10], s3[10], ch; + int r; + + r = tor_sscanf("hello world", "hello world"); /* String match: success */ + test_eq(r, 0); + r = tor_sscanf("hello world 3", "hello worlb %u", &u1); /* String fail */ + test_eq(r, 0); + r = tor_sscanf("12345", "%u", &u1); /* Simple number */ + test_eq(r, 1); + test_eq(u1, 12345u); + r = tor_sscanf("", "%u", &u1); /* absent number */ + test_eq(r, 0); + r = tor_sscanf("A", "%u", &u1); /* bogus number */ + test_eq(r, 0); + r = tor_sscanf("4294967295", "%u", &u1); /* UINT32_MAX should work. */ + test_eq(r, 1); + test_eq(u1, 4294967295u); + r = tor_sscanf("4294967296", "%u", &u1); /* Always say -1 at 32 bits. */ + test_eq(r, 0); + r = tor_sscanf("123456", "%2u%u", &u1, &u2); /* Width */ + test_eq(r, 2); + test_eq(u1, 12u); + test_eq(u2, 3456u); + r = tor_sscanf("!12:3:456", "!%2u:%2u:%3u", &u1, &u2, &u3); /* separators */ + test_eq(r, 3); + test_eq(u1, 12u); + test_eq(u2, 3u); + test_eq(u3, 456u); + r = tor_sscanf("12:3:045", "%2u:%2u:%3u", &u1, &u2, &u3); /* 0s */ + test_eq(r, 3); + test_eq(u1, 12u); + test_eq(u2, 3u); + test_eq(u3, 45u); + /* %u does not match space.*/ + r = tor_sscanf("12:3: 45", "%2u:%2u:%3u", &u1, &u2, &u3); + test_eq(r, 2); + /* %u does not match negative numbers. */ + r = tor_sscanf("12:3:-4", "%2u:%2u:%3u", &u1, &u2, &u3); + test_eq(r, 2); + /* Arbitrary amounts of 0-padding are okay */ + r = tor_sscanf("12:03:000000000000000099", "%2u:%2u:%u", &u1, &u2, &u3); + test_eq(r, 3); + test_eq(u1, 12u); + test_eq(u2, 3u); + test_eq(u3, 99u); + r = tor_sscanf("hello", "%s", s1); /* %s needs a number. */ + test_eq(r, -1); + + r = tor_sscanf("hello", "%3s%7s", s1, s2); /* %s matches characters. */ + test_eq(r, 2); + test_streq(s1, "hel"); + test_streq(s2, "lo"); + r = tor_sscanf("WD40", "%2s%u", s3, &u1); /* %s%u */ + test_eq(r, 2); + test_streq(s3, "WD"); + test_eq(u1, 40); + r = tor_sscanf("76trombones", "%6u%9s", &u1, s1); /* %u%s */ + test_eq(r, 2); + test_eq(u1, 76); + test_streq(s1, "trombones"); + r = tor_sscanf("hello world", "%9s %9s", s1, s2); /* %s doesn't eat space. */ + test_eq(r, 2); + test_streq(s1, "hello"); + test_streq(s2, "world"); + r = tor_sscanf("hi", "%9s%9s%3s", s1, s2, s3); /* %s can be empty. */ + test_eq(r, 3); + test_streq(s1, "hi"); + test_streq(s2, ""); + test_streq(s3, ""); + + r = tor_sscanf("1.2.3", "%u.%u.%u%c", &u1, &u2, &u3, &ch); + test_eq(r, 3); + r = tor_sscanf("1.2.3 foobar", "%u.%u.%u%c", &u1, &u2, &u3, &ch); + test_eq(r, 4); + + done: + ; +} + /** Run unit tests for the onion handshake code. */ static void test_onion_handshake(void) @@ -4665,6 +4748,7 @@ static struct { SUBENT(util, mmap), SUBENT(util, threads), SUBENT(util, order_functions), + SUBENT(util, sscanf), ENT(onion_handshake), ENT(dir_format), ENT(dirutil), |