From b4024bf808bd558026fdc6096e8457f199ace306 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
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
---
 lib/http.c                 | 84 ++++++++++++--------------------------
 tests/data/Makefile.am     |  2 +-
 tests/data/test2506        | 64 +++++++++++++++++++++++++++++
 tests/data/test998         |  1 -
 tests/libtest/Makefile.inc |  2 +-
 tests/libtest/lib2506.c    | 71 ++++++++++++++++++++++++++++++++
 6 files changed, 162 insertions(+), 62 deletions(-)
 create mode 100644 tests/data/test2506
 create mode 100644 tests/libtest/lib2506.c

Index: curl-8.14.1/lib/http.c
===================================================================
--- curl-8.14.1.orig/lib/http.c
+++ curl-8.14.1/lib/http.c
@@ -1262,71 +1262,41 @@ CURLcode Curl_http_follow(struct Curl_ea
       return CURLE_OUT_OF_MEMORY;
   }
   else {
-    uc = curl_url_get(data->state.uh, CURLUPART_URL, &follow_url, 0);
-    if(uc)
+    bool same_origin;
+    CURLcode result;
+    CURLU *u = curl_url();
+    if(!u)
+      return CURLE_OUT_OF_MEMORY;
+    uc = curl_url_set(u, CURLUPART_URL,
+                      data->state.url,
+                      CURLU_URLENCODE | CURLU_ALLOW_SPACE);
+    if(!uc)
+      uc = curl_url_get(data->state.uh, CURLUPART_URL, &follow_url, 0);
+    if(uc) {
+      curl_url_cleanup(u);
       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;
-      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);
-        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);
-        }
+    same_origin = Curl_url_same_origin(u, data->state.uh);
+    curl_url_cleanup(u);
 
-        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);
-        if(result) {
-          free(follow_url);
-          return result;
-        }
-        Curl_safefree(data->state.aptr.user);
-        Curl_safefree(data->state.aptr.passwd);
+    if((!same_origin && !data->set.allow_auth_to_other_hosts) ||
+       !data->set.str[STRING_USERNAME]) {
+      result = Curl_reset_userpwd(data);
+      if(result) {
+        free(follow_url);
+        return result;
       }
+      Curl_safefree(data->state.aptr.user);
+      Curl_safefree(data->state.aptr.passwd);
     }
-  }
-  DEBUGASSERT(follow_url);
-  {
-    CURLcode result = Curl_reset_proxypwd(data);
+    result = Curl_reset_proxypwd(data);
     if(result) {
       free(follow_url);
       return result;
     }
   }
+  DEBUGASSERT(follow_url);
 
   if(type == FOLLOW_FAKE) {
     /* we are only figuring out the new URL if we would have followed locations
Index: curl-8.14.1/lib/urlapi-int.h
===================================================================
--- curl-8.14.1.orig/lib/urlapi-int.h
+++ curl-8.14.1/lib/urlapi-int.h
@@ -37,4 +37,6 @@ UNITTEST CURLUcode Curl_parse_port(struc
                                    bool has_scheme);
 #endif
 
+bool Curl_url_same_origin(CURLU *base, CURLU *href);
+
 #endif /* HEADER_CURL_URLAPI_INT_H */
Index: curl-8.14.1/lib/urlapi.c
===================================================================
--- curl-8.14.1.orig/lib/urlapi.c
+++ curl-8.14.1/lib/urlapi.c
@@ -1939,3 +1939,36 @@ nomem:
   }
   return CURLUE_OK;
 }
+
+bool Curl_url_same_origin(CURLU *base, CURLU *href)
+{
+  const struct Curl_handler *h = NULL;
+
+  /* base must be an absolute URL */
+  if(!base->scheme || !base->host)
+    return FALSE;
+  if(href->scheme && !curl_strequal(base->scheme, href->scheme))
+    return FALSE;
+  if(href->host) {
+    if(!curl_strequal(base->host, href->host))
+      return FALSE;
+    if(!curl_strequal(base->port, href->port)) {
+      /* This may still match if only one has an explicit port
+       * and it is the default for the scheme. */
+      if(base->port && href->port)
+        return FALSE;
+
+      h = Curl_get_scheme_handler(base->scheme);
+      if(!h) /* Cannot match default port for unknown handler */
+        return FALSE;
+
+      /* The port which is set must be the default one */
+      if((base->port && (base->portnum != h->defport)) ||
+         (href->port && (href->portnum != h->defport)))
+        return FALSE;
+    }
+  }
+  else if(href->port) /* no host in href, then there must be no port */
+    return FALSE;
+  return TRUE;
+}
Index: curl-8.14.1/tests/data/Makefile.am
===================================================================
--- curl-8.14.1.orig/tests/data/Makefile.am
+++ curl-8.14.1/tests/data/Makefile.am
@@ -264,7 +264,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 \
 \
Index: curl-8.14.1/tests/data/test2506
===================================================================
--- /dev/null
+++ curl-8.14.1/tests/data/test2506
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+<info>
+<keywords>
+HTTP
+cookies
+</keywords>
+</info>
+
+<reply>
+<data crlf="headers" nocheck="yes">
+HTTP/1.1 301 redirect
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 3
+Location: http://numbertwo.example/%TESTNUMBER0002
+
+ok
+</data>
+<data2 crlf="headers" nocheck="yes">
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 4
+
+yes
+</data2>
+</reply>
+
+<client>
+<server>
+http
+</server>
+<features>
+proxy
+</features>
+<tool>
+lib%TESTNUMBER
+</tool>
+<name>
+netrc with redirect using proxy
+</name>
+<file name="%LOGDIR/netrc2506">
+machine site.example login batman password robin
+</file>
+<command>
+http://%HOSTIP:%HTTPPORT http://site.example/ %LOGDIR/netrc2506
+</command>
+</client>
+
+<verify>
+<protocol crlf="headers">
+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
+
+</protocol>
+</verify>
+</testcase>
Index: curl-8.14.1/tests/data/test998
===================================================================
--- curl-8.14.1.orig/tests/data/test998
+++ curl-8.14.1/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
Index: curl-8.14.1/tests/libtest/lib2506.c
===================================================================
--- /dev/null
+++ curl-8.14.1/tests/libtest/lib2506.c
@@ -0,0 +1,74 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Linus Nielsen Feltzing <linus@haxx.se>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#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)
+{
+  CURL *curl;
+  CURLcode res = CURLE_OUT_OF_MEMORY;
+
+  if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
+    curl_mfprintf(stderr, "curl_global_init() failed\n");
+    return TEST_ERR_MAJOR_BAD;
+  }
+
+  curl = curl_easy_init();
+  if(!curl) {
+    curl_mfprintf(stderr, "curl_easy_init() failed\n");
+    curl_global_cleanup();
+    return TEST_ERR_MAJOR_BAD;
+  }
+
+  test_setopt(curl, CURLOPT_WRITEFUNCTION, sink2506);
+  test_setopt(curl, CURLOPT_PROXY, URL);
+  test_setopt(curl, CURLOPT_URL, libtest_arg2);
+  test_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
+  test_setopt(curl, CURLOPT_NETRC_FILE, libtest_arg3);
+  test_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+  test_setopt(curl, CURLOPT_VERBOSE, 1L);
+
+  /* CURLOPT_UNRESTRICTED_AUTH should not make a difference because the
+     credentials come from netrc */
+  test_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, 1L);
+
+  res = curl_easy_perform(curl);
+
+test_cleanup:
+  curl_easy_cleanup(curl);
+  curl_global_cleanup();
+
+  return res;
+}
Index: curl-8.14.1/tests/libtest/Makefile.inc
===================================================================
--- curl-8.14.1.orig/tests/libtest/Makefile.inc
+++ curl-8.14.1/tests/libtest/Makefile.inc
@@ -77,7 +77,7 @@ LIBTESTPROGS = libauthretry libntlmconne
  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
@@ -724,6 +724,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)
 
