Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(CURLRequest): multiple header sections after redirects #9426

30 changes: 30 additions & 0 deletions system/HTTP/CURLRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}
}
68 changes: 68 additions & 0 deletions tests/system/HTTP/CURLRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
2 changes: 2 additions & 0 deletions user_guide_src/source/changelogs/v4.6.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_
for a complete list of bugs fixed.
Loading