[PATCH] url: use same credentials on redirect
authorDaniel Stenberg <daniel@haxx.se>
Sat, 12 Oct 2024 21:54:39 +0000 (23:54 +0200)
committerSamuel Henrique <samueloph@debian.org>
Sun, 9 Mar 2025 10:45:45 +0000 (10:45 +0000)
Previously it could lose the username and only use the password.

Added test 998 and 999 to verify.

Reported-by: Tobias Bora
Fixes #15262
Closes #15282
Backported by: Matheus Polkorny <mpolkorny@gmail.com>.

Changes:

- Refresh patch context

- Small change in the Makefile to add a new test

Gbp-Pq: Name url-use-same-credentials-on-redirect.patch

lib/transfer.c
lib/url.c
lib/urldata.h
tests/data/Makefile.inc
tests/data/test998 [new file with mode: 0644]
tests/data/test999 [new file with mode: 0644]

index 6da96a0994281e335e17771ed956a8f81be24e72..9a3c7cd2ebf8b121c32cbda2b3ff37e0d1b02e7f 100644 (file)
@@ -1419,6 +1419,9 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
       return CURLE_OUT_OF_MEMORY;
   }
 
+  if(data->set.str[STRING_USERNAME] ||
+      data->set.str[STRING_PASSWORD])
+    data->state.creds_from = CREDS_OPTION;
   if(!result)
     result = Curl_setstropt(&data->state.aptr.user,
                             data->set.str[STRING_USERNAME]);
index 6900f11c760b5c20a04f097555e80896c993bb5a..e6155dc0c042abbf52667bd104cc6c70124e765b 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -1880,10 +1880,10 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data,
     return result;
 
   /*
-   * User name and password set with their own options override the
-   * credentials possibly set in the URL.
+   * username and password set with their own options override the credentials
+   * possibly set in the URL, but netrc does not.
    */
-  if(!data->state.aptr.passwd) {
+  if(!data->state.aptr.passwd || (data->state.creds_from != CREDS_OPTION)) {
     uc = curl_url_get(uh, CURLUPART_PASSWORD, &data->state.up.password, 0);
     if(!uc) {
       char *decoded;
@@ -1896,12 +1896,13 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data,
       result = Curl_setstropt(&data->state.aptr.passwd, decoded);
       if(result)
         return result;
+      data->state.creds_from = CREDS_URL;
     }
     else if(uc != CURLUE_NO_PASSWORD)
       return Curl_uc_to_curlcode(uc);
   }
 
-  if(!data->set.str[STRING_USERNAME]) {
+  if(!data->state.aptr.user || (data->state.creds_from != CREDS_OPTION)) {
     /* we don't use the URL API's URL decoder option here since it rejects
        control codes and we want to allow them for some schemes in the user
        and password fields */
@@ -1915,13 +1916,10 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data,
         return result;
       conn->user = decoded;
       result = Curl_setstropt(&data->state.aptr.user, decoded);
+      data->state.creds_from = CREDS_URL;
     }
     else if(uc != CURLUE_NO_USER)
       return Curl_uc_to_curlcode(uc);
-    else if(data->state.aptr.passwd) {
-      /* no user was set but a password, set a blank user */
-      result = Curl_setstropt(&data->state.aptr.user, "");
-    }
     if(result)
       return result;
   }
@@ -2733,7 +2731,8 @@ static CURLcode override_login(struct Curl_easy *data,
     int ret;
     bool url_provided = FALSE;
 
-    if(data->state.aptr.user) {
+    if(data->state.aptr.user &&
+        (data->state.creds_from != CREDS_NETRC)) {
       /* there was a user name in the URL. Use the URL decoded version */
       userp = &data->state.aptr.user;
       url_provided = TRUE;
@@ -2779,6 +2778,7 @@ static CURLcode override_login(struct Curl_easy *data,
       result = Curl_setstropt(&data->state.aptr.user, *userp);
       if(result)
         return result;
+      data->state.creds_from = CREDS_NETRC;
     }
   }
   if(data->state.aptr.user) {
@@ -2796,6 +2796,7 @@ static CURLcode override_login(struct Curl_easy *data,
     CURLcode result = Curl_setstropt(&data->state.aptr.passwd, *passwdp);
     if(result)
       return result;
+    data->state.creds_from = CREDS_NETRC;
   }
   if(data->state.aptr.passwd) {
     uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD,
index 68d42f804d534b9ff90392cd4e2b56d52b93b718..a222f870d9739a9cb00cd521e6c7ef47a22b102a 100644 (file)
@@ -1270,6 +1270,11 @@ struct urlpieces {
   char *query;
 };
 
+#define CREDS_NONE   0
+#define CREDS_URL    1 /* from URL */
+#define CREDS_OPTION 2 /* set with a CURLOPT_ */
+#define CREDS_NETRC  3 /* found in netrc */
+
 struct UrlState {
   /* Points to the connection cache */
   struct conncache *conn_cache;
@@ -1405,6 +1410,9 @@ struct UrlState {
     char *proxypasswd;
   } aptr;
 
+  unsigned int creds_from:2; /* where is the server credentials originating
+                                from, see the CREDS_* defines above */
+
 #ifdef CURLDEBUG
   BIT(conncache_lock);
 #endif
index 379e6e0f221a81adf8da107d05cac3155e7062fe..d69262e34c7577468e3587d93d4de9807f6d8132 100644 (file)
@@ -128,6 +128,8 @@ test972 test973 test974 test975 test976 test977 \
 \
 test980 test981 test982 test983 test984 test985 test986 \
 \
+test998 test999 \
+\
 test1000 test1001 test1002 test1003 test1004 test1005 test1006 test1007 \
 test1008 test1009 test1010 test1011 test1012 test1013 test1014 test1015 \
 test1016 test1017 test1018 test1019 test1020 test1021 test1022 test1023 \
diff --git a/tests/data/test998 b/tests/data/test998
new file mode 100644 (file)
index 0000000..c3a8f51
--- /dev/null
@@ -0,0 +1,92 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+--location-trusted
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+HTTP/1.1 301 redirect
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 0
+Connection: close
+Content-Type: text/html
+Location: http://somewhere.else.example/a/path/%TESTNUMBER0002
+
+</data>
+<data2>
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 6
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data2>
+
+<datacheck>
+HTTP/1.1 301 redirect
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 0
+Connection: close
+Content-Type: text/html
+Location: http://somewhere.else.example/a/path/%TESTNUMBER0002
+
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 6
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</datacheck>
+
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+proxy
+</features>
+<server>
+http
+</server>
+<name>
+HTTP with auth in URL redirected to another host
+</name>
+<command>
+-x %HOSTIP:%HTTPPORT http://alberto:einstein@somwhere.example/%TESTNUMBER --location-trusted
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+QUIT
+</strip>
+<protocol>
+GET http://somwhere.example/998 HTTP/1.1\r
+Host: somwhere.example\r
+Authorization: Basic YWxiZXJ0bzplaW5zdGVpbg==\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+Proxy-Connection: Keep-Alive\r
+\r
+GET http://somewhere.else.example/a/path/9980002 HTTP/1.1\r
+Host: somewhere.else.example\r
+Authorization: Basic YWxiZXJ0bzplaW5zdGVpbg==\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+Proxy-Connection: Keep-Alive\r
+\r
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test999 b/tests/data/test999
new file mode 100644 (file)
index 0000000..990a8d0
--- /dev/null
@@ -0,0 +1,81 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+--location-trusted
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 6
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+
+<datacheck>
+HTTP/1.1 301 redirect
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 0
+Connection: close
+Content-Type: text/html
+Location: http://somewhere.else.example/a/path/%TESTNUMBER0002
+
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Content-Length: 6
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</datacheck>
+
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+proxy
+</features>
+<server>
+http
+</server>
+<name>
+HTTP with auth in first URL but not second
+</name>
+<command>
+-x %HOSTIP:%HTTPPORT http://alberto:einstein@somwhere.example/%TESTNUMBER http://somewhere.else.example/%TESTNUMBER
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+QUIT
+</strip>
+<protocol>
+GET http://somwhere.example/%TESTNUMBER HTTP/1.1\r
+Host: somwhere.example\r
+Authorization: Basic YWxiZXJ0bzplaW5zdGVpbg==\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+Proxy-Connection: Keep-Alive\r
+\r
+GET http://somewhere.else.example/%TESTNUMBER HTTP/1.1\r
+Host: somewhere.else.example\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+Proxy-Connection: Keep-Alive\r
+\r
+</protocol>
+</verify>
+</testcase>