/* This software was written by Dirk Engling It is considered beerware. Prost. Skol. Cheers or whatever. $id$ */ /* System */ #include /* Opentracker */ #include "scan_urlencoded_query.h" #include "test.h" /* The scanner returns the number of bytes written to the destination, or -1 for a parse error and -2 for a hard terminator encountered before any output was produced. It advances the source pointer past the separator it stopped on, except for hard terminators and the '&' ending a parameter, which it leaves for the next call to see. */ static void case_path(void) { char query[] = "/announce?info_hash=A"; char dest[64]; char *p = query; ssize_t n; test_case("path stops at the question mark"); n = scan_urlencoded_query(&p, dest, SCAN_PATH); CHECK_INT(n, 9); dest[n] = 0; CHECK_STR(dest, "/announce"); CHECK_INT(*p, 'i'); } static void case_param_and_value(void) { char query[] = "info_hash=abc&port=80"; char dest[64]; char *p = query; ssize_t n; test_case("parameter stops at '=', value stops at '&'"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); CHECK_INT(n, 9); dest[n] = 0; CHECK_STR(dest, "info_hash"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, 3); dest[n] = 0; CHECK_STR(dest, "abc"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); CHECK_INT(n, 4); dest[n] = 0; CHECK_STR(dest, "port"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, 2); dest[n] = 0; CHECK_STR(dest, "80"); } static void case_percent_decoding(void) { char query[] = "%41%42%43&x=1"; char dest[64]; char *p = query; ssize_t n; test_case("percent escapes decode"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, 3); CHECK_MEM(dest, "ABC", 3); } static void case_binary_info_hash(void) { /* The twenty byte info_hash is what the announce path really decodes */ char query[] = "%00%01%02%ff%fe%7f%80ABCDEFGHIJKLM&x=1"; unsigned char want[] = {0x00, 0x01, 0x02, 0xff, 0xfe, 0x7f, 0x80, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M'}; char dest[64]; char *p = query; ssize_t n; test_case("twenty byte binary value survives decoding"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, 20); CHECK_MEM(dest, want, 20); } static void case_in_place(void) { char query[] = "%41%42&x=1"; char *p = query; ssize_t n; test_case("decoding in place over the source is safe"); n = scan_urlencoded_query(&p, query, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, 2); CHECK_MEM(query, "AB", 2); } static void case_bad_escape(void) { char query[] = "%zz"; char dest[64]; char *p = query; ssize_t n; test_case("a non hexadecimal escape is a parse error"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, -1); } static void case_truncated_escape(void) { char query[] = "%4"; char dest[64]; char *p = query; ssize_t n; test_case("an escape cut short by the terminator is a parse error"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, -1); } static void case_terminator(void) { char empty[] = ""; char space[] = " rest"; char dest[64]; char *p = empty; ssize_t n; test_case("a hard terminator before any output reports end of input"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); CHECK_INT(n, -2); p = space; n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); CHECK_INT(n, -2); } static void case_terminator_after_output(void) { char query[] = "abc"; char dest[64]; char *p = query; ssize_t n; test_case("a hard terminator after output is left for the next call"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, 3); CHECK_INT(*p, 0); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, -2); } /* The character matrix makes '?' terminate a path only, and '=' terminate a path or a parameter only. Both are ordinary characters inside a value, so a value is free to contain a nested query string. */ static void case_separators_are_contextual(void) { char question[] = "a?b&x=1"; char equals[] = "a=b&x=1"; char dest[64]; char *p = question; ssize_t n; test_case("a question mark is literal inside a value"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, 3); dest[n] = 0; CHECK_STR(dest, "a?b"); test_case("an equals sign is literal inside a value"); p = equals; n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, 3); dest[n] = 0; CHECK_STR(dest, "a=b"); } static void case_ampersand_in_path(void) { char query[] = "/path&x"; char dest[64]; char *p = query; ssize_t n; test_case("an ampersand in a path is a parse error"); n = scan_urlencoded_query(&p, dest, SCAN_PATH); CHECK_INT(n, -1); } static void case_raw_high_byte(void) { char query[] = {'a', 'b', (char)0x80, 0}; char dest[64]; char *p = query; ssize_t n; test_case("an undecoded byte above ASCII is a parse error"); n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); CHECK_INT(n, -1); } static void case_skipvalue(void) { char query[] = "abc&next=1"; char bare[] = "abc"; char *p = query; test_case("skipvalue steps over the value and its separator"); scan_urlencoded_skipvalue(&p); CHECK_STR(p, "next=1"); test_case("skipvalue leaves a hard terminator in place"); p = bare; scan_urlencoded_skipvalue(&p); CHECK_INT(*p, 0); } static void case_find_keywords(void) { static const ot_keywords keywords[] = {{"port", 1}, {"left", 2}, {"compact", 3}, {NULL, -3}}; char match[] = "left=0"; char nomatch[] = "bogus=1"; char prefix[] = "por=1"; char *p = match; test_case("a known keyword returns its value"); CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), 2); test_case("an unknown keyword returns -3"); p = nomatch; CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), -3); test_case("a prefix of a keyword does not match it"); p = prefix; CHECK_INT(scan_find_keywords(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); } void test_scan_urlencoded_query(void) { case_path(); case_param_and_value(); case_percent_decoding(); case_binary_info_hash(); case_in_place(); case_bad_escape(); case_truncated_escape(); case_terminator(); case_terminator_after_output(); case_separators_are_contextual(); case_ampersand_in_path(); case_raw_high_byte(); case_skipvalue(); case_find_keywords(); case_fixed_int(); case_fixed_int_bounded(); }