From 3a19987a87f393d9394fe5acc7643f6c263c92db Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Tue, 14 Apr 2026 08:51:44 +0200
Subject: [PATCH] urldata: move cookiehost to struct SingleRequest

To make it scoped for the single request appropriately.

Reported-by: Muhamad Arga Reksapati

Verify with libtest 2504: a custom Host *disabled* on reused handle

Closes #21312
---
 lib/http.c                 | 14 +++---
 lib/request.c              |  3 ++
 lib/request.h              |  3 ++
 lib/url.c                  |  2 +-
 lib/urldata.h              |  3 --
 tests/data/Makefile.am     |  2 +-
 tests/data/test2504        | 52 +++++++++++++++++++++
 tests/libtest/Makefile.inc |  2 +-
 tests/libtest/lib2504.c    | 93 ++++++++++++++++++++++++++++++++++++++
 9 files changed, 162 insertions(+), 12 deletions(-)
 create mode 100644 tests/data/test2504
 create mode 100644 tests/libtest/lib2504.c

Index: curl-8.14.1/lib/http.c
===================================================================
--- curl-8.14.1.orig/lib/http.c
+++ curl-8.14.1/lib/http.c
@@ -1903,6 +1903,9 @@ static CURLcode http_host(struct Curl_ea
     data->state.first_remote_protocol = conn->handler->protocol;
   }
   Curl_safefree(aptr->host);
+#ifndef CURL_DISABLE_COOKIES
+  Curl_safefree(data->req.cookiehost);
+#endif
 
   ptr = Curl_checkheaders(data, STRCONST("Host"));
   if(ptr && (!data->state.this_is_a_follow ||
@@ -1937,8 +1940,7 @@ static CURLcode http_host(struct Curl_ea
         if(colon)
           *colon = 0; /* The host must not include an embedded port number */
       }
-      free(aptr->cookiehost);
-      aptr->cookiehost = cookiehost;
+      data->req.cookiehost = cookiehost;
     }
 #endif
 
@@ -2452,8 +2454,8 @@ static CURLcode http_cookies(struct Curl
     int rc = 1;
 
     if(data->cookies && data->state.cookie_engine) {
-      const char *host = data->state.aptr.cookiehost ?
-        data->state.aptr.cookiehost : conn->host.name;
+      const char *host = data->req.cookiehost ?
+        data->req.cookiehost : conn->host.name;
       const bool secure_context =
         conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) ||
         strcasecompare("localhost", host) ||
@@ -3287,8 +3289,8 @@ static CURLcode http_header(struct Curl_
     if(v) {
       /* If there is a custom-set Host: name, use it here, or else use
        * real peer hostname. */
-      const char *host = data->state.aptr.cookiehost ?
-        data->state.aptr.cookiehost : conn->host.name;
+      const char *host = data->req.cookiehost ?
+        data->req.cookiehost : conn->host.name;
       const bool secure_context =
         conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) ||
         strcasecompare("localhost", host) ||
Index: curl-8.14.1/lib/request.c
===================================================================
--- curl-8.14.1.orig/lib/request.c
+++ curl-8.14.1/lib/request.c
@@ -120,6 +120,9 @@ void Curl_req_hard_reset(struct SingleRe
 
   Curl_safefree(req->newurl);
   Curl_client_reset(data);
+#ifndef CURL_DISABLE_COOKIES
+  Curl_safefree(req->cookiehost);
+#endif
   if(req->sendbuf_init)
     Curl_bufq_reset(&req->sendbuf);
 
Index: curl-8.14.1/lib/request.h
===================================================================
--- curl-8.14.1.orig/lib/request.h
+++ curl-8.14.1/lib/request.h
@@ -100,6 +100,9 @@ struct SingleRequest {
                        wanted */
 
 #ifndef CURL_DISABLE_COOKIES
+  char *cookiehost;
+#endif
+#ifndef CURL_DISABLE_COOKIES
   unsigned char setcookies;
 #endif
   BIT(header);        /* incoming data has HTTP header */
Index: curl-8.14.1/lib/url.c
===================================================================
--- curl-8.14.1.orig/lib/url.c
+++ curl-8.14.1/lib/url.c
@@ -317,7 +317,7 @@ CURLcode Curl_close(struct Curl_easy **d
   Curl_safefree(data->state.aptr.ref);
   Curl_safefree(data->state.aptr.host);
 #ifndef CURL_DISABLE_COOKIES
-  Curl_safefree(data->state.aptr.cookiehost);
+  Curl_safefree(data->req.cookiehost);
 #endif
 #ifndef CURL_DISABLE_RTSP
   Curl_safefree(data->state.aptr.rtsp_transport);
Index: curl-8.14.1/lib/urldata.h
===================================================================
--- curl-8.14.1.orig/lib/urldata.h
+++ curl-8.14.1/lib/urldata.h
@@ -1241,9 +1241,6 @@ struct UrlState {
     char *rangeline;
     char *ref;
     char *host;
-#ifndef CURL_DISABLE_COOKIES
-    char *cookiehost;
-#endif
 #ifndef CURL_DISABLE_RTSP
     char *rtsp_transport;
 #endif
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 \
+test2500 test2501 test2502 test2503 test2504 \
 \
 test2600 test2601 test2602 test2603 test2604 \
 \
Index: curl-8.14.1/tests/data/test2504
===================================================================
--- /dev/null
+++ curl-8.14.1/tests/data/test2504
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+<info>
+<keywords>
+HTTP
+cookies
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data crlf="headers" nocheck="yes">
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: server.example.com
+Content-Length: 47
+Set-Cookie: sid=SECRET123; Path=/
+
+file contents should appear once for each file
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<tool>
+lib%TESTNUMBER
+</tool>
+<name>
+custom Host with cookie, handle reuse, no custom Host:
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<protocol crlf="headers">
+GET / HTTP/1.1
+Host: victim.internal
+Accept: */*
+
+GET / HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Accept: */*
+
+</protocol>
+</verify>
+</testcase>
Index: curl-8.14.1/tests/libtest/lib2504.c
===================================================================
--- /dev/null
+++ curl-8.14.1/tests/libtest/lib2504.c
@@ -0,0 +1,97 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  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 "first.h"
+
+#include "testtrace.h"
+#include "testutil.h"
+#include "warnless.h"
+#include "memdebug.h"
+
+static size_t sink2504(char *ptr, size_t size, size_t nmemb, void *ud)
+{
+  (void)ptr;
+  (void)ud;
+  return size * nmemb;
+}
+
+static void dump_cookies2504(CURL *h, const char *tag)
+{
+  struct curl_slist *cookies = NULL;
+  struct curl_slist *nc;
+  CURLcode rc = curl_easy_getinfo(h, CURLINFO_COOKIELIST, &cookies);
+
+  curl_mprintf("== %s ==\n", tag);
+  if(rc) {
+    curl_mprintf("getinfo error: %d\n", (int)rc);
+    return;
+  }
+  for(nc = cookies; nc; nc = nc->next)
+    puts(nc->data);
+  curl_slist_free_all(cookies);
+}
+
+CURLcode test(char *URL)
+{
+  CURL *curl;
+  CURLcode res = CURLE_OUT_OF_MEMORY;
+  struct curl_slist *hdrs = NULL;
+
+  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;
+  }
+
+  hdrs = curl_slist_append(hdrs, "Host: victim.internal");
+  if(hdrs) {
+    test_setopt(curl, CURLOPT_WRITEFUNCTION, sink2504);
+    test_setopt(curl, CURLOPT_COOKIEFILE, "");
+    test_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
+    test_setopt(curl, CURLOPT_URL, URL);
+
+    res = curl_easy_perform(curl);
+    curl_mprintf("req1=%d\n", (int)res);
+    dump_cookies2504(curl, "after request 1");
+
+    test_setopt(curl, CURLOPT_HTTPHEADER, NULL);
+    test_setopt(curl, CURLOPT_URL, URL);
+
+    res = curl_easy_perform(curl);
+    curl_mprintf("req2=%d\n", (int)res);
+    dump_cookies2504(curl, "after request 2");
+  }
+test_cleanup:
+  curl_slist_free_all(hdrs);
+  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 \
+ lib2502 lib2504 \
  lib2700 \
  lib3010 lib3025 lib3026 lib3027 \
  lib3100 lib3101 lib3102 lib3103 lib3104 lib3105 lib3207 lib3208
@@ -721,6 +721,9 @@ lib2405_LDADD = $(TESTUTIL_LIBS)
 lib2502_SOURCES = lib2502.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(WARNLESS)
 lib2502_LDADD = $(TESTUTIL_LIBS)
 
+lib2504_SOURCES = lib2504.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(WARNLESS)
+lib2504_LDADD = $(TESTUTIL_LIBS)
+
 lib2700_SOURCES = lib2700.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
 lib2700_LDADD = $(TESTUTIL_LIBS)
 
