urlapi: allow more path characters "raw" when asked to URL encode

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
This commit is contained in:
Daniel Stenberg 2025-07-24 18:36:28 +02:00
parent 1055144063
commit 768c0f0779
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
3 changed files with 24 additions and 5 deletions

View 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.

View 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 */

View 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/",