urlapi: remove percent encoded dot sequences from the URL path

Treat %2e and %2E to be "dot equivalents" in the function and remove
such sequences as well, according to RFC 3986 section 5.2.4. That is
also what the browsers do.

This DOES NOT consider %2f sequences in the path to be actual slashes,
so there is no removal of dots for those.

This function does not decode nor encode any percent sequences.

Also switched the code to use dynbuf.

Extends test 1395 and 1560 to verify.

Assisted-by: Demi Marie Obenour

Fixes #16869
Closes #16870
This commit is contained in:
Daniel Stenberg 2025-03-29 19:10:40 +01:00
parent b2926e2248
commit c31dd6631f
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
3 changed files with 161 additions and 96 deletions

View file

@ -1218,6 +1218,12 @@ static const struct redircase set_url_list[] = {
{"http://example.org/", "../path/././../././../moo",
"http://example.org/moo",
0, 0, CURLUE_OK},
{"http://example.org/", ".%2e/path/././../%2E/./../moo",
"http://example.org/moo",
0, 0, CURLUE_OK},
{"http://example.org/", ".%2e/path/./%2e/.%2E/%2E/./%2e%2E/moo",
"http://example.org/moo",
0, 0, CURLUE_OK},
{"http://example.org?bar/moo", "?weird",
"http://example.org/?weird", 0, 0, CURLUE_OK},

View file

@ -48,28 +48,72 @@ UNITTEST_START
unsigned int i;
int fails = 0;
const struct dotdot pairs[] = {
{ "%2f%2e%2e%2f/../a", "%2f%2e%2e%2f/a" },
{ "%2f%2e%2e%2f/../", "%2f%2e%2e%2f/" },
{ "%2f%2e%2e%2f/.", "%2f%2e%2e%2f/" },
{ "%2f%2e%2e%2f/", "%2f%2e%2e%2f/" },
{ "%2f%2e%2e%2f", "%2f%2e%2e%2f" },
{ "%2f%2e%2e%2", "%2f%2e%2e%2" },
{ "%2f%2e%2e%", "%2f%2e%2e%" },
{ "%2f%2e%2e", "%2f%2e%2e" },
{ "%2f%2e%2", "%2f%2e%2" },
{ "%2f%2e%", "%2f%2e%" },
{ "%2f%2e", "%2f%2e" },
{ "%2f%2", "%2f%2" },
{ "%2f%", "%2f%" },
{ "%2f", "%2f" },
{ "%2", "%2" },
{ "%", NULL },
{ "2", NULL },
{ "e", NULL },
{ ".", NULL },
{ "./", "" },
{ "..", "" },
{ "../", "" },
{ "../a", "a" },
{ "///moo.", "///moo." },
{ ".///moo.", "//moo." },
{ "./moo..", "moo.." },
{ "./moo../", "moo../" },
{ "./moo../.m", "moo../.m" },
{ "./moo", "moo" },
{ "../moo", "moo" },
{ "../moo?", "moo?" },
{ "../moo?#", "moo?#" },
{ "../moo?#?..", "moo?#?.." },
{ "/../moo/..", "/" },
{ "/a/c/%2e%2E/b", "/a/b" },
{ "/a/%2e/g", "/a/g" },
{ "/a/b/c/./g", "/a/b/c/g" },
{ "/a/c/../b", "/a/b" },
{ "/a/b/c/./../../g", "/a/g" },
{ "/a/b/c/./%2e%2E/../g", "/a/g" },
{ "/a/b/c/./../%2e%2E/g", "/a/g" },
{ "/a/b/c/%2E/%2e%2E/%2e%2E/g", "/a/g" },
{ "mid/content=5/../6", "mid/6" },
{ "/hello/../moo", "/moo" },
{ "/1/../1", "/1" },
{ "/1/./1", "/1/1" },
{ "/1/%2e/1", "/1/1" },
{ "/1/%2E/1", "/1/1" },
{ "/1/..", "/" },
{ "/1/.", "/1/" },
{ "/1/%2e", "/1/" },
{ "/1/%2E", "/1/" },
{ "/1/./..", "/" },
{ "/1/%2e/.%2E", "/" },
{ "/1/./%2e.", "/" },
{ "/1/./../2", "/2" },
{ "/hello/1/./../2", "/hello/2" },
{ "test/this", NULL },
{ "test/this", "test/this" },
{ "test/this/../now", "test/now" },
{ "/1../moo../foo", "/1../moo../foo"},
{ "/../../moo", "/moo"},
{ "/../../moo?", "/moo?"},
{ "/123?", NULL},
{ "/../moo/..?", "/" },
{ "/123?", "/123?" },
{ "/", NULL },
{ "", NULL },
{ "/.../", "/.../" },
{ "./moo", "moo" },
{ "../moo", "moo" },
{ "/.", "/" },
{ "/..", "/" },
{ "/moo/..", "/" },