Repository: codeigniter4/CodeIgniter4 (PHP)

Issue:
Bug: [CURLRequest] not clear Response headers before next request
### PHP Version

8.1

### CodeIgniter4 Version

4.3.3

### CodeIgniter4 Installation Method

Manual (zip or tar.gz)

### Which operating systems have you tested for this bug?

Linux

### Which server did you use?

fpm-fcgi

### Database

_No response_

### What happened?

When I send more than one requests by same instance of CURLRequest class, some headers of responses in front will exist in the later responses. For example:
At the first time,I requested 'http://domain.com/URI/A' successfully, then I can get some headers such as 'expires' or 'etag' by method 'getHeaderLine'.
At the second time, I requested 'http://domain.com/URI/B' by the same instance, it failed and the server doesn't response the header 'etag'. But when I call the 'hasHeader' method, I call still get it.

### Steps to Reproduce

Send more than one requests by same instance of CURLRequest class, and the server responses different header names.

### Expected Output

Headers of responses in front should be cleared before the later request.

### Anything else?

My way to solve this problem is just setting two properties of response instance (a property of CURLRequest instance) to empty array before parsing output of curl:
$break = strpos($output, $breakString);
$this->response->headers   = [];
$this->response->headerMap = [];
...
I‘m a green hand,maybe there are some better ways?


Discussion:
This is configurable - please read the user guide: https://www.codeigniter.com/user_guide/libraries/curlrequest.html#config-for-curlrequest
> This is configurable - please read the user guide: https://www.codeigniter.com/user_guide/libraries/curlrequest.html#config-for-curlrequest

I had read about this config, it's set to decide whether to share "request options" in different request. When it's set to true, the program only clear "headers of CURLRequest instance",but the old "headers of response" were still there.
> This is configurable - please read the user guide: https://www.codeigniter.com/user_guide/libraries/curlrequest.html#config-for-curlrequest

Excuse me for my broken English!
I mean: I want to "get the correct headers from server in each request", not "send the correct headers to server in each request".
AS you replied,the latter works correctly, but the former seems not.
Oh, no problem, the fault is on my side. 
We will have to look into it.
> Oh, no problem, the fault is on my side. We will have to look into it.

Thanks! I'm a Chinese, and I used CodeIgniter since 2.X, I mean ,I like it very much!
I even revised it to support swoole. I wish one day it would support swoole officially,beacuse I like swoole very much too.
Anyway, thank you very much!
This is a bug.
I don't see any reason to share the Response object between requests.



Patch:
diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php
index f8d9a7318380..1f467a57521f 100644
--- a/system/HTTP/CURLRequest.php
+++ b/system/HTTP/CURLRequest.php
@@ -28,6 +28,13 @@ class CURLRequest extends OutgoingRequest
      */
     protected $response;
 
+    /**
+     * The original response object associated with this request
+     *
+     * @var ResponseInterface|null
+     */
+    protected $responseOrig;
+
     /**
      * The URI associated with this request
      *
@@ -105,7 +112,7 @@ public function __construct(App $config, URI $uri, ?ResponseInterface $response
 
         parent::__construct('GET', $uri);
 
-        $this->response       = $response;
+        $this->responseOrig   = $response ?? new Response(config('App'));
         $this->baseURI        = $uri->useRawQueryString();
         $this->defaultOptions = $options;
 
@@ -125,6 +132,8 @@ public function __construct(App $config, URI $uri, ?ResponseInterface $response
      */
     public function request($method, string $url, array $options = []): ResponseInterface
     {
+        $this->response = clone $this->responseOrig;
+
         $this->parseOptions($options);
 
         $url = $this->prepareURL($url);
diff --git a/system/Test/Mock/MockCURLRequest.php b/system/Test/Mock/MockCURLRequest.php
index 635db366234c..9ee38247ee83 100644
--- a/system/Test/Mock/MockCURLRequest.php
+++ b/system/Test/Mock/MockCURLRequest.php
@@ -34,6 +34,8 @@ public function setOutput($output)
 
     protected function sendRequest(array $curlOptions = []): string
     {
+        $this->response = clone $this->responseOrig;
+
         // Save so we can access later.
         $this->curl_options = $curlOptions;
 
diff --git a/user_guide_src/source/changelogs/v4.3.4.rst b/user_guide_src/source/changelogs/v4.3.4.rst
index b705b2df827f..1e3c1b77284c 100644
--- a/user_guide_src/source/changelogs/v4.3.4.rst
+++ b/user_guide_src/source/changelogs/v4.3.4.rst
@@ -27,6 +27,8 @@ Deprecations
 Bugs Fixed
 **********
 
+- **CURLRequest:** Fixed a bug where the response class was shared between requests.
+
 See the repo's
 `CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_
 for a complete list of bugs fixed.


Test patch:
diff --git a/tests/system/HTTP/CURLRequestDoNotShareOptionsTest.php b/tests/system/HTTP/CURLRequestDoNotShareOptionsTest.php
index ad64e66af506..7dcc47f40082 100644
--- a/tests/system/HTTP/CURLRequestDoNotShareOptionsTest.php
+++ b/tests/system/HTTP/CURLRequestDoNotShareOptionsTest.php
@@ -782,6 +782,62 @@ public function testSendContinuedWithManyHeaders()
         $this->assertSame(200, $response->getStatusCode());
     }
 
+    /**
+     * See: https://github.com/codeigniter4/CodeIgniter4/issues/7394
+     */
+    public function testResponseHeadersWithMultipleRequests()
+    {
+        $request = $this->getRequest([
+            'base_uri' => 'http://www.foo.com/api/v1/',
+        ]);
+
+        $output = "HTTP/2.0 200 OK
+Server: ddos-guard
+Expires: Thu, 19 Nov 1981 08:52:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Type: application/xml; charset=utf-8
+Transfer-Encoding: chunked\x0d\x0a\x0d\x0a<title>Hello1</title>";
+        $request->setOutput($output);
+
+        $response = $request->get('answer1');
+
+        $this->assertSame('<title>Hello1</title>', $response->getBody());
+
+        $responseHeaderKeys = [
+            'Cache-Control',
+            'Content-Type',
+            'Server',
+            'Expires',
+            'Pragma',
+            'Transfer-Encoding',
+        ];
+        $this->assertSame($responseHeaderKeys, array_keys($response->headers()));
+
+        $this->assertSame(200, $response->getStatusCode());
+
+        $output = "HTTP/2.0 200 OK
+Expires: Thu, 19 Nov 1982 08:52:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Content-Type: application/xml; charset=utf-8
+Transfer-Encoding: chunked\x0d\x0a\x0d\x0a<title>Hello2</title>";
+        $request->setOutput($output);
+
+        $response = $request->get('answer2');
+
+        $this->assertSame('<title>Hello2</title>', $response->getBody());
+
+        $responseHeaderKeys = [
+            'Cache-Control',
+            'Content-Type',
+            'Expires',
+            'Transfer-Encoding',
+        ];
+        $this->assertSame($responseHeaderKeys, array_keys($response->headers()));
+
+        $this->assertSame(200, $response->getStatusCode());
+    }
+
     public function testSplitResponse()
     {
         $request = $this->getRequest([
diff --git a/tests/system/HTTP/CURLRequestTest.php b/tests/system/HTTP/CURLRequestTest.php
index 74e6f38e9325..92c43cc58461 100644
--- a/tests/system/HTTP/CURLRequestTest.php
+++ b/tests/system/HTTP/CURLRequestTest.php
@@ -765,6 +765,62 @@ public function testSendContinuedWithManyHeaders()
         $this->assertSame(200, $response->getStatusCode());
     }
 
+    /**
+     * See: https://github.com/codeigniter4/CodeIgniter4/issues/7394
+     */
+    public function testResponseHeadersWithMultipleRequests()
+    {
+        $request = $this->getRequest([
+            'base_uri' => 'http://www.foo.com/api/v1/',
+        ]);
+
+        $output = "HTTP/2.0 200 OK
+Server: ddos-guard
+Expires: Thu, 19 Nov 1981 08:52:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Pragma: no-cache
+Content-Type: application/xml; charset=utf-8
+Transfer-Encoding: chunked\x0d\x0a\x0d\x0a<title>Hello1</title>";
+        $request->setOutput($output);
+
+        $response = $request->get('answer1');
+
+        $this->assertSame('<title>Hello1</title>', $response->getBody());
+
+        $responseHeaderKeys = [
+            'Cache-Control',
+            'Content-Type',
+            'Server',
+            'Expires',
+            'Pragma',
+            'Transfer-Encoding',
+        ];
+        $this->assertSame($responseHeaderKeys, array_keys($response->headers()));
+
+        $this->assertSame(200, $response->getStatusCode());
+
+        $output = "HTTP/2.0 200 OK
+Expires: Thu, 19 Nov 1982 08:52:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Content-Type: application/xml; charset=utf-8
+Transfer-Encoding: chunked\x0d\x0a\x0d\x0a<title>Hello2</title>";
+        $request->setOutput($output);
+
+        $response = $request->get('answer2');
+
+        $this->assertSame('<title>Hello2</title>', $response->getBody());
+
+        $responseHeaderKeys = [
+            'Cache-Control',
+            'Content-Type',
+            'Expires',
+            'Transfer-Encoding',
+        ];
+        $this->assertSame($responseHeaderKeys, array_keys($response->headers()));
+
+        $this->assertSame(200, $response->getStatusCode());
+    }
+
     public function testSplitResponse()
     {
         $request = $this->getRequest([