From 2a89bf79f28b94d5a4ad9a3c5d8b0f51664aaa34 Mon Sep 17 00:00:00 2001 From: Torben Dannhauer Date: Wed, 22 Jul 2026 22:51:39 +0200 Subject: [PATCH] fix(core): omit TCP defaults for Unix-socket SQL config Core#198 preserved protocol/socket keys but still injected host=localhost and port=3306. PDO strips protocol from the DSN, so socket PostgreSQL deployments still dial MySQL's port and fail (e.g. smartmobile portal Token/PermissionService for non-admins). --- src/Factory/DbServiceFactory.php | 33 +++++++++++--- test/Unit/Factory/DbServiceFactoryTest.php | 51 ++++++++++++++++++++++ 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/Factory/DbServiceFactory.php b/src/Factory/DbServiceFactory.php index b9888e21..be2dfb37 100644 --- a/src/Factory/DbServiceFactory.php +++ b/src/Factory/DbServiceFactory.php @@ -38,6 +38,7 @@ * @category Horde * @package Core * @author Ralf Lang + * @author Torben Dannhauer * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 */ class DbServiceFactory @@ -196,6 +197,11 @@ private function getAdapterClass(string $phptype): string * * Normalizes config keys across different naming conventions. * + * For protocol=unix, do not inject TCP host/port defaults. The PDO + * adapters strip `protocol` when building the DSN; if host=localhost + * and port=3306 remain, PostgreSQL tries TCP to MySQL's port and + * fails on socket-only deployments (see Horde Core#198 follow-up). + * * @param array $sqlConfig Raw SQL configuration * @return array Normalized adapter configuration */ @@ -205,14 +211,9 @@ private function buildConnectionConfig(array $sqlConfig): array 'username' => $sqlConfig['username'] ?? '', 'password' => $sqlConfig['password'] ?? '', 'database' => $sqlConfig['database'] ?? $sqlConfig['dbname'] ?? '', - 'host' => $sqlConfig['hostspec'] ?? $sqlConfig['host'] ?? 'localhost', - 'port' => $sqlConfig['port'] ?? 3306, 'charset' => $sqlConfig['charset'] ?? 'UTF-8', ]; - // Preserve Unix-socket connection settings when the config asks - // for them; without this the adapter falls back to the TCP - // host/port defaults above and breaks socket deployments. if (isset($sqlConfig['protocol'])) { $config['protocol'] = $sqlConfig['protocol']; } @@ -220,6 +221,28 @@ private function buildConnectionConfig(array $sqlConfig): array $config['socket'] = $sqlConfig['socket']; } + $protocol = $sqlConfig['protocol'] ?? null; + $hasExplicitHost = isset($sqlConfig['hostspec']) || isset($sqlConfig['host']); + + // Unix socket: only pass host/port when the config sets them. + // Omitting them lets PDO_pgsql use the default server socket. + if ($protocol === 'unix') { + if ($hasExplicitHost) { + $config['host'] = $sqlConfig['hostspec'] ?? $sqlConfig['host']; + } + if (isset($sqlConfig['port'])) { + $config['port'] = $sqlConfig['port']; + } + + return $config; + } + + $phptype = $sqlConfig['phptype'] ?? 'mysqli'; + $defaultPort = ($phptype === 'pgsql') ? 5432 : 3306; + + $config['host'] = $sqlConfig['hostspec'] ?? $sqlConfig['host'] ?? 'localhost'; + $config['port'] = $sqlConfig['port'] ?? $defaultPort; + return $config; } } diff --git a/test/Unit/Factory/DbServiceFactoryTest.php b/test/Unit/Factory/DbServiceFactoryTest.php index ead2ac7d..01fc869a 100644 --- a/test/Unit/Factory/DbServiceFactoryTest.php +++ b/test/Unit/Factory/DbServiceFactoryTest.php @@ -39,6 +39,41 @@ public function testBuildConnectionConfigPreservesUnixProtocol(): void self::assertSame('unix', $config['protocol']); self::assertSame('/var/run/postgresql/.s.PGSQL.5432', $config['socket']); self::assertSame('horde', $config['database']); + // Must not invent TCP defaults — PDO would dial localhost:3306. + self::assertArrayNotHasKey('host', $config); + self::assertArrayNotHasKey('port', $config); + } + + public function testBuildConnectionConfigUnixWithoutSocketOmitsTcpDefaults(): void + { + $config = $this->buildConnectionConfig([ + 'username' => 'horde', + 'password' => 'secret', + 'protocol' => 'unix', + 'database' => 'horde', + 'phptype' => 'pgsql', + ]); + + self::assertSame('unix', $config['protocol']); + self::assertArrayNotHasKey('host', $config); + self::assertArrayNotHasKey('port', $config); + self::assertArrayNotHasKey('socket', $config); + } + + public function testBuildConnectionConfigUnixKeepsExplicitHostAndPort(): void + { + $config = $this->buildConnectionConfig([ + 'username' => 'horde', + 'password' => 'secret', + 'protocol' => 'unix', + 'hostspec' => '/var/run/postgresql', + 'port' => 5432, + 'database' => 'horde', + 'phptype' => 'pgsql', + ]); + + self::assertSame('/var/run/postgresql', $config['host']); + self::assertSame(5432, $config['port']); } public function testBuildConnectionConfigRenamesHostspecToHost(): void @@ -57,6 +92,20 @@ public function testBuildConnectionConfigRenamesHostspecToHost(): void self::assertSame(5433, $config['port']); } + public function testBuildConnectionConfigDefaultsPgsqlTcpPort(): void + { + $config = $this->buildConnectionConfig([ + 'username' => 'horde', + 'password' => 'secret', + 'protocol' => 'tcp', + 'database' => 'horde', + 'phptype' => 'pgsql', + ]); + + self::assertSame('localhost', $config['host']); + self::assertSame(5432, $config['port']); + } + public function testBuildConnectionConfigDefaultsCharset(): void { $config = $this->buildConnectionConfig([ @@ -67,6 +116,8 @@ public function testBuildConnectionConfigDefaultsCharset(): void ]); self::assertSame('UTF-8', $config['charset']); + self::assertSame('localhost', $config['host']); + self::assertSame(3306, $config['port']); } /**