urlapi: avoid dedotdotify() if possible

The dedotdotify() function that removes ./ and ../ sequences from paths
juggles memory and is slow. Now needs_dedotdot() is called first to
determine if the removal process is necessary and otherwise avoids doing
it. Avoids unnecessary memory operations.

Adjusted unit test 1395 accordingly because now a lot of input strings
return NULL for "no change necessary".

Suggested-by: Max Dymond

Closes #22557
This commit is contained in:
Daniel Stenberg 2026-08-12 12:11:37 +02:00
parent da04aa96ab
commit eae88a7473
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
2 changed files with 56 additions and 33 deletions

View file

@ -750,6 +750,29 @@ static bool is_dot(const char **str, size_t *clen)
#define ISSLASH(x) ((x) == '/')
/* prescan the string to see if it needs work */
static bool needs_dedotdot(const char *p, size_t pn)
{
/* a single byte path cannot be cleaned up */
if(pn < 2)
return FALSE;
while(pn) {
if(is_dot(&p, &pn)) {
/* "./" or dot before end of string */
if(!pn || ISSLASH(*p))
return TRUE;
/* "../" or ".." before end of string */
else if(is_dot(&p, &pn) && (!pn || ISSLASH(*p)))
return TRUE;
}
else {
p++;
pn--;
}
}
return FALSE;
}
/*
* dedotdotify()
*
@ -761,7 +784,8 @@ static bool is_dot(const char **str, size_t *clen)
*
* RETURNS
*
* Zero for success and 'out' set to an allocated dedotdotified string.
* Zero for success and 'out' set to an allocated string (or NULL if there's
* nothing to do).
*
* @unittest 1395
*/
@ -776,8 +800,7 @@ UNITTEST int dedotdotify(const char *input, size_t clen, char **outp)
size_t dlen = clen;
*outp = NULL;
/* a single byte path cannot be cleaned up */
if(clen < 2)
if(!needs_dedotdot(input, clen))
return 0;
curlx_dyn_init(&out, clen + 1);

View file

@ -39,33 +39,33 @@ static CURLcode test_unit1395(const char *arg)
{ "/%2f%2e%2e%2f/../a", "/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%2", "/%2f%2e%2e%2" },
{ "/%2f%2e%2e%", "/%2f%2e%2e%" },
{ "/%2f%2e%2e%2f/", NULL },
{ "/%2f%2e%2e%2f", NULL },
{ "/%2f%2e%2e%2", NULL },
{ "/%2f%2e%2e%", NULL },
{ "/%2f%2e%2e", "/%2f%2e%2e" },
{ "/%2f%2e%2", "/%2f%2e%2" },
{ "/%2f%2e%", "/%2f%2e%" },
{ "/%2f%2e%2", NULL },
{ "/%2f%2e%", NULL },
{ "/%2f%2e", "/%2f%2e" },
{ "/%2f%2", "/%2f%2" },
{ "/%2f%", "/%2f%" },
{ "/%2f", "/%2f" },
{ "/%2", "/%2" },
{ "/%2f%2", NULL },
{ "/%2f%", NULL },
{ "/%2f", NULL },
{ "/%2", NULL },
{ "%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/", NULL },
{ "%2f%2e%2e%2f", NULL },
{ "%2f%2e%2e%2", NULL },
{ "%2f%2e%2e%", NULL },
{ "%2f%2e%2e", "%2f%2e%2e" },
{ "%2f%2e%2", "%2f%2e%2" },
{ "%2f%2e%", "%2f%2e%" },
{ "%2f%2e%2", NULL },
{ "%2f%2e%", NULL },
{ "%2f%2e", "%2f%2e" },
{ "%2f%2", "%2f%2" },
{ "%2f%", "%2f%" },
{ "%2f", "%2f" },
{ "%2", "%2" },
{ "%2f%2", NULL },
{ "%2f%", NULL },
{ "%2f", NULL },
{ "%2", NULL },
{ "%", NULL },
{ "2", NULL },
{ "e", NULL },
@ -108,12 +108,12 @@ static CURLcode test_unit1395(const char *arg)
{ "/1/./%2e.", "/" },
{ "/1/./../2", "/2" },
{ "/hello/1/./../2", "/hello/2" },
{ "test/this", "test/this" },
{ "test/this", NULL },
{ "test/this/../now", "test/now" },
{ "/1../moo../foo", "/1../moo../foo" },
{ "/../../moo", "/moo" },
{ "/../../moo?", "/moo?" },
{ "/123?", "/123?" },
{ "/123?", NULL },
{ "/", NULL },
{ "", NULL },
{ "/.../", "/.../" },
@ -131,16 +131,16 @@ static CURLcode test_unit1395(const char *arg)
{ "/a/%2E%2e/b", "/b" },
{ "/a/%2e./b", "/b" },
{ "/a/.%2e/b", "/b" },
{ "/%2f..%2f", "/%2f..%2f" },
{ "/%2f..%2f", NULL },
{ "/a/b/.", "/a/b/" },
{ "/a/b/..", "/a/" },
{ "well-known", "well-known" },
{ ".well-known", ".well-known" },
{ "..well-known", "..well-known" },
{ "...well-known", "...well-known" },
{ "....well-known", "....well-known" },
{ "%2ewell-known", "%2ewell-known" },
{ "%2Ewell-known", "%2Ewell-known" },
{ "well-known", NULL },
{ ".well-known", NULL },
{ "..well-known", NULL },
{ "...well-known", NULL },
{ "....well-known", NULL },
{ "%2ewell-known", NULL },
{ "%2Ewell-known", NULL },
{ "../.well-known", ".well-known" },
};