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
@@ -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;
}
}
68 changes: 68 additions & 0 deletions tests/system/HTTP/CURLRequestTest.php
Original file line number Diff line number Diff line change
@@ -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
@@ -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.

Unchanged files with check annotations Beta

<?php

Check failure on line 1 in tests/system/Database/Live/SQLite3/AlterTableTest.php

GitHub Actions / PHP Static Analysis

Ignored error pattern #^Parameter \#2 \$forge of class CodeIgniter\\Database\\SQLite3\\Table constructor expects CodeIgniter\\Database\\SQLite3\\Forge, CodeIgniter\\Database\\Forge given\.$# in path /home/runner/work/CodeIgniter4/CodeIgniter4/tests/system/Database/Live/SQLite3/AlterTableTest.php was not matched in reported errors.

Check failure on line 1 in tests/system/Database/Live/SQLite3/AlterTableTest.php

GitHub Actions / PHP Static Analysis

Ignored error pattern #^Parameter \#2 \$forge of class CodeIgniter\\Database\\SQLite3\\Table constructor expects CodeIgniter\\Database\\SQLite3\\Forge, CodeIgniter\\Database\\Forge given\.$# in path /home/runner/work/CodeIgniter4/CodeIgniter4/tests/system/Database/Live/SQLite3/AlterTableTest.php was not matched in reported errors.
declare(strict_types=1);
};
}
public function testNoFormatterJSON(): void

Check warning on line 133 in tests/system/API/ResponseTraitTest.php

GitHub Actions / Others (8.2) / Sanity Tests

Took 1.8656s from 0.5000s limit to run CodeIgniter\\API\\ResponseTraitTest::testNoFormatterJSON

Check warning on line 133 in tests/system/API/ResponseTraitTest.php

GitHub Actions / Others (8.2) / Sanity Tests

Took 1.9900s from 0.5000s limit to run CodeIgniter\\API\\ResponseTraitTest::testNoFormatterJSON
{
$this->formatter = null;
$controller = $this->makeController(
$this->assertSame(200, $this->handler->getHeight());
}
public function testImageCreation(): void

Check warning on line 356 in tests/system/Images/ImageMagickHandlerTest.php

GitHub Actions / Others (8.2) / Sanity Tests

Took 0.8466s from 0.5000s limit to run CodeIgniter\\Images\\ImageMagickHandlerTest::testImageCreation

Check warning on line 356 in tests/system/Images/ImageMagickHandlerTest.php

GitHub Actions / Others (8.2) / Sanity Tests

Took 0.6233s from 0.5000s limit to run CodeIgniter\\Images\\ImageMagickHandlerTest::testImageCreation
{
foreach (['gif', 'jpeg', 'png', 'webp'] as $type) {
if ($type === 'webp' && ! in_array('WEBP', Imagick::queryFormats(), true)) {
$this->assertSame(exif_imagetype($this->root . 'ci-logo.png'), IMAGETYPE_PNG);
}
public function testImageReorientLandscape(): void

Check warning on line 453 in tests/system/Images/ImageMagickHandlerTest.php

GitHub Actions / Others (8.2) / Sanity Tests

Took 0.5669s from 0.5000s limit to run CodeIgniter\\Images\\ImageMagickHandlerTest::testImageReorientLandscape

Check warning on line 453 in tests/system/Images/ImageMagickHandlerTest.php

GitHub Actions / Others (8.2) / Sanity Tests

Took 0.5771s from 0.5000s limit to run CodeIgniter\\Images\\ImageMagickHandlerTest::testImageReorientLandscape
{
for ($i = 0; $i <= 8; $i++) {
$source = $this->origin . 'EXIFsamples/landscape_' . $i . '.jpg';
}
}
public function testImageReorientPortrait(): void

Check warning on line 472 in tests/system/Images/ImageMagickHandlerTest.php

GitHub Actions / Others (8.2) / Sanity Tests

Took 0.5229s from 0.5000s limit to run CodeIgniter\\Images\\ImageMagickHandlerTest::testImageReorientPortrait

Check warning on line 472 in tests/system/Images/ImageMagickHandlerTest.php

GitHub Actions / Others (8.2) / Sanity Tests

Took 0.5539s from 0.5000s limit to run CodeIgniter\\Images\\ImageMagickHandlerTest::testImageReorientPortrait
{
for ($i = 0; $i <= 8; $i++) {
$source = $this->origin . 'EXIFsamples/portrait_' . $i . '.jpg';
$this->assertNull($this->handler->getMetaData(self::$dummy));
}
public function testGetMetaData(): void

Check warning on line 35 in tests/system/Cache/Handlers/AbstractHandlerTestCase.php

GitHub Actions / CacheLive (8.2) / Cache Live Tests

Took 1.8727s from 0.5000s limit to run CodeIgniter\\Cache\\Handlers\\MemcachedHandlerTest::testGetMetaData

Check warning on line 35 in tests/system/Cache/Handlers/AbstractHandlerTestCase.php

GitHub Actions / CacheLive (8.2) / Cache Live Tests

Took 1.8598s from 0.5000s limit to run CodeIgniter\\Cache\\Handlers\\MemcachedHandlerTest::testGetMetaData
{
$time = Time::now()->getTimestamp();
$this->handler->save(self::$key1, 'value');
return $this->getStreamFilterBuffer();
}
public function testCreateDatabase(): void

Check warning on line 72 in tests/system/Commands/CreateDatabaseTest.php

GitHub Actions / DatabaseLive (8.2, SQLite3, 8.0) / tests

Took 2.3245s from 0.5000s limit to run CodeIgniter\\Commands\\CreateDatabaseTest::testCreateDatabase

Check warning on line 72 in tests/system/Commands/CreateDatabaseTest.php

GitHub Actions / DatabaseLive (8.2, Postgre, 8.0) / tests

Took 2.5956s from 0.5000s limit to run CodeIgniter\\Commands\\CreateDatabaseTest::testCreateDatabase

Check warning on line 72 in tests/system/Commands/CreateDatabaseTest.php

GitHub Actions / DatabaseLive (8.2, SQLSRV, 8.0) / tests

Took 2.7343s from 0.5000s limit to run CodeIgniter\\Commands\\CreateDatabaseTest::testCreateDatabase

Check warning on line 72 in tests/system/Commands/CreateDatabaseTest.php

GitHub Actions / DatabaseLive (8.2, MySQLi, 8.0) / tests

Took 2.5692s from 0.5000s limit to run CodeIgniter\\Commands\\CreateDatabaseTest::testCreateDatabase

Check warning on line 72 in tests/system/Commands/CreateDatabaseTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 2.1040s from 0.5000s limit to run CodeIgniter\\Commands\\CreateDatabaseTest::testCreateDatabase

Check warning on line 72 in tests/system/Commands/CreateDatabaseTest.php

GitHub Actions / DatabaseLive (8.2, Postgre, 8.0) / tests

Took 2.6356s from 0.5000s limit to run CodeIgniter\\Commands\\CreateDatabaseTest::testCreateDatabase

Check warning on line 72 in tests/system/Commands/CreateDatabaseTest.php

GitHub Actions / DatabaseLive (8.2, SQLite3, 8.0) / tests

Took 2.3282s from 0.5000s limit to run CodeIgniter\\Commands\\CreateDatabaseTest::testCreateDatabase

Check warning on line 72 in tests/system/Commands/CreateDatabaseTest.php

GitHub Actions / DatabaseLive (8.2, SQLSRV, 8.0) / tests

Took 2.7964s from 0.5000s limit to run CodeIgniter\\Commands\\CreateDatabaseTest::testCreateDatabase

Check warning on line 72 in tests/system/Commands/CreateDatabaseTest.php

GitHub Actions / DatabaseLive (8.2, MySQLi, 8.0) / tests

Took 2.4980s from 0.5000s limit to run CodeIgniter\\Commands\\CreateDatabaseTest::testCreateDatabase

Check warning on line 72 in tests/system/Commands/CreateDatabaseTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 2.0551s from 0.5000s limit to run CodeIgniter\\Commands\\CreateDatabaseTest::testCreateDatabase
{
if ($this->connection instanceof OCI8Connection) {
$this->markTestSkipped('Needs to run on non-OCI8 drivers.');
CLI::init();
}
public function testMigrateAllWithWithTwoNamespaces(): void

Check warning on line 77 in tests/system/Commands/Database/MigrateStatusTest.php

GitHub Actions / DatabaseLive (8.2, SQLite3, 8.0) / tests

Took 0.7183s from 0.5000s limit to run CodeIgniter\\Commands\\Database\\MigrateStatusTest::testMigrateAllWithWithTwoNamespaces

Check warning on line 77 in tests/system/Commands/Database/MigrateStatusTest.php

GitHub Actions / DatabaseLive (8.2, Postgre, 8.0) / tests

Took 0.6078s from 0.5000s limit to run CodeIgniter\\Commands\\Database\\MigrateStatusTest::testMigrateAllWithWithTwoNamespaces

Check warning on line 77 in tests/system/Commands/Database/MigrateStatusTest.php

GitHub Actions / DatabaseLive (8.2, SQLSRV, 8.0) / tests

Took 0.6094s from 0.5000s limit to run CodeIgniter\\Commands\\Database\\MigrateStatusTest::testMigrateAllWithWithTwoNamespaces

Check warning on line 77 in tests/system/Commands/Database/MigrateStatusTest.php

GitHub Actions / DatabaseLive (8.2, MySQLi, 8.0) / tests

Took 0.6050s from 0.5000s limit to run CodeIgniter\\Commands\\Database\\MigrateStatusTest::testMigrateAllWithWithTwoNamespaces

Check warning on line 77 in tests/system/Commands/Database/MigrateStatusTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 1.6658s from 0.5000s limit to run CodeIgniter\\Commands\\Database\\MigrateStatusTest::testMigrateAllWithWithTwoNamespaces

Check warning on line 77 in tests/system/Commands/Database/MigrateStatusTest.php

GitHub Actions / DatabaseLive (8.2, Postgre, 8.0) / tests

Took 0.6106s from 0.5000s limit to run CodeIgniter\\Commands\\Database\\MigrateStatusTest::testMigrateAllWithWithTwoNamespaces

Check warning on line 77 in tests/system/Commands/Database/MigrateStatusTest.php

GitHub Actions / DatabaseLive (8.2, SQLite3, 8.0) / tests

Took 0.7474s from 0.5000s limit to run CodeIgniter\\Commands\\Database\\MigrateStatusTest::testMigrateAllWithWithTwoNamespaces

Check warning on line 77 in tests/system/Commands/Database/MigrateStatusTest.php

GitHub Actions / DatabaseLive (8.2, SQLSRV, 8.0) / tests

Took 0.7435s from 0.5000s limit to run CodeIgniter\\Commands\\Database\\MigrateStatusTest::testMigrateAllWithWithTwoNamespaces

Check warning on line 77 in tests/system/Commands/Database/MigrateStatusTest.php

GitHub Actions / DatabaseLive (8.2, MySQLi, 8.0) / tests

Took 0.6400s from 0.5000s limit to run CodeIgniter\\Commands\\Database\\MigrateStatusTest::testMigrateAllWithWithTwoNamespaces

Check warning on line 77 in tests/system/Commands/Database/MigrateStatusTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 1.6551s from 0.5000s limit to run CodeIgniter\\Commands\\Database\\MigrateStatusTest::testMigrateAllWithWithTwoNamespaces
{
command('migrate --all');
$this->resetStreamFilterBuffer();
unset($_ENV['encryption.key'], $_SERVER['encryption.key']);
}
public function testGenerateKeyShowsEncodedKey(): void

Check warning on line 77 in tests/system/Commands/GenerateKeyTest.php

GitHub Actions / SeparateProcess (8.2) / tests

Took 2.1349s from 0.5000s limit to run CodeIgniter\\Commands\\GenerateKeyTest::testGenerateKeyShowsEncodedKey

Check warning on line 77 in tests/system/Commands/GenerateKeyTest.php

GitHub Actions / SeparateProcess (8.2) / tests

Took 2.1332s from 0.5000s limit to run CodeIgniter\\Commands\\GenerateKeyTest::testGenerateKeyShowsEncodedKey
{
command('key:generate --show');
$this->assertStringContainsString('hex2bin:', $this->getBuffer());
$this->forge->dropTable('', true);
}
public function testForeignKey(): void

Check warning on line 494 in tests/system/Database/Live/ForgeTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 2.8588s from 0.5000s limit to run CodeIgniter\\Database\\Live\\ForgeTest::testForeignKey

Check warning on line 494 in tests/system/Database/Live/ForgeTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 2.8337s from 0.5000s limit to run CodeIgniter\\Database\\Live\\ForgeTest::testForeignKey
{
$this->forge->dropTable('forge_test_invoices', true);
$this->forge->dropTable('forge_test_users', true);
$this->forge->dropTable('forge_test_1', true);
}
public function testSetKeyNames(): void

Check warning on line 1236 in tests/system/Database/Live/ForgeTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 1.0305s from 0.5000s limit to run CodeIgniter\\Database\\Live\\ForgeTest::testSetKeyNames

Check warning on line 1236 in tests/system/Database/Live/ForgeTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 1.0615s from 0.5000s limit to run CodeIgniter\\Database\\Live\\ForgeTest::testSetKeyNames
{
$this->forge->dropTable('forge_test_1', true);
$this->forge->dropTable('forge_test_four', true);
}
public function testDropKey(): void

Check warning on line 1583 in tests/system/Database/Live/ForgeTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 0.8033s from 0.5000s limit to run CodeIgniter\\Database\\Live\\ForgeTest::testDropKey

Check warning on line 1583 in tests/system/Database/Live/ForgeTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 0.8331s from 0.5000s limit to run CodeIgniter\\Database\\Live\\ForgeTest::testDropKey
{
$this->forge->dropTable('key_test_users', true);
$keyName = 'key_test_users_id';
$this->forge->dropTable('forge_test_users', true);
}
public function testProcessIndexes(): void

Check warning on line 1676 in tests/system/Database/Live/ForgeTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 1.3883s from 0.5000s limit to run CodeIgniter\\Database\\Live\\ForgeTest::testProcessIndexes

Check warning on line 1676 in tests/system/Database/Live/ForgeTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 1.4339s from 0.5000s limit to run CodeIgniter\\Database\\Live\\ForgeTest::testProcessIndexes
{
// make sure tables don't exist
$this->forge->dropTable('actions', true);
$this->assertSame('MySQLi', $this->getPrivateProperty($db1, 'DBDriver'));
}
public function testConnectWithFailover(): void

Check warning on line 97 in tests/system/Database/Live/ConnectTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 1.0913s from 0.5000s limit to run CodeIgniter\\Database\\Live\\ConnectTest::testConnectWithFailover

Check warning on line 97 in tests/system/Database/Live/ConnectTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 1.0884s from 0.5000s limit to run CodeIgniter\\Database\\Live\\ConnectTest::testConnectWithFailover
{
$this->tests['failover'][] = $this->tests;
unset($this->tests['failover'][0]['failover']);
protected $refresh = true;
public function testCreateAddsToDatabase(): void

Check warning on line 34 in tests/system/Database/Live/FabricatorLiveTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 0.8826s from 0.5000s limit to run CodeIgniter\\Database\\Live\\FabricatorLiveTest::testCreateAddsToDatabase

Check warning on line 34 in tests/system/Database/Live/FabricatorLiveTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 0.8928s from 0.5000s limit to run CodeIgniter\\Database\\Live\\FabricatorLiveTest::testCreateAddsToDatabase
{
$fabricator = new Fabricator(UserModel::class);
]);
}
public function testInsertBatchSuccess(): void

Check warning on line 52 in tests/system/Models/InsertModelTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 0.8045s from 0.5000s limit to run CodeIgniter\\Models\\InsertModelTest::testInsertBatchSuccess
{
$jobData = [
[
$this->createModel(UserModel::class)->insert([]);
}
public function testInsertPermitInsertNoData(): void

Check warning on line 239 in tests/system/Models/InsertModelTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 0.7332s from 0.5000s limit to run CodeIgniter\\Models\\InsertModelTest::testInsertPermitInsertNoData
{
$forge = Database::forge();
$forge->addField([
);
}
public function testDeleteBatchWithQuery(): void

Check warning on line 149 in tests/system/Database/Live/DeleteTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 0.7261s from 0.5000s limit to run CodeIgniter\\Database\\Live\\DeleteTest::testDeleteBatchWithQuery
{
$this->forge = Database::forge($this->DBGroup);
$this->seeInDatabase('job', ['name' => 'Grocery Sales']);
}
public function testInsertBatch(): void

Check warning on line 53 in tests/system/Database/Live/InsertTest.php

GitHub Actions / DatabaseLive (8.2, OCI8, 8.0) / tests

Took 0.8505s from 0.5000s limit to run CodeIgniter\\Database\\Live\\InsertTest::testInsertBatch
{
$table = 'type_test';