From 4da87dc15876060c620353718b72a80b7c63f821 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 5 Nov 2026 22:17:26 +0100 Subject: [PATCH] fix(Database): escape table, column, and sequence names in Postgre Connection::insertID() --- docs/changelogs/v4.7.7.md | 1 + system/Database/Postgre/Connection.php | 4 +- .../system/Database/Postgre/InsertIDTest.php | 102 ++++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 tests/system/Database/Postgre/InsertIDTest.php diff --git a/docs/changelogs/v4.7.7.md b/docs/changelogs/v4.7.7.md index cdf4eb2db148..f57922a725d9 100644 --- a/docs/changelogs/v4.7.7.md +++ b/docs/changelogs/v4.7.7.md @@ -14,6 +14,7 @@ Release Date: Unreleased ## Bugs Fixed +- **Database:** Fixed a bug where `Postgre\Connection::insertID()` interpolated table, column, and sequence names without escaping. - **Security:** Fixed a bug where `Security::__construct()` threw a `TypeError` when the CSRF cookie was an array, causing an HTTP 500 response for affected requests. - **Worker Mode:** Fixed a bug where every request that ended with an uncaught exception, including a `404 Page Not Found` without a 404 override, called `exit()` in the exception handler. This ended the worker script, so FrankenPHP booted a new one, and the worker loop skipped its cleanup after the request. The exception handler now returns to the worker loop. diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 900f8f716c38..1a7526630ffd 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -489,7 +489,7 @@ public function insertID() $sql = 'SELECT LASTVAL() AS ins_id'; } elseif ($table !== null) { if ($column !== null && $v >= '8.0') { - $sql = "SELECT pg_get_serial_sequence('{$table}', '{$column}') AS seq"; + $sql = 'SELECT pg_get_serial_sequence(' . $this->escape($table) . ', ' . $this->escape($column) . ') AS seq'; $query = $this->query($sql); $query = $query->getRow(); $seq = $query->seq; @@ -498,7 +498,7 @@ public function insertID() $seq = $table; } - $sql = "SELECT CURRVAL('{$seq}') AS ins_id"; + $sql = 'SELECT CURRVAL(' . $this->escape($seq) . ') AS ins_id'; } else { return pg_last_oid($this->resultID); } diff --git a/tests/system/Database/Postgre/InsertIDTest.php b/tests/system/Database/Postgre/InsertIDTest.php new file mode 100644 index 000000000000..bd96f2fdbf81 --- /dev/null +++ b/tests/system/Database/Postgre/InsertIDTest.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Database\Postgre; + +use CodeIgniter\Test\CIUnitTestCase; +use PHPUnit\Framework\Attributes\Group; +use stdClass; + +/** + * Mock pg_version in the Postgre namespace to test insertID without live DB connection. + * + * @param mixed $connection + * + * @return array{server: string} + */ +function pg_version($connection = null): array +{ + return ['server' => '15.0']; +} + +/** + * Mock pg_escape_literal in the Postgre namespace for testing without live DB connection. + * + * @param mixed $connection + */ +function pg_escape_literal($connection, string $string): string +{ + return "'" . str_replace("'", "''", $string) . "'"; +} + +/** + * @internal + */ +#[Group('Others')] +final class InsertIDTest extends CIUnitTestCase +{ + public function testInsertIDQuotesTableColumnAndSequenceNames(): void + { + $connection = new class () extends Connection { + /** + * @var list + */ + public array $queries = []; + + public function __construct() + { + $this->DBDriver = 'Postgre'; + $this->connID = false; + } + + public function initialize(): void + { + } + + /** + * @param array|string|null $binds + */ + public function query(string $sql, $binds = null, bool $setEscapeFlags = true, string $queryClass = ''): Result + { + $this->queries[] = $sql; + + $row = new stdClass(); + $row->seq = "user's_id_seq"; + $row->ins_id = 123; + + $result = new class ($this->connID, $this->resultID) extends Result { + public function setRowData(stdClass $row): void + { + $this->resultObject = [$row]; + } + }; + $result->setRowData($row); + + return $result; + } + }; + + $id = $connection->insertID("user's", "id'col"); + + $this->assertSame(123, $id); + $this->assertCount(2, $connection->queries); + $this->assertSame( + "SELECT pg_get_serial_sequence('user''s', 'id''col') AS seq", + $connection->queries[0], + ); + $this->assertSame( + "SELECT CURRVAL('user''s_id_seq') AS ins_id", + $connection->queries[1], + ); + } +}