From 78c3cebc389a2b0d426d74900a65b8656edb426d Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Mon, 26 Oct 2026 18:13:27 +0100 Subject: Third run of making variable and function names more readable, integer scans and ip parsers --- opentracker.c | 4 +- ot_config.c | 22 ++++----- ot_http.c | 23 +++++----- ot_ip.c | 2 +- ot_ip.h | 2 +- ot_scan.c | 29 ++++++++++-- ot_scan.h | 12 +++-- ot_sync.c | 2 +- ot_urlencoded.c | 13 ------ ot_urlencoded.h | 6 --- proxy.c | 4 +- tests/t_ip.c | 54 +++++++++++----------- tests/t_scan.c | 124 ++++++++++++++++++++++++++++++++++++--------------- tests/t_urlencoded.c | 29 ------------ 14 files changed, 179 insertions(+), 147 deletions(-) diff --git a/opentracker.c b/opentracker.c index f0f6404..7de6c6a 100644 --- a/opentracker.c +++ b/opentracker.c @@ -379,10 +379,10 @@ void load_state(const char *const state_filename) { continue; i = 2 * sizeof(ot_hash); - if (inbuf[i++] != ':' || !(consumed = scan_udec(inbuf + i, &base, UINT64_MAX))) + if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &base, UINT64_MAX))) continue; i += consumed; - if (inbuf[i++] != ':' || !(consumed = scan_udec(inbuf + i, &downcount, UINT64_MAX))) + if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &downcount, UINT64_MAX))) continue; add_torrent_from_saved_state(infohash, base, downcount); } diff --git a/ot_config.c b/ot_config.c index a5fc06e..32625bd 100644 --- a/ot_config.c +++ b/ot_config.c @@ -107,7 +107,7 @@ static int listen_scan_options(char *p, int *shared, int *workers) { *shared = 1; break; case LISTEN_OPTION_WORKERS: { - size_t off = scan_udec(p, &value, UINT_MAX); + size_t off = scan_uint(p, &value, UINT_MAX); if (!off) return 0; @@ -140,7 +140,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) { ++s; if (*s == '[') ++s, ++bracket; /* for v6 style notation */ - if (!(off = ip_scan(s, ip))) + if (!(off = ip_parse(s, ip))) return 0; s += off; if (bracket && *s == ']') @@ -157,7 +157,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) { if (*(s++) != ':') return 0; } - if (!(off = scan_udec(s, &value, 65535))) + if (!(off = scan_uint(s, &value, 65535))) return 0; *port = (uint16_t)value; return off + s - src; @@ -169,14 +169,14 @@ static int scan_ip6_net(const char *src, ot_net *net) { int off; while (isspace(*s)) ++s; - if (!(off = ip_scan(s, net->address))) + if (!(off = ip_parse(s, net->address))) return 0; s += off; if (*s != '/') net->bits = 128; else { s++; - if (!(off = scan_udec(s, &value, 128))) + if (!(off = scan_uint(s, &value, 128))) return 0; net->bits = (int)value; if (ip_is_v4mapped(net->address)) @@ -324,12 +324,12 @@ static int parse_configfile(char *config_filename) { break; } case CONFIG_LISTEN_UDP_WORKERS: - if (!scan_udec(p, &value, UINT_MAX)) + if (!scan_uint(p, &value, UINT_MAX)) goto parse_error; g_listen_workers = (int)value; break; case CONFIG_LISTEN_SHARED: - if (!scan_udec(p, &value, 1)) + if (!scan_uint(p, &value, 1)) goto parse_error; g_listen_shared = (int)value; break; @@ -479,7 +479,7 @@ void config_parse(int argc, char **argv) { scanon = 0; break; case 'i': - if (!ip_scan(optarg, serverip)) { + if (!ip_parse(optarg, serverip)) { usage(argv[0]); exit(1); } @@ -494,7 +494,7 @@ void config_parse(int argc, char **argv) { break; #endif case 'p': - if (!scan_udec(optarg, &value, 65535)) { + if (!scan_uint(optarg, &value, 65535)) { usage(argv[0]); exit(1); } @@ -502,7 +502,7 @@ void config_parse(int argc, char **argv) { bound++; break; case 'P': - if (!scan_udec(optarg, &value, 65535)) { + if (!scan_uint(optarg, &value, 65535)) { usage(argv[0]); exit(1); } @@ -511,7 +511,7 @@ void config_parse(int argc, char **argv) { break; #ifdef WANT_SYNC_LIVE case 's': - if (!scan_udec(optarg, &value, 65535)) { + if (!scan_uint(optarg, &value, 65535)) { usage(argv[0]); exit(1); } diff --git a/ot_http.c b/ot_http.c index 6c3332c..3c135cb 100644 --- a/ot_http.c +++ b/ot_http.c @@ -482,7 +482,8 @@ static ot_keywords keywords_announce[] = {{"port", 1}, {"left", 2}, {"event", static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}}; static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { size_t numwant; - int tmp, scanon; + int64_t tmp; + int scanon; unsigned short port = 0; char *write_ptr; ssize_t len; @@ -501,7 +502,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct if (accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_PROXY)) { ot_ip6 proxied_ip; char *fwd = http_header(ws->request, ws->header_size, "x-forwarded-for"); - if (fwd && ip_scan(fwd, proxied_ip)) { + if (fwd && ip_parse(fwd, proxied_ip)) { OT_SETIP(ws->peer, proxied_ip); } else OT_SETIP(ws->peer, cookie->ip); @@ -529,15 +530,15 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct break; case 1: /* matched "port" */ len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); - if ((len <= 0) || scan_fixed_int(write_ptr, len, &tmp) || (tmp > 0xffff)) + if ((len <= 0) || scan_int(write_ptr, len, &tmp) || (tmp > 0xffff)) HTTPERROR_400_PARAM; - port = htons(tmp); + port = htons((uint16_t)tmp); OT_SETPORT(&ws->peer, &port); break; case 2: /* matched "left" */ if ((len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) HTTPERROR_400_PARAM; - if (scan_fixed_int(write_ptr, len, &tmp)) + if (scan_int(write_ptr, len, &tmp)) tmp = 0; if (!tmp) OT_PEERFLAG(&ws->peer) |= PEER_FLAG_SEEDING; @@ -558,17 +559,17 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct break; case 4: /* matched "numwant" */ len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); - if ((len <= 0) || scan_fixed_int(write_ptr, len, &tmp)) + if ((len <= 0) || scan_int(write_ptr, len, &tmp)) HTTPERROR_400_PARAM; if (tmp < 0) tmp = 50; if (tmp > 200) tmp = 200; - numwant = tmp; + numwant = (size_t)tmp; break; case 5: /* matched "compact" */ len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); - if ((len <= 0) || scan_fixed_int(write_ptr, len, &tmp)) + if ((len <= 0) || scan_int(write_ptr, len, &tmp)) HTTPERROR_400_PARAM; if (!tmp) HTTPERROR_400_COMPACT; @@ -587,7 +588,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply + 16; len = urlencoded_parse(&read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE); tmp_buf2[len] = 0; - if ((len <= 0) || !ip_scan(tmp_buf2, tmp_buf1)) + if ((len <= 0) || !ip_parse(tmp_buf2, tmp_buf1)) HTTPERROR_400_PARAM; OT_SETIP(&ws->peer, tmp_buf1); } break; @@ -609,13 +610,13 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct loglist_reset(); return ws->reply_size = sprintf(ws->reply, "Successfully removed.\n"); } - parsed = ip_scan(tmp_buf, net.address); + parsed = ip_parse(tmp_buf, net.address); if (!parsed) HTTPERROR_400_PARAM; if (tmp_buf[parsed++] != '/') bits = 128; else { - parsed = scan_udec(tmp_buf + parsed, &value, 128); + parsed = scan_uint(tmp_buf + parsed, &value, 128); if (!parsed) HTTPERROR_400_PARAM; bits = (signed short)value; diff --git a/ot_ip.c b/ot_ip.c index 8cde7d2..e694883 100644 --- a/ot_ip.c +++ b/ot_ip.c @@ -120,7 +120,7 @@ size_t ip_format(char *dest, const ot_ip6 ip) { return (size_t)(d - dest); } -size_t ip_scan(const char *src, ot_ip6 ip) { +size_t ip_parse(const char *src, ot_ip6 ip) { uint8_t prefix[16], suffix[16]; size_t prefixlen = 0, suffixlen = 0; const char *s = src; diff --git a/ot_ip.h b/ot_ip.h index ac8964d..344bd45 100644 --- a/ot_ip.h +++ b/ot_ip.h @@ -23,7 +23,7 @@ static inline int ip_is_v4mapped(const void *ip) { return !memcmp(ip, ip_v4mappe The count lets the caller carry on where the address ended, which is how "address:port" and "address/bits" are read. */ -size_t ip_scan(const char *src, ot_ip6 ip); +size_t ip_parse(const char *src, ot_ip6 ip); /* Write an address and return how many characters it took. Nothing is terminated, so a caller may keep appending. A v4 mapped address is written diff --git a/ot_scan.c b/ot_scan.c index a96f82b..c4b951a 100644 --- a/ot_scan.c +++ b/ot_scan.c @@ -11,7 +11,7 @@ /* Opentracker */ #include "ot_scan.h" -size_t scan_udec(const char *src, uint64_t *dest, uint64_t max) { +size_t scan_uint(const char *src, uint64_t *dest, uint64_t max) { unsigned long long value; char *end; @@ -28,9 +28,9 @@ size_t scan_udec(const char *src, uint64_t *dest, uint64_t max) { return (size_t)(end - src); } -size_t scan_udec_bencode(const char *src, uint64_t *dest, uint64_t max) { +size_t scan_uint_bencode(const char *src, uint64_t *dest, uint64_t max) { uint64_t value; - size_t consumed = scan_udec(src, &value, max); + size_t consumed = scan_uint(src, &value, max); /* bencode writes no leading zeros, so anything longer than a single digit must not begin with one. */ @@ -42,3 +42,26 @@ size_t scan_udec_bencode(const char *src, uint64_t *dest, uint64_t max) { return consumed; } + +int scan_int(const char *src, size_t len, int64_t *dest) { + int64_t value = 0; + int negate = 0; + + if (len && *src == '-') + --len, ++src, negate = 1; + if (!len) + return -1; + + while (len--) { + if (*src < '0' || *src > '9') + return -1; + /* A twenty character field of nines does not fit, and silently wrapping + would magically heal a nonsense announce */ + if (value > (INT64_MAX - (*src - '0')) / 10) + return -1; + value = 10 * value + (*src++ - '0'); + } + + *dest = negate ? -value : value; + return 0; +} diff --git a/ot_scan.h b/ot_scan.h index 860a088..ca3bbbd 100644 --- a/ot_scan.h +++ b/ot_scan.h @@ -6,16 +6,22 @@ #ifndef OT_SCAN_H__ #define OT_SCAN_H__ +#include #include #include /* Read an unsigned decimal number from src into dest and return how many characters were consumed. Returns zero and leaves dest untouched when src does not begin with a digit, or when the value would exceed max. */ -size_t scan_udec(const char *src, uint64_t *dest, uint64_t max); +size_t scan_uint(const char *src, uint64_t *dest, uint64_t max); -/* As scan_udec, but following the bencode integer grammar, which permits a +/* As scan_uint, but following the bencode integer grammar, which permits a leading zero on zero alone. */ -size_t scan_udec_bencode(const char *src, uint64_t *dest, uint64_t max); +size_t scan_uint_bencode(const char *src, uint64_t *dest, uint64_t max); + +/* Read a signed decimal number that has to take up exactly len characters, + which is how a value lifted out of a query string arrives. Returns 0, or + -1 leaving dest untouched. */ +int scan_int(const char *src, size_t len, int64_t *dest); #endif diff --git a/ot_sync.c b/ot_sync.c index 5527c94..1a6bebc 100644 --- a/ot_sync.c +++ b/ot_sync.c @@ -51,7 +51,7 @@ int add_changeset_to_tracker(uint8_t *data, size_t len) { data += sizeof(ot_hash); /* Scan string length indicator, strictly: bencode writes no leading zeros */ - data += (len = scan_udec_bencode((char *)data, &peer_count, (uint64_t)(end - data))); + data += (len = scan_uint_bencode((char *)data, &peer_count, (uint64_t)(end - data))); /* If no long was scanned, it is not divisible by 8, it is not followed by a colon or claims to need to much memory, we fail */ diff --git a/ot_urlencoded.c b/ot_urlencoded.c index 1ca6e81..17f12a4 100644 --- a/ot_urlencoded.c +++ b/ot_urlencoded.c @@ -146,19 +146,6 @@ ssize_t urlencoded_parse(char **string, char *deststring, SCAN_SEARCHPATH_FLAG f return d - (unsigned char *)deststring; } -ssize_t scan_fixed_int(char *data, size_t len, int *tmp) { - int minus = 0; - *tmp = 0; - if (*data == '-') - --len, ++data, ++minus; - while ((len > 0) && (*data >= '0') && (*data <= '9')) { - --len; - *tmp = 10 * *tmp + *data++ - '0'; - } - if (minus) - *tmp = -*tmp; - return len; -} size_t urlencoded_format(char *dest, const uint8_t *src, size_t len) { static const char safe[] = "$/.=~-_"; diff --git a/ot_urlencoded.h b/ot_urlencoded.h index 8147370..b6f1208 100644 --- a/ot_urlencoded.h +++ b/ot_urlencoded.h @@ -47,12 +47,6 @@ int urlencoded_match_keyword(const ot_keywords *keywords, char **string, SCA */ void urlencoded_skip_value(char **string); -/* data pointer to len chars of string - len length of chars in data to parse - number number to receive result - returns number of bytes not parsed, mostly !=0 means fail - */ -ssize_t scan_fixed_int(char *data, size_t len, int *number); /* The other direction: percent encode len bytes from src into dest and return how many characters were written. dest needs room for three times len. */ diff --git a/proxy.c b/proxy.c index 0cac20c..a5c6bb7 100644 --- a/proxy.c +++ b/proxy.c @@ -507,7 +507,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) { ++s; if (*s == '[') ++s, ++bracket; /* for v6 style notation */ - if (!(off = ip_scan(s, ip))) + if (!(off = ip_parse(s, ip))) return 0; s += off; if (*s == 0 || isspace(*s)) @@ -522,7 +522,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) { if (*(s++) != ':') return 0; } - if (!(off = scan_udec(s, &value, 65535))) + if (!(off = scan_uint(s, &value, 65535))) return 0; *port = (uint16_t)value; return off + s - src; diff --git a/tests/t_ip.c b/tests/t_ip.c index 2fe3a72..8d71679 100644 --- a/tests/t_ip.c +++ b/tests/t_ip.c @@ -95,27 +95,27 @@ static void case_scan_forms(void) { char ip[16]; test_case("2.2 form 1, every group spelled out"); - CHECK_INT(ip_scan("1111:2222:3333:4444:5555:6666:7777:8888", ip), 39); + CHECK_INT(ip_parse("1111:2222:3333:4444:5555:6666:7777:8888", ip), 39); CHECK_MEM(ip, "\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88", 16); test_case("2.2 form 2, zero groups abbreviated"); - CHECK_INT(ip_scan("2001:db8::1", ip), 11); + CHECK_INT(ip_parse("2001:db8::1", ip), 11); CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); - CHECK_INT(ip_scan("::1", ip), 3); + CHECK_INT(ip_parse("::1", ip), 3); CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); - CHECK_INT(ip_scan("::", ip), 2); + CHECK_INT(ip_parse("::", ip), 2); CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16); - CHECK_INT(ip_scan("1::", ip), 3); + CHECK_INT(ip_parse("1::", ip), 3); CHECK_MEM(ip, "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16); test_case("2.2 form 3, a dotted quad in the last two groups"); - CHECK_INT(ip_scan("::ffff:1.2.3.4", ip), 14); + CHECK_INT(ip_parse("::ffff:1.2.3.4", ip), 14); CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x01\x02\x03\x04", 16); - CHECK_INT(ip_scan("1:2:3:4:5:6:1.2.3.4", ip), 19); + CHECK_INT(ip_parse("1:2:3:4:5:6:1.2.3.4", ip), 19); CHECK_MEM(ip, "\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x01\x02\x03\x04", 16); test_case("upper case input is accepted"); - CHECK_INT(ip_scan("2001:DB8::1", ip), 11); + CHECK_INT(ip_parse("2001:DB8::1", ip), 11); CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); } @@ -125,7 +125,7 @@ static void case_scan_accepts_bare_ipv4(void) { /* Every address opentracker holds is sixteen bytes, so an operator writing a plain IPv4 address in the config has to land as the v4 mapped form */ test_case("a bare IPv4 address is stored v4 mapped"); - CHECK_INT(ip_scan("10.0.0.1", ip), 8); + CHECK_INT(ip_parse("10.0.0.1", ip), 8); CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x0a\x00\x00\x01", 16); CHECK(ip_is_v4mapped(ip)); } @@ -136,39 +136,39 @@ static void case_scan_reports_length(void) { /* The config parser continues from where the address ended, which is how "address:port" and "address/bits" are read */ test_case("scanning stops at the character after the address"); - CHECK_INT(ip_scan("10.0.0.1:6969", ip), 8); - CHECK_INT(ip_scan("2001:db8::1/64", ip), 11); - CHECK_INT(ip_scan("::1]", ip), 3); - CHECK_INT(ip_scan("1:2:3:4:5:6:7:8 trailing", ip), 15); + CHECK_INT(ip_parse("10.0.0.1:6969", ip), 8); + CHECK_INT(ip_parse("2001:db8::1/64", ip), 11); + CHECK_INT(ip_parse("::1]", ip), 3); + CHECK_INT(ip_parse("1:2:3:4:5:6:7:8 trailing", ip), 15); } static void case_scan_rejects(void) { char ip[16]; test_case("an empty or junk string is refused"); - CHECK_INT(ip_scan("", ip), 0); - CHECK_INT(ip_scan("x", ip), 0); - CHECK_INT(ip_scan(":", ip), 0); - CHECK_INT(ip_scan(":1:2", ip), 0); + CHECK_INT(ip_parse("", ip), 0); + CHECK_INT(ip_parse("x", ip), 0); + CHECK_INT(ip_parse(":", ip), 0); + CHECK_INT(ip_parse(":1:2", ip), 0); test_case("too few groups without :: are refused"); - CHECK_INT(ip_scan("1:2:3:4:5:6:7", ip), 0); - CHECK_INT(ip_scan("2001:db8:1", ip), 0); + CHECK_INT(ip_parse("1:2:3:4:5:6:7", ip), 0); + CHECK_INT(ip_parse("2001:db8:1", ip), 0); /* libowfat accepted a prefix of each of these and carried on. Taking only part of a malformed address is worse than refusing it, especially where the text comes from a config file or an X-Forwarded-For header. */ test_case("a second :: is refused rather than truncated"); - CHECK_INT(ip_scan("1::2::3", ip), 0); - CHECK_INT(ip_scan("::1::", ip), 0); - CHECK_INT(ip_scan("::::", ip), 0); + CHECK_INT(ip_parse("1::2::3", ip), 0); + CHECK_INT(ip_parse("::1::", ip), 0); + CHECK_INT(ip_parse("::::", ip), 0); test_case("more than eight groups is refused"); - CHECK_INT(ip_scan("1:2:3:4:5:6:7:8:9", ip), 0); + CHECK_INT(ip_parse("1:2:3:4:5:6:7:8:9", ip), 0); test_case("a group of more than four digits is refused, not truncated"); - CHECK_INT(ip_scan("12345::1", ip), 0); - CHECK_INT(ip_scan("fffff::1", ip), 0); + CHECK_INT(ip_parse("12345::1", ip), 0); + CHECK_INT(ip_parse("fffff::1", ip), 0); } static void case_round_trip(void) { @@ -187,7 +187,7 @@ static void case_round_trip(void) { test_case("every formatted address scans back to the same bytes"); for (i = 0; samples[i]; ++i) { - size_t n = ip_scan(samples[i], ip); + size_t n = ip_parse(samples[i], ip); if (n != strlen(samples[i])) { test_failf(__FILE__, __LINE__, "\"%s\" consumed %zu of %zu", samples[i], n, strlen(samples[i])); return; @@ -197,7 +197,7 @@ static void case_round_trip(void) { test_failf(__FILE__, __LINE__, "\"%s\" formatted back as \"%s\"", samples[i], text); return; } - if (!ip_scan(text, again) || memcmp(ip, again, 16)) { + if (!ip_parse(text, again) || memcmp(ip, again, 16)) { test_failf(__FILE__, __LINE__, "\"%s\" did not survive a second pass", samples[i]); return; } diff --git a/tests/t_scan.c b/tests/t_scan.c index 89f1590..aab4413 100644 --- a/tests/t_scan.c +++ b/tests/t_scan.c @@ -15,13 +15,13 @@ static void case_plain_numbers(void) { uint64_t value; test_case("a decimal number is read and its length reported"); - CHECK_INT(scan_udec("0", &value, 65535), 1); + CHECK_INT(scan_uint("0", &value, 65535), 1); CHECK_INT(value, 0); - CHECK_INT(scan_udec("7", &value, 65535), 1); + CHECK_INT(scan_uint("7", &value, 65535), 1); CHECK_INT(value, 7); - CHECK_INT(scan_udec("6969", &value, 65535), 4); + CHECK_INT(scan_uint("6969", &value, 65535), 4); CHECK_INT(value, 6969); - CHECK_INT(scan_udec("65535", &value, 65535), 5); + CHECK_INT(scan_uint("65535", &value, 65535), 5); CHECK_INT(value, 65535); } @@ -29,11 +29,11 @@ static void case_stops_at_non_digit(void) { uint64_t value; test_case("scanning stops at the first character that is not a digit"); - CHECK_INT(scan_udec("6969:rest", &value, 65535), 4); + CHECK_INT(scan_uint("6969:rest", &value, 65535), 4); CHECK_INT(value, 6969); - CHECK_INT(scan_udec("128/8", &value, 65535), 3); + CHECK_INT(scan_uint("128/8", &value, 65535), 3); CHECK_INT(value, 128); - CHECK_INT(scan_udec("42\n", &value, 65535), 2); + CHECK_INT(scan_uint("42\n", &value, 65535), 2); CHECK_INT(value, 42); } @@ -41,9 +41,9 @@ static void case_rejects_whitespace(void) { uint64_t value = 0xdead; test_case("leading whitespace is not skipped"); - CHECK_INT(scan_udec(" 1", &value, 65535), 0); - CHECK_INT(scan_udec("\t1", &value, 65535), 0); - CHECK_INT(scan_udec("\n1", &value, 65535), 0); + CHECK_INT(scan_uint(" 1", &value, 65535), 0); + CHECK_INT(scan_uint("\t1", &value, 65535), 0); + CHECK_INT(scan_uint("\n1", &value, 65535), 0); CHECK_INT(value, 0xdead); } @@ -51,9 +51,9 @@ static void case_rejects_sign(void) { uint64_t value = 0xdead; test_case("a sign is not accepted"); - CHECK_INT(scan_udec("+1", &value, 65535), 0); - CHECK_INT(scan_udec("-1", &value, 65535), 0); - CHECK_INT(scan_udec("-0", &value, 65535), 0); + CHECK_INT(scan_uint("+1", &value, 65535), 0); + CHECK_INT(scan_uint("-1", &value, 65535), 0); + CHECK_INT(scan_uint("-0", &value, 65535), 0); CHECK_INT(value, 0xdead); } @@ -61,9 +61,9 @@ static void case_rejects_empty_and_junk(void) { uint64_t value = 0xdead; test_case("a string with no leading digit yields nothing"); - CHECK_INT(scan_udec("", &value, 65535), 0); - CHECK_INT(scan_udec("x", &value, 65535), 0); - CHECK_INT(scan_udec(":80", &value, 65535), 0); + CHECK_INT(scan_uint("", &value, 65535), 0); + CHECK_INT(scan_uint("x", &value, 65535), 0); + CHECK_INT(scan_uint(":80", &value, 65535), 0); CHECK_INT(value, 0xdead); } @@ -71,9 +71,9 @@ static void case_no_alternative_bases(void) { uint64_t value; test_case("hexadecimal and octal notations are read as plain decimal"); - CHECK_INT(scan_udec("0x10", &value, 65535), 1); + CHECK_INT(scan_uint("0x10", &value, 65535), 1); CHECK_INT(value, 0); - CHECK_INT(scan_udec("010", &value, 65535), 3); + CHECK_INT(scan_uint("010", &value, 65535), 3); CHECK_INT(value, 10); } @@ -81,13 +81,13 @@ static void case_refuses_out_of_range(void) { uint64_t value = 0xdead; test_case("a value above max is refused, not truncated"); - CHECK_INT(scan_udec("65536", &value, 65535), 0); - CHECK_INT(scan_udec("99999", &value, 65535), 0); - CHECK_INT(scan_udec("129", &value, 128), 0); + CHECK_INT(scan_uint("65536", &value, 65535), 0); + CHECK_INT(scan_uint("99999", &value, 65535), 0); + CHECK_INT(scan_uint("129", &value, 128), 0); CHECK_INT(value, 0xdead); test_case("the boundary itself is accepted"); - CHECK_INT(scan_udec("128", &value, 128), 3); + CHECK_INT(scan_uint("128", &value, 128), 3); CHECK_INT(value, 128); } @@ -95,21 +95,21 @@ static void case_sixty_four_bit_range(void) { uint64_t value; test_case("the full unsigned 64 bit range is available"); - CHECK_INT(scan_udec("18446744073709551615", &value, UINT64_MAX), 20); + CHECK_INT(scan_uint("18446744073709551615", &value, UINT64_MAX), 20); CHECK(value == UINT64_MAX); test_case("a number too large for 64 bits is refused"); - CHECK_INT(scan_udec("18446744073709551616", &value, UINT64_MAX), 0); - CHECK_INT(scan_udec("99999999999999999999999999", &value, UINT64_MAX), 0); + CHECK_INT(scan_uint("18446744073709551616", &value, UINT64_MAX), 0); + CHECK_INT(scan_uint("99999999999999999999999999", &value, UINT64_MAX), 0); } static void case_leading_zeros_allowed(void) { uint64_t value; test_case("leading zeros are accepted outside bencode"); - CHECK_INT(scan_udec("007", &value, 65535), 3); + CHECK_INT(scan_uint("007", &value, 65535), 3); CHECK_INT(value, 7); - CHECK_INT(scan_udec("0000", &value, 65535), 4); + CHECK_INT(scan_uint("0000", &value, 65535), 4); CHECK_INT(value, 0); } @@ -117,9 +117,9 @@ static void case_bencode_rejects_leading_zeros(void) { uint64_t value = 0xdead; test_case("bencode refuses a leading zero on anything but zero"); - CHECK_INT(scan_udec_bencode("007", &value, 65535), 0); - CHECK_INT(scan_udec_bencode("0000", &value, 65535), 0); - CHECK_INT(scan_udec_bencode("01", &value, 65535), 0); + CHECK_INT(scan_uint_bencode("007", &value, 65535), 0); + CHECK_INT(scan_uint_bencode("0000", &value, 65535), 0); + CHECK_INT(scan_uint_bencode("01", &value, 65535), 0); CHECK_INT(value, 0xdead); } @@ -127,11 +127,11 @@ static void case_bencode_accepts_canonical(void) { uint64_t value; test_case("bencode accepts a canonically written number"); - CHECK_INT(scan_udec_bencode("0", &value, 65535), 1); + CHECK_INT(scan_uint_bencode("0", &value, 65535), 1); CHECK_INT(value, 0); - CHECK_INT(scan_udec_bencode("0:", &value, 65535), 1); + CHECK_INT(scan_uint_bencode("0:", &value, 65535), 1); CHECK_INT(value, 0); - CHECK_INT(scan_udec_bencode("160:", &value, 65535), 3); + CHECK_INT(scan_uint_bencode("160:", &value, 65535), 3); CHECK_INT(value, 160); } @@ -139,14 +139,64 @@ static void case_bencode_inherits_the_rest(void) { uint64_t value = 0xdead; test_case("bencode is no laxer than the plain scanner"); - CHECK_INT(scan_udec_bencode(" 1", &value, 65535), 0); - CHECK_INT(scan_udec_bencode("-1", &value, 65535), 0); - CHECK_INT(scan_udec_bencode("+1", &value, 65535), 0); - CHECK_INT(scan_udec_bencode("65536", &value, 65535), 0); + CHECK_INT(scan_uint_bencode(" 1", &value, 65535), 0); + CHECK_INT(scan_uint_bencode("-1", &value, 65535), 0); + CHECK_INT(scan_uint_bencode("+1", &value, 65535), 0); + CHECK_INT(scan_uint_bencode("65536", &value, 65535), 0); CHECK_INT(value, 0xdead); } +/* ------------------------------------------- fixed width signed integers */ + +/* A value lifted out of a query string arrives as a pointer and a length + with nothing terminating it, and the whole of it has to be the number. */ + +static void case_int_whole_field(void) { + int64_t value = 0; + + test_case("the whole field is read, sign and all"); + CHECK_INT(scan_int("123", 3, &value), 0); + CHECK_INT(value, 123); + CHECK_INT(scan_int("-45", 3, &value), 0); + CHECK_INT(value, -45); + + test_case("and only as far as the length given"); + CHECK_INT(scan_int("12345", 3, &value), 0); + CHECK_INT(value, 123); +} + +static void case_int_rejects_the_rest(void) { + int64_t value = 4711; + + test_case("anything that is not all digits is refused"); + CHECK_INT(scan_int("12a", 3, &value), -1); + CHECK_INT(scan_int("", 0, &value), -1); + CHECK_INT(scan_int("-", 1, &value), -1); + CHECK_INT(scan_int(" 12", 3, &value), -1); + CHECK_INT(scan_int("+12", 3, &value), -1); + + test_case("and a refused field leaves the number alone"); + CHECK_INT(value, 4711); +} + +static void case_int_overflow(void) { + int64_t value = 0; + + test_case("a field too long to fit is refused rather than wrapped"); + CHECK_INT(scan_int("9223372036854775807", 19, &value), 0); + CHECK_INT(value, INT64_MAX); + CHECK_INT(scan_int("9223372036854775808", 19, &value), -1); + CHECK_INT(scan_int("99999999999999999999", 20, &value), -1); + + test_case("including the negative side"); + CHECK_INT(scan_int("-9223372036854775807", 20, &value), 0); + CHECK_INT(value, -INT64_MAX); +} + void test_scan(void) { + case_int_whole_field(); + case_int_rejects_the_rest(); + case_int_overflow(); case_plain_numbers(); case_stops_at_non_digit(); case_rejects_whitespace(); diff --git a/tests/t_urlencoded.c b/tests/t_urlencoded.c index 1239e6b..6e0ee31 100644 --- a/tests/t_urlencoded.c +++ b/tests/t_urlencoded.c @@ -228,33 +228,6 @@ static void case_find_keywords(void) { CHECK_INT(urlencoded_match_keyword(keywords, &p, SCAN_SEARCHPATH_PARAM), -3); } -static void case_fixed_int(void) { - char positive[] = "123"; - char negative[] = "-45"; - char trailing[] = "12a"; - int value; - - test_case("scan_fixed_int returns the count of unparsed bytes"); - CHECK_INT(scan_fixed_int(positive, 3, &value), 0); - CHECK_INT(value, 123); - - CHECK_INT(scan_fixed_int(negative, 3, &value), 0); - CHECK_INT(value, -45); - - CHECK_INT(scan_fixed_int(trailing, 3, &value), 1); - CHECK_INT(value, 12); -} - -static void case_fixed_int_bounded(void) { - char digits[] = "12345"; - int value; - - test_case("scan_fixed_int honours the length it is given"); - CHECK_INT(scan_fixed_int(digits, 3, &value), 0); - CHECK_INT(value, 123); -} - - /* The encoding direction. Which bytes survive unescaped is fixed by what existing parsers of the urlencoded full scrape expect, so these cases pin the set rather than describe a preference. */ @@ -361,8 +334,6 @@ void test_urlencoded(void) { case_raw_high_byte(); case_skipvalue(); case_find_keywords(); - case_fixed_int(); - case_fixed_int_bounded(); case_fmt_passes_safe_bytes(); case_fmt_escapes_the_rest(); case_fmt_hex_is_lower_case(); -- cgit v1.3