]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
urlapi: allow more path characters "raw" when asked to URL encode 18024/head
authorDaniel Stenberg <daniel@haxx.se>
Thu, 24 Jul 2025 16:36:28 +0000 (18:36 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 25 Jul 2025 16:30:25 +0000 (18:30 +0200)
Setting the path component to contain the letters:

    ! $ & ' ( ) { } [ ] * + , ; = : @

now leaves them un-encoded when CURLU_URLENCODE is used.

Amended test 1560 to verify.

Reported-by: Jeroen Ooms
Fixes #17977

docs/libcurl/curl_url_set.md
lib/urlapi.c
tests/libtest/lib1560.c

index 5758a105b11523c1319e14f2d7072ebecb27480e..2fa31123f15e66fe64df1873aedf5916c7828f88 100644 (file)
@@ -184,8 +184,10 @@ course cannot know if the provided scheme is a valid one or not.
 When set, curl_url_set(3) URL encodes the part on entry, except for
 **scheme**, **port** and **URL**.
 
-When setting the path component with URL encoding enabled, the slash character
-is skipped.
+When setting the path component with URL encoding enabled, the following
+characters are left as-is if present:
+
+    ! $ & ' ( ) { } [ ] * + , ; = : @
 
 The query part gets space-to-plus converted before the URL conversion is
 applied.
index c266fd3ebc2554d110771c245b911f2a92dcc6b6..fd1dc5feab02a624be3a91a39811f23df176dbf0 100644 (file)
@@ -1757,13 +1757,26 @@ static CURLUcode urlset_clear(CURLU *u, CURLUPart what)
   return CURLUE_OK;
 }
 
+static bool allowed_in_path(unsigned char x)
+{
+  switch(x) {
+  case '!': case '$': case '&': case '\'':
+  case '(': case ')': case '{': case '}':
+  case '[': case ']': case '*': case '+':
+  case ',': case ';': case '=': case ':':
+  case '@': case '/':
+    return TRUE;
+  }
+  return FALSE;
+}
+
 CURLUcode curl_url_set(CURLU *u, CURLUPart what,
                        const char *part, unsigned int flags)
 {
   char **storep = NULL;
   bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
   bool plusencode = FALSE;
-  bool urlskipslash = FALSE;
+  bool pathmode = FALSE;
   bool leadingslash = FALSE;
   bool appendquery = FALSE;
   bool equalsencode = FALSE;
@@ -1808,7 +1821,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what,
   case CURLUPART_PORT:
     return set_url_port(u, part);
   case CURLUPART_PATH:
-    urlskipslash = TRUE;
+    pathmode = TRUE;
     leadingslash = TRUE; /* enforce */
     storep = &u->path;
     break;
@@ -1850,7 +1863,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what,
             return CURLUE_OUT_OF_MEMORY;
         }
         else if(ISUNRESERVED(*i) ||
-                ((*i == '/') && urlskipslash) ||
+                (pathmode && allowed_in_path(*i)) ||
                 ((*i == '=') && equalsencode)) {
           if((*i == '=') && equalsencode)
             /* only skip the first equals sign */
index 26a98de286eff3eb8a8a931741c8adfe9d638e94..7a6e0733e60791e8a096a52fddee2004025c0170 100644 (file)
@@ -895,6 +895,10 @@ static const struct setgetcase setget_parts_list[] = {
 
 /* !checksrc! disable SPACEBEFORECOMMA 1 */
 static const struct setcase set_parts_list[] = {
+  {"https://example.com/",
+   "path=one /$!$&'()*+;=:@{}[]%,",
+   "https://example.com/one%20/$!$&'()*+;=:@{}[]%25",
+   0, CURLU_URLENCODE, CURLUE_OK, CURLUE_OK},
   {NULL, /* start fresh! */
    "scheme=https,path=/,url=\"\",", /* incomplete url, redirect to "" */
    "https://example.com/",