From eae88a7473475511220767b7f2ed792da82a79d6 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 12 Aug 2026 12:11:37 +0200 Subject: [PATCH] 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 --- lib/urlapi.c | 29 ++++++++++++++++++--- tests/unit/unit1395.c | 60 +++++++++++++++++++++---------------------- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/lib/urlapi.c b/lib/urlapi.c index 1e61fbcae5..1b498e95d0 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -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); diff --git a/tests/unit/unit1395.c b/tests/unit/unit1395.c index a286303977..58945b59a2 100644 --- a/tests/unit/unit1395.c +++ b/tests/unit/unit1395.c @@ -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" }, };