From: Daniel Stenberg Date: Thu, 16 Apr 2026 14:26:20 +0200 Subject: [PATCH] http: clear credentials better on redirect Verify with test 2506: netrc with redirect using proxy Updated test 998 which was wrong. Reported-by: Muhamad Arga Reksapati Closes #21345 Backported-by: Samuel Henrique * lib/http.c: upstream rewrites the relevant block in Curl_http_follow() entirely, replacing the manual port/scheme comparison with a Curl_url_same_origin() call against a parsed copy of data->state.url. Neither Curl_url_same_origin nor the required Curl_bufref_ptr(&data->state.url) accessor exist in 8.14.1 (state.url is a plain char *). Instead, keep the existing port/scheme check in place and add the new "no STRING_USERNAME" case as a short-circuit ahead of it: when no explicit username was set, force clear=TRUE unconditionally; otherwise fall back to the historical port/scheme test. Use Curl_safefree (8.14.1) rather than upstream's curlx_safefree. * tests/data/test998: drop the now-stale Authorization header line in the second request of the verify block. Upstream uses the %b64[alberto:einstein]b64% placeholder while 8.14.1 still has it pre-encoded as YWxiZXJ0bzplaW5zdGVpbg==; either way the line needs to go away because the fix now clears those credentials on the cross-host redirect. * tests/data/test2506: regression test from upstream, with crlf="headers" -> crlf="yes" so the 8.14.1 test runner correctly applies CRLF to header lines on both the server-side data and the expected protocol block. * tests/libtest/lib2506.c: rewritten to use the 8.14.1 libtest harness (test.h / CURLcode test(char *URL) / easy_init / test_setopt with goto test_cleanup) instead of upstream's newer first.h-based one. Test logic is identical. --- lib/http.c | 77 ++++++++++++++++++---------------- tests/data/Makefile.am | 2 +- tests/data/test2506 | 64 ++++++++++++++++++++++++++++ tests/data/test998 | 1 - tests/libtest/Makefile.inc | 5 ++- tests/libtest/{lib1558.c => lib2506.c} | 47 ++++++++++----------- 6 files changed, 134 insertions(+), 62 deletions(-) create mode 100644 tests/data/test2506 copy tests/libtest/{lib1558.c => lib2506.c} (64%) diff --git a/lib/http.c b/lib/http.c index 68657e5..cc5df34 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1266,47 +1266,54 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, if(uc) return Curl_uc_to_curlcode(uc); - /* Clear auth if this redirects to a different port number or protocol, - unless permitted */ - if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) { - char *portnum; - int port; + /* Clear auth on redirect when: + - this redirects to a different port or protocol (unless permitted), or + - no explicit username was set (the credentials came from netrc or + similar, and must not be carried across a redirect at all). */ + if(type != FOLLOW_FAKE) { bool clear = FALSE; - if(data->set.use_port && data->state.allow_port) - /* a custom port is used */ - port = (int)data->set.use_port; - else { - uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum, - CURLU_DEFAULT_PORT); - if(uc) { - free(follow_url); - return Curl_uc_to_curlcode(uc); - } - port = atoi(portnum); - free(portnum); - } - if(port != data->info.conn_remote_port) { - infof(data, "Clear auth, redirects to port from %u to %u", - data->info.conn_remote_port, port); + if(!data->set.str[STRING_USERNAME]) clear = TRUE; - } - else { - char *scheme; - const struct Curl_handler *p; - uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0); - if(uc) { - free(follow_url); - return Curl_uc_to_curlcode(uc); + else if(!data->set.allow_auth_to_other_hosts) { + char *portnum; + int port; + + if(data->set.use_port && data->state.allow_port) + /* a custom port is used */ + port = (int)data->set.use_port; + else { + uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum, + CURLU_DEFAULT_PORT); + if(uc) { + free(follow_url); + return Curl_uc_to_curlcode(uc); + } + port = atoi(portnum); + free(portnum); } - - p = Curl_get_scheme_handler(scheme); - if(p && (p->protocol != data->info.conn_protocol)) { - infof(data, "Clear auth, redirects scheme from %s to %s", - data->info.conn_scheme, scheme); + if(port != data->info.conn_remote_port) { + infof(data, "Clear auth, redirects to port from %u to %u", + data->info.conn_remote_port, port); clear = TRUE; } - free(scheme); + else { + char *scheme; + const struct Curl_handler *p; + uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0); + if(uc) { + free(follow_url); + return Curl_uc_to_curlcode(uc); + } + + p = Curl_get_scheme_handler(scheme); + if(p && (p->protocol != data->info.conn_protocol)) { + infof(data, "Clear auth, redirects scheme from %s to %s", + data->info.conn_scheme, scheme); + clear = TRUE; + } + free(scheme); + } } if(clear) { CURLcode result = Curl_reset_userpwd(data); diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 70d45c6..7e4163d 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -265,7 +265,7 @@ test2308 test2309 \ \ test2400 test2401 test2402 test2403 test2404 test2405 test2406 \ \ -test2500 test2501 test2502 test2503 test2504 \ +test2500 test2501 test2502 test2503 test2504 test2506 \ \ test2600 test2601 test2602 test2603 test2604 \ \ diff --git a/tests/data/test2506 b/tests/data/test2506 new file mode 100644 index 0000000..305e3d0 --- /dev/null +++ b/tests/data/test2506 @@ -0,0 +1,64 @@ + + + + +HTTP +cookies + + + + + +HTTP/1.1 301 redirect +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 3 +Location: http://numbertwo.example/%TESTNUMBER0002 + +ok + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 4 + +yes + + + + + +http + + +proxy + + +lib%TESTNUMBER + + +netrc with redirect using proxy + + +machine site.example login batman password robin + + +http://%HOSTIP:%HTTPPORT http://site.example/ %LOGDIR/netrc2506 + + + + + +GET http://site.example/ HTTP/1.1 +Host: site.example +Authorization: Basic %b64[batman:robin]b64% +Accept: */* +Proxy-Connection: Keep-Alive + +GET http://numbertwo.example/25060002 HTTP/1.1 +Host: numbertwo.example +Accept: */* +Proxy-Connection: Keep-Alive + + + + diff --git a/tests/data/test998 b/tests/data/test998 index c3a8f51..091c235 100644 --- a/tests/data/test998 +++ b/tests/data/test998 @@ -82,7 +82,6 @@ Proxy-Connection: Keep-Alive GET http://somewhere.else.example/a/path/9980002 HTTP/1.1 Host: somewhere.else.example -Authorization: Basic YWxiZXJ0bzplaW5zdGVpbg== User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index bdbd8b5..284be0c 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -77,7 +77,7 @@ LIBTESTPROGS = libauthretry libntlmconnect libprereq \ lib1970 lib1971 lib1972 lib1973 lib1974 lib1975 lib1977 lib1978 \ lib2301 lib2302 lib2304 lib2306 lib2308 lib2309 \ lib2402 lib2404 lib2405 \ - lib2502 lib2504 \ + lib2502 lib2504 lib2506 \ lib2700 \ lib3010 lib3025 lib3026 lib3027 \ lib3100 lib3101 lib3102 lib3103 lib3104 lib3105 lib3207 lib3208 @@ -727,6 +727,9 @@ lib2502_LDADD = $(TESTUTIL_LIBS) lib2504_SOURCES = lib2504.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(WARNLESS) lib2504_LDADD = $(TESTUTIL_LIBS) +lib2506_SOURCES = lib2506.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(WARNLESS) +lib2506_LDADD = $(TESTUTIL_LIBS) + lib2700_SOURCES = lib2700.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE) lib2700_LDADD = $(TESTUTIL_LIBS) diff --git a/tests/libtest/lib1558.c b/tests/libtest/lib2506.c similarity index 64% copy from tests/libtest/lib1558.c copy to tests/libtest/lib2506.c index 9bb18ca..86d5a50 100644 --- a/tests/libtest/lib1558.c +++ b/tests/libtest/lib2506.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Linus Nielsen Feltzing * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -23,45 +23,44 @@ ***************************************************************************/ #include "test.h" +#include "testtrace.h" #include "testutil.h" #include "warnless.h" #include "memdebug.h" +static size_t sink2506(char *ptr, size_t size, size_t nmemb, void *ud) +{ + (void)ptr; + (void)ud; + return size * nmemb; +} + CURLcode test(char *URL) { - CURLcode res = CURLE_OK; CURL *curl = NULL; - long protocol = 0; + CURLcode res = CURLE_OK; global_init(CURL_GLOBAL_ALL); - easy_init(curl); - easy_setopt(curl, CURLOPT_URL, URL); - res = curl_easy_perform(curl); - if(res) { - curl_mfprintf(stderr, "curl_easy_perform() returned %d (%s)\n", - res, curl_easy_strerror(res)); - goto test_cleanup; - } - - res = curl_easy_getinfo(curl, CURLINFO_PROTOCOL, &protocol); - if(res) { - curl_mfprintf(stderr, "curl_easy_getinfo() returned %d (%s)\n", - res, curl_easy_strerror(res)); - goto test_cleanup; - } + easy_init(curl); - curl_mprintf("Protocol: %lx\n", protocol); + test_setopt(curl, CURLOPT_WRITEFUNCTION, sink2506); + test_setopt(curl, CURLOPT_PROXY, URL); + test_setopt(curl, CURLOPT_URL, libtest_arg2); + test_setopt(curl, CURLOPT_NETRC, (long)CURL_NETRC_OPTIONAL); + test_setopt(curl, CURLOPT_NETRC_FILE, libtest_arg3); + test_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + test_setopt(curl, CURLOPT_VERBOSE, 1L); - curl_easy_cleanup(curl); - curl_global_cleanup(); + /* CURLOPT_UNRESTRICTED_AUTH should not make a difference because the + credentials come from netrc */ + test_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, 1L); - return CURLE_OK; + res = curl_easy_perform(curl); test_cleanup: - curl_easy_cleanup(curl); curl_global_cleanup(); - return res; /* return the final return code */ + return res; }