Skip to content
Closed
199 changes: 199 additions & 0 deletions Tests/AbstractDatabaseDriverTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
}
}
59 changes: 36 additions & 23 deletions src/DatabaseDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<socket>[^:]+)$/', $this->options['host'], $matches)) {
$portNew = $port ?? $defaultPort;

if (preg_match('/^unix:(?P<socket>[^:]+)$/', $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<host>((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<port>.+))?$/',
$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<host>\[.*\])(:(?P<port>.+))?$/', $this->options['host'], $matches)) {
} elseif (preg_match('/^(?P<host>\[.*\])(:(?P<port>.+))?$/', $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<host>(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P<port>[^:]+))?$/i', $this->options['host'], $matches)) {
} elseif (preg_match('/^(?P<host>(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P<port>[^:]+))?$/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<port>[^:]+)$/', $this->options['host'], $matches)) {
} elseif (preg_match('/^:(?P<port>[^:]+)$/', $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];
}
}
3 changes: 2 additions & 1 deletion src/Mysqli/MysqliDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
22 changes: 9 additions & 13 deletions src/Pdo/PdoDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down