From 909e9b0a1a7c5581e9ce84439d9d68cece871679 Mon Sep 17 00:00:00 2001 From: Thomas Nguyen Date: Thu, 23 Jan 2025 14:28:25 +1300 Subject: [PATCH] fix(CURLRequest): multiple header sections after redirects (#9426) * fix: strip out multiple header sections for redirect * test: add test for stripping out multiple redirect header sections * docs: changelog for fixing returning multiple header sections after redirects * Added a non-standard http redirect code test * apply cs-fix for test * Moved redirect header loop to a separate function * Updated changelog to be clearer --- system/HTTP/CURLRequest.php | 30 +++++++++ tests/system/HTTP/CURLRequestTest.php | 68 +++++++++++++++++++++ user_guide_src/source/changelogs/v4.6.1.rst | 2 + 3 files changed, 100 insertions(+) diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index e9e2d89d9481..742da9f18d08 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -385,6 +385,10 @@ public function send(string $method, string $url) // Set the string we want to break our response from $breakString = "\r\n\r\n"; + if (isset($this->config['allow_redirects']) && $this->config['allow_redirects'] !== false) { + $output = $this->handleRedirectHeaders($output, $breakString); + } + while (str_starts_with($output, 'HTTP/1.1 100 Continue')) { $output = substr($output, strpos($output, $breakString) + 4); } @@ -713,4 +717,30 @@ protected function sendRequest(array $curlOptions = []): string return $output; } + + private function handleRedirectHeaders(string $output, string $breakString): string + { + // Strip out multiple redirect header sections + while (preg_match('/^HTTP\/\d(?:\.\d)? 3\d\d/', $output)) { + $breakStringPos = strpos($output, $breakString); + $redirectHeaderSection = substr($output, 0, $breakStringPos); + $redirectHeaders = explode("\n", $redirectHeaderSection); + $locationHeaderFound = false; + + foreach ($redirectHeaders as $header) { + if (str_starts_with(strtolower($header), 'location:')) { + $locationHeaderFound = true; + break; + } + } + + if ($locationHeaderFound) { + $output = substr($output, $breakStringPos + 4); + } else { + break; + } + } + + return $output; + } } diff --git a/tests/system/HTTP/CURLRequestTest.php b/tests/system/HTTP/CURLRequestTest.php index 138b7eaa7cb2..94c2b7dbbb24 100644 --- a/tests/system/HTTP/CURLRequestTest.php +++ b/tests/system/HTTP/CURLRequestTest.php @@ -1313,4 +1313,72 @@ public function testHTTPversionAsString(): void $this->assertArrayHasKey(CURLOPT_HTTP_VERSION, $options); $this->assertSame(CURL_HTTP_VERSION_2_0, $options[CURLOPT_HTTP_VERSION]); } + + public function testRemoveMultipleRedirectHeaderSections(): void + { + $testBody = 'Hello world'; + + $output = "HTTP/1.1 301 Moved Permanently +content-type: text/html; charset=utf-8 +content-length: 211 +location: http://example.com +date: Mon, 20 Jan 2025 11:46:34 GMT +server: nginx/1.21.6 +vary: Origin\r\n\r\nHTTP/1.1 302 Found +content-type: text/html; charset=utf-8 +content-length: 211 +location: http://example.com +date: Mon, 20 Jan 2025 11:46:34 GMT +server: nginx/1.21.6 +vary: Origin\r\n\r\nHTTP/1.1 399 Custom Redirect +content-type: text/html; charset=utf-8 +content-length: 211 +location: http://example.com +date: Mon, 20 Jan 2025 11:46:34 GMT +server: nginx/1.21.6 +vary: Origin\r\n\r\nHTTP/2 308 +content-type: text/html; charset=utf-8 +content-length: 211 +location: http://example.com +date: Mon, 20 Jan 2025 11:46:34 GMT +server: nginx/1.21.6 +vary: Origin\r\n\r\nHTTP/1.1 200 OK +content-type: text/html; charset=utf-8 +content-length: 211 +date: Mon, 20 Jan 2025 11:46:34 GMT +server: nginx/1.21.6 +vary: Origin\r\n\r\n" . $testBody; + + $this->request->setOutput($output); + + $response = $this->request->request('GET', 'http://example.com', [ + 'allow_redirects' => true, + ]); + + $this->assertSame(200, $response->getStatusCode()); + + $this->assertSame($testBody, $response->getBody()); + } + + public function testNotRemoveMultipleRedirectHeaderSectionsWithoutLocationHeader(): void + { + $testBody = 'Hello world'; + + $output = "HTTP/1.1 301 Moved Permanently +content-type: text/html; charset=utf-8 +content-length: 211 +date: Mon, 20 Jan 2025 11:46:34 GMT +server: nginx/1.21.6 +vary: Origin\r\n\r\n" . $testBody; + + $this->request->setOutput($output); + + $response = $this->request->request('GET', 'http://example.com', [ + 'allow_redirects' => true, + ]); + + $this->assertSame(301, $response->getStatusCode()); + + $this->assertSame($testBody, $response->getBody()); + } } diff --git a/user_guide_src/source/changelogs/v4.6.1.rst b/user_guide_src/source/changelogs/v4.6.1.rst index ca3d601c61c0..c230ee6d32b9 100644 --- a/user_guide_src/source/changelogs/v4.6.1.rst +++ b/user_guide_src/source/changelogs/v4.6.1.rst @@ -30,6 +30,8 @@ Deprecations Bugs Fixed ********** +- **CURLRequest:** Fixed an issue where multiple header sections appeared in the CURL response body during multiple redirects from the target server. + See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed.