diff --git a/Tests/AbstractDatabaseDriverTestCase.php b/Tests/AbstractDatabaseDriverTestCase.php index 2d7ea2b1..93456cf5 100644 --- a/Tests/AbstractDatabaseDriverTestCase.php +++ b/Tests/AbstractDatabaseDriverTestCase.php @@ -1036,4 +1036,203 @@ public function testMonitorWithRepeatedStatement() $params ); } + + /** + * @testdox Pure host name or IP address and port or socket can be extracted from the host name option + */ + public function testExtractHostPortSocket() + { + $refObject = new \ReflectionObject(static::$connection); + $refMethod = $refObject->getMethod('extractHostPortSocket'); + + $this->assertSame( + ['', 3306, null], + $refMethod->invoke(static::$connection, '', null, null, 3306) + ); + + $this->assertSame( + ['', 3307, null], + $refMethod->invoke(static::$connection, '', 3307, null, 3306) + ); + + $this->assertSame( + [null, null, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, null, null, '/path/to/unix/socket.sock', 3306) + ); + + $this->assertSame( + [null, 3307, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, null, 3307, '/path/to/unix/socket.sock', 3306) + ); + + $this->assertSame( + [null, 3306, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, 'unix:/path/to/unix/socket.sock', null, null, 3306) + ); + + $this->assertSame( + [null, 3307, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, 'unix:/path/to/unix/socket.sock', 3307, null, 3306) + ); + + $this->assertSame( + ['192.168.67.254', 3306, null], + $refMethod->invoke(static::$connection, '192.168.67.254', null, null, 3306) + ); + + $this->assertSame( + ['192.168.67.254', 3307, null], + $refMethod->invoke(static::$connection, '192.168.67.254', 3307, null, 3306) + ); + + $this->assertSame( + ['192.168.67.254', 3308, null], + $refMethod->invoke(static::$connection, '192.168.67.254:3308', null, null, 3306) + ); + + $this->assertSame( + ['192.168.67.254', 3308, null], + $refMethod->invoke(static::$connection, '192.168.67.254:3308', 3307, null, 3306) + ); + + $this->assertSame( + ['[fe80:102::2%eth1]', 3306, null], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]', null, null, 3306) + ); + + $this->assertSame( + ['[fe80:102::2%eth1]', 3307, null], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]', 3307, null, 3306) + ); + + $this->assertSame( + ['[fe80:102::2%eth1]', 3308, null], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]:3308', null, null, 3306) + ); + + $this->assertSame( + ['[fe80:102::2%eth1]', 3308, null], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]:3308', 3307, null, 3306) + ); + + $this->assertSame( + ['fe80:102::2%eth1', 3306, null], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]', null, null, 3306, false) + ); + + $this->assertSame( + ['fe80:102::2%eth1', 3307, null], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]', 3307, null, 3306, false) + ); + + $this->assertSame( + ['fe80:102::2%eth1', 3308, null], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]:3308', null, null, 3306, false) + ); + + $this->assertSame( + ['fe80:102::2%eth1', 3308, null], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]:3308', 3307, null, 3306, false) + ); + + $this->assertSame( + ['somehost', 3306, null], + $refMethod->invoke(static::$connection, 'somehost', null, null, 3306) + ); + + $this->assertSame( + ['somehost', 3307, null], + $refMethod->invoke(static::$connection, 'somehost', 3307, null, 3306) + ); + + $this->assertSame( + ['somehost', 3308, null], + $refMethod->invoke(static::$connection, 'somehost:3308', null, null, 3306) + ); + + $this->assertSame( + ['somehost', 3308, null], + $refMethod->invoke(static::$connection, 'somehost:3308', 3307, null, 3306) + ); + + $this->assertSame( + ['somehost.example.com', 3306, null], + $refMethod->invoke(static::$connection, 'somehost.example.com', null, null, 3306) + ); + + $this->assertSame( + ['somehost.example.com', 3307, null], + $refMethod->invoke(static::$connection, 'somehost.example.com', 3307, null, 3306) + ); + + $this->assertSame( + ['somehost.example.com', 3308, null], + $refMethod->invoke(static::$connection, 'somehost.example.com:3308', null, null, 3306) + ); + + $this->assertSame( + ['somehost.example.com', 3308, null], + $refMethod->invoke(static::$connection, 'somehost.example.com:3308', 3307, null, 3306) + ); + + $this->assertSame( + ['localhost', 3308, null], + $refMethod->invoke(static::$connection, ':3308', null, null, 3306) + ); + + $this->assertSame( + ['localhost', 3308, null], + $refMethod->invoke(static::$connection, ':3308', 3307, null, 3306) + ); + + $this->assertSame( + ['somehost', null, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, 'somehost:/path/to/unix/socket.sock', null, null, 3306) + ); + + $this->assertSame( + ['somehost', 3307, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, 'somehost:/path/to/unix/socket.sock', 3307, null, 3306) + ); + + $this->assertSame( + ['192.168.67.254', null, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, '192.168.67.254:/path/to/unix/socket.sock', null, null, 3306) + ); + + $this->assertSame( + ['192.168.67.254', 3307, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, '192.168.67.254:/path/to/unix/socket.sock', 3307, null, 3306) + ); + + $this->assertSame( + ['[fe80:102::2%eth1]', null, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]:/path/to/unix/socket.sock', null, null, 3306) + ); + + $this->assertSame( + ['[fe80:102::2%eth1]', 3307, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]:/path/to/unix/socket.sock', 3307, null, 3306) + ); + + $this->assertSame( + ['fe80:102::2%eth1', null, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]:/path/to/unix/socket.sock', null, null, 3306, false) + ); + + $this->assertSame( + ['fe80:102::2%eth1', 3307, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, '[fe80:102::2%eth1]:/path/to/unix/socket.sock', 3307, null, 3306, false) + ); + + $this->assertSame( + ['localhost', null, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, ':/path/to/unix/socket.sock', null, null, 3306) + ); + + $this->assertSame( + ['localhost', 3307, '/path/to/unix/socket.sock'], + $refMethod->invoke(static::$connection, ':/path/to/unix/socket.sock', 3307, null, 3306) + ); + } } diff --git a/src/DatabaseDriver.php b/src/DatabaseDriver.php index 87e77e38..a5b9bd64 100644 --- a/src/DatabaseDriver.php +++ b/src/DatabaseDriver.php @@ -1897,59 +1897,72 @@ public function updateObject($table, &$object, $key, $nulls = false) /** * Extract pure host name (or IP address) and port or socket from host name option. * - * @param integer $defaultPort The default port number to be used if no port is given. + * @param string $host Host given in options used to configure the connection, null if none. + * @param integer|null $port Port given in options used to configure the connection, null if none. + * @param string|null $socket Socket given in options used to configure the connection, null if none. + * @param integer $defaultPort The default port number to be used if no port is given. + * @param boolean $ipv6SquareBrackets True if database connector uses ipv6 address with square brackets, false if not. * - * @since __DEPLOY_VERSION__ + * @return array Array with host, port and socket. + * + * @since __DEPLOY_VERSION__ */ - protected function setHostPortSocket($defaultPort) + protected function extractHostPortSocket(?string $host, ?int $port, ?string $socket, int $defaultPort, bool $ipv6SquareBrackets = true): array { - $port = $this->options['port'] ?? $defaultPort; + // Do nothing if a socket is given and no host + if ($host === null && $socket !== null) { + return [$host, $port, $socket]; + } - if (preg_match('/^unix:(?P[^:]+)$/', $this->options['host'], $matches)) { + $portNew = $port ?? $defaultPort; + + if (preg_match('/^unix:(?P[^:]+)$/', $host, $matches)) { // UNIX socket URI, e.g. 'unix:/path/to/unix/socket.sock' - $this->options['host'] = null; - $this->options['socket'] = $matches['socket']; - $this->options['port'] = null; + $host = null; + $socket = $matches['socket']; + $port = null; } elseif ( preg_match( '/^(?P((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(:(?P.+))?$/', - $this->options['host'], + $host, $matches ) ) { // It's an IPv4 address with or without port - $this->options['host'] = $matches['host']; + $host = $matches['host']; if (!empty($matches['port'])) { - $port = $matches['port']; + $portNew = $matches['port']; } - } elseif (preg_match('/^(?P\[.*\])(:(?P.+))?$/', $this->options['host'], $matches)) { + } elseif (preg_match('/^(?P\[.*\])(:(?P.+))?$/', $host, $matches)) { // We assume square-bracketed IPv6 address with or without port, e.g. [fe80:102::2%eth1]:3306 - $this->options['host'] = $matches['host']; + $host = $ipv6SquareBrackets ? $matches['host'] : rtrim(ltrim($matches['host'], '['), ']'); if (!empty($matches['port'])) { - $port = $matches['port']; + $portNew = $matches['port']; } - } elseif (preg_match('/^(?P(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P[^:]+))?$/i', $this->options['host'], $matches)) { + } elseif (preg_match('/^(?P(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P[^:]+))?$/i', $host, $matches)) { // Named host (e.g example.com or localhost) with or without port - $this->options['host'] = $matches['host']; + $host = $matches['host']; if (!empty($matches['port'])) { - $port = $matches['port']; + $portNew = $matches['port']; } - } elseif (preg_match('/^:(?P[^:]+)$/', $this->options['host'], $matches)) { + } elseif (preg_match('/^:(?P[^:]+)$/', $host, $matches)) { // Empty host, just port, e.g. ':3306' - $this->options['host'] = 'localhost'; - $port = $matches['port']; + $host = 'localhost'; + $portNew = $matches['port']; } // ... else we assume normal (naked) IPv6 address, so host and port stay as they are or default // Get the port number or socket name - if (is_numeric($port)) { - $this->options['port'] = (int) $port; + if (is_numeric($portNew)) { + $port = (int) $portNew; } else { - $this->options['socket'] = $port; + $socket = $portNew; } + + return [$host, $port, $socket]; } } diff --git a/src/Mysqli/MysqliDriver.php b/src/Mysqli/MysqliDriver.php index dbc6977c..540e3bb4 100644 --- a/src/Mysqli/MysqliDriver.php +++ b/src/Mysqli/MysqliDriver.php @@ -199,7 +199,8 @@ public function connect() } // Extract host and port or socket from host option - $this->setHostPortSocket(3306); + [$this->options['host'], $this->options['port'], $this->options['socket']] + = $this->extractHostPortSocket($this->options['host'], $this->options['port'], $this->options['socket'], 3306); $this->connection = mysqli_init(); diff --git a/src/Pdo/PdoDriver.php b/src/Pdo/PdoDriver.php index 6bdfe857..3ebbac57 100644 --- a/src/Pdo/PdoDriver.php +++ b/src/Pdo/PdoDriver.php @@ -210,22 +210,17 @@ public function connect() case 'mysql': // Extract host and port or socket from host option - $this->setHostPortSocket(3306); + [$host, $port, $socket] + = $this->extractHostPortSocket($this->options['host'], $this->options['port'], $this->options['socket'], 3306); - if ($this->options['socket'] !== null) { + if ($socket !== null) { $format = 'mysql:unix_socket=#SOCKET#;dbname=#DBNAME#;charset=#CHARSET#'; } else { $format = 'mysql:host=#HOST#;port=#PORT#;dbname=#DBNAME#;charset=#CHARSET#'; } $replace = ['#HOST#', '#PORT#', '#SOCKET#', '#DBNAME#', '#CHARSET#']; - $with = [ - $this->options['host'], - $this->options['port'], - $this->options['socket'], - $this->options['database'], - $this->options['charset'], - ]; + $with = [$host, $port, $socket, $this->options['database'], $this->options['charset']]; break; @@ -258,17 +253,18 @@ public function connect() break; case 'pgsql': - // Extract host and port or socket from host option - $this->setHostPortSocket(5432); + // Extract host and port or socket from host option and remove square brackets around ipv6 address + [$host, $port, $socket] + = $this->extractHostPortSocket($this->options['host'], $this->options['port'], $this->options['socket'], 5432, false); - if ($this->options['socket'] !== null) { + if ($socket !== null) { $format = 'pgsql:host=#SOCKET#;dbname=#DBNAME#'; } else { $format = 'pgsql:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; } $replace = ['#HOST#', '#PORT#', '#SOCKET#', '#DBNAME#']; - $with = [$this->options['host'], $this->options['port'], $this->options['socket'], $this->options['database']]; + $with = [$host, $port, $socket, $this->options['database']]; // For data in transit TLS encryption. if ($this->options['ssl'] !== [] && $this->options['ssl']['enable'] === true) {