diff options
| author | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-24 05:49:51 +0200 |
|---|---|---|
| committer | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-24 05:49:51 +0200 |
| commit | 2cb9b191b66a69aba15ebdefe8cd4bdd962e5b2b (patch) | |
| tree | 418a4d41745ca201c67869383dda9bf6728ed822 /tests | |
| parent | 6e40b628c3d9076e843868bd75f14c44f17bdabb (diff) | |
Add tests for the url escaper
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/t_scan_urlencoded_query.c | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/tests/t_scan_urlencoded_query.c b/tests/t_scan_urlencoded_query.c index 4c1ef23..6fa34e0 100644 --- a/tests/t_scan_urlencoded_query.c +++ b/tests/t_scan_urlencoded_query.c | |||
| @@ -254,6 +254,106 @@ static void case_fixed_int_bounded(void) { | |||
| 254 | CHECK_INT(value, 123); | 254 | CHECK_INT(value, 123); |
| 255 | } | 255 | } |
| 256 | 256 | ||
| 257 | |||
| 258 | /* The encoding direction. Which bytes survive unescaped is fixed by what | ||
| 259 | existing parsers of the urlencoded full scrape expect, so these cases pin | ||
| 260 | the set rather than describe a preference. */ | ||
| 261 | |||
| 262 | static void case_fmt_passes_safe_bytes(void) { | ||
| 263 | const uint8_t src[] = "aZ09$-./=_~"; | ||
| 264 | char dest[64]; | ||
| 265 | size_t n; | ||
| 266 | |||
| 267 | test_case("alphanumerics and the safe punctuation pass through"); | ||
| 268 | n = fmt_urlencoded(dest, src, 11); | ||
| 269 | dest[n] = 0; | ||
| 270 | CHECK_INT(n, 11); | ||
| 271 | CHECK_STR(dest, "aZ09$-./=_~"); | ||
| 272 | } | ||
| 273 | |||
| 274 | static void case_fmt_escapes_the_rest(void) { | ||
| 275 | const uint8_t src[] = {' ', '%', '&', '+', ':', '?'}; | ||
| 276 | char dest[64]; | ||
| 277 | size_t n; | ||
| 278 | |||
| 279 | test_case("everything else becomes a percent escape"); | ||
| 280 | n = fmt_urlencoded(dest, src, sizeof(src)); | ||
| 281 | dest[n] = 0; | ||
| 282 | CHECK_INT(n, 18); | ||
| 283 | CHECK_STR(dest, "%20%25%26%2b%3a%3f"); | ||
| 284 | } | ||
| 285 | |||
| 286 | static void case_fmt_hex_is_lower_case(void) { | ||
| 287 | const uint8_t src[] = {0x00, 0xab, 0xff}; | ||
| 288 | char dest[64]; | ||
| 289 | size_t n; | ||
| 290 | |||
| 291 | test_case("escapes use lower case hex digits"); | ||
| 292 | n = fmt_urlencoded(dest, src, sizeof(src)); | ||
| 293 | dest[n] = 0; | ||
| 294 | CHECK_INT(n, 9); | ||
| 295 | CHECK_STR(dest, "%00%ab%ff"); | ||
| 296 | } | ||
| 297 | |||
| 298 | static void case_fmt_length_only(void) { | ||
| 299 | const uint8_t src[] = {'a', 0xff, 'b'}; | ||
| 300 | |||
| 301 | test_case("a null destination reports the length without writing"); | ||
| 302 | CHECK_INT(fmt_urlencoded(NULL, src, sizeof(src)), 5); | ||
| 303 | CHECK_INT(fmt_urlencoded(NULL, src, 0), 0); | ||
| 304 | } | ||
| 305 | |||
| 306 | static void case_fmt_zero_length(void) { | ||
| 307 | char dest[4]; | ||
| 308 | |||
| 309 | test_case("a zero length input writes nothing"); | ||
| 310 | memset(dest, 0x5a, sizeof(dest)); | ||
| 311 | CHECK_INT(fmt_urlencoded(dest, (const uint8_t *)"abc", 0), 0); | ||
| 312 | CHECK_MEM(dest, "\x5a\x5a\x5a\x5a", 4); | ||
| 313 | } | ||
| 314 | |||
| 315 | static void case_fmt_worst_case_fits(void) { | ||
| 316 | uint8_t src[20]; | ||
| 317 | char dest[61]; | ||
| 318 | size_t i, n; | ||
| 319 | |||
| 320 | test_case("a hash of entirely unsafe bytes needs three times its length"); | ||
| 321 | for (i = 0; i < sizeof(src); ++i) | ||
| 322 | src[i] = 0xff; | ||
| 323 | n = fmt_urlencoded(dest, src, sizeof(src)); | ||
| 324 | dest[n] = 0; | ||
| 325 | CHECK_INT(n, 60); | ||
| 326 | for (i = 0; i < 20; ++i) | ||
| 327 | if (memcmp(dest + 3 * i, "%ff", 3)) { | ||
| 328 | test_failf(__FILE__, __LINE__, "escape %zu is wrong", i); | ||
| 329 | return; | ||
| 330 | } | ||
| 331 | ++test_checks; | ||
| 332 | } | ||
| 333 | |||
| 334 | static void case_fmt_decoder_disagrees_on_dollar(void) { | ||
| 335 | /* Documenting a known asymmetry rather than a wish. Of the bytes the | ||
| 336 | encoder passes through unescaped, '$' is the only one the decoder then | ||
| 337 | refuses, and it refuses the whole string rather than stopping short. | ||
| 338 | Output of the urlencoded full scrape therefore does not round trip | ||
| 339 | through scan_urlencoded_query when a hash contains 0x24. Harmless today | ||
| 340 | because only third party tooling reads that output. */ | ||
| 341 | const uint8_t src[] = {'a', '$', 'b'}; | ||
| 342 | char dest[16]; | ||
| 343 | char scanned[16]; | ||
| 344 | char *p = dest; | ||
| 345 | size_t n; | ||
| 346 | ssize_t got; | ||
| 347 | |||
| 348 | test_case("the encoder passes '$' but the decoder rejects it"); | ||
| 349 | n = fmt_urlencoded(dest, src, sizeof(src)); | ||
| 350 | dest[n] = 0; | ||
| 351 | CHECK_STR(dest, "a$b"); | ||
| 352 | |||
| 353 | got = scan_urlencoded_query(&p, scanned, SCAN_SEARCHPATH_VALUE); | ||
| 354 | CHECK_INT(got, -1); | ||
| 355 | } | ||
| 356 | |||
| 257 | void test_scan_urlencoded_query(void) { | 357 | void test_scan_urlencoded_query(void) { |
| 258 | case_path(); | 358 | case_path(); |
| 259 | case_param_and_value(); | 359 | case_param_and_value(); |
| @@ -271,4 +371,11 @@ void test_scan_urlencoded_query(void) { | |||
| 271 | case_find_keywords(); | 371 | case_find_keywords(); |
| 272 | case_fixed_int(); | 372 | case_fixed_int(); |
| 273 | case_fixed_int_bounded(); | 373 | case_fixed_int_bounded(); |
| 374 | case_fmt_passes_safe_bytes(); | ||
| 375 | case_fmt_escapes_the_rest(); | ||
| 376 | case_fmt_hex_is_lower_case(); | ||
| 377 | case_fmt_length_only(); | ||
| 378 | case_fmt_zero_length(); | ||
| 379 | case_fmt_worst_case_fits(); | ||
| 380 | case_fmt_decoder_disagrees_on_dollar(); | ||
| 274 | } | 381 | } |
