Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

urlapi: fix relative redirects to fragment-only #13394

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 35 additions & 32 deletions lib/urlapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ static CURLcode concat_url(char *base, const char *relurl, char **newurl)
const char *useurl = relurl;
CURLcode result = CURLE_OK;
CURLUcode uc;
bool skip_slash = FALSE;
*newurl = NULL;

/* protsep points to the start of the host name */
Expand All @@ -283,48 +284,50 @@ static CURLcode concat_url(char *base, const char *relurl, char **newurl)
*pathsep = 0;

/* we have a relative path to append to the last slash if there's one
available, or if the new URL is just a query string (starts with a
'?') we append the new one at the end of the entire currently worked
out URL */
if(useurl[0] != '?') {
available, or the new URL is just a query string (starts with a '?') or
a fragment (starts with '#') we append the new one at the end of the
current URL */
if((useurl[0] != '?') && (useurl[0] != '#')) {
pathsep = strrchr(protsep, '/');
if(pathsep)
*pathsep = 0;
}

/* Check if there's any slash after the host name, and if so, remember
that position instead */
pathsep = strchr(protsep, '/');
if(pathsep)
protsep = pathsep + 1;
else
protsep = NULL;
/* Check if there's any slash after the host name, and if so, remember
that position instead */
pathsep = strchr(protsep, '/');
if(pathsep)
protsep = pathsep + 1;
else
protsep = NULL;

/* now deal with one "./" or any amount of "../" in the newurl
and act accordingly */
/* now deal with one "./" or any amount of "../" in the newurl
and act accordingly */

if((useurl[0] == '.') && (useurl[1] == '/'))
useurl += 2; /* just skip the "./" */
if((useurl[0] == '.') && (useurl[1] == '/'))
useurl += 2; /* just skip the "./" */

while((useurl[0] == '.') &&
(useurl[1] == '.') &&
(useurl[2] == '/')) {
level++;
useurl += 3; /* pass the "../" */
}
while((useurl[0] == '.') &&
(useurl[1] == '.') &&
(useurl[2] == '/')) {
level++;
useurl += 3; /* pass the "../" */
}

if(protsep) {
while(level--) {
/* cut off one more level from the right of the original URL */
pathsep = strrchr(protsep, '/');
if(pathsep)
*pathsep = 0;
else {
*protsep = 0;
break;
if(protsep) {
while(level--) {
/* cut off one more level from the right of the original URL */
pathsep = strrchr(protsep, '/');
if(pathsep)
*pathsep = 0;
else {
*protsep = 0;
break;
}
}
}
}
else
skip_slash = TRUE;
}
else {
/* We got a new absolute path for this server */
Expand Down Expand Up @@ -370,7 +373,7 @@ static CURLcode concat_url(char *base, const char *relurl, char **newurl)
return result;

/* check if we need to append a slash */
if(('/' == useurl[0]) || (protsep && !*protsep) || ('?' == useurl[0]))
if(('/' == useurl[0]) || (protsep && !*protsep) || skip_slash)
;
else {
result = Curl_dyn_addn(&newest, "/", 1);
Expand Down
48 changes: 48 additions & 0 deletions tests/libtest/lib1560.c
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,54 @@ static CURLUcode updateurl(CURLU *u, const char *cmd, unsigned int setflags)
}

static const struct redircase set_url_list[] = {
{"http://example.org/",
"../path/././../../moo",
"http://example.org/moo",
0, 0, CURLUE_OK},
{"http://example.org/",
"//example.org/../path/../../",
"http://example.org/",
0, 0, CURLUE_OK},
{"http://example.org/",
"///example.org/../path/../../",
"http://example.org/",
0, 0, CURLUE_OK},
{"http://example.org/foo/bar",
":23",
"http://example.org/foo/:23",
0, 0, CURLUE_OK},
{"http://example.org/foo/bar",
"\\x",
"http://example.org/foo/\\x",
/* WHATWG disagrees */
0, 0, CURLUE_OK},
{"http://example.org/foo/bar",
"#/",
"http://example.org/foo/bar#/",
0, 0, CURLUE_OK},
{"http://example.org/foo/bar",
"?/",
"http://example.org/foo/bar?/",
0, 0, CURLUE_OK},
{"http://example.org/foo/bar",
"#;?",
"http://example.org/foo/bar#;?",
0, 0, CURLUE_OK},
{"http://example.org/foo/bar",
"#",
"http://example.org/foo/bar",
/* This happens because the parser removes empty fragments */
0, 0, CURLUE_OK},
{"http://example.org/foo/bar",
"?",
"http://example.org/foo/bar",
/* This happens because the parser removes empty queries */
0, 0, CURLUE_OK},
{"http://example.org/foo/bar",
"?#",
"http://example.org/foo/bar",
/* This happens because the parser removes empty queries and fragments */
0, 0, CURLUE_OK},
{"http://example.com/please/../gimme/%TESTNUMBER?foobar#hello",
"http://example.net/there/it/is/../../tes t case=/%TESTNUMBER0002? yes no",
"http://example.net/there/tes%20t%20case=/%TESTNUMBER0002?+yes+no",
Expand Down