diff --git a/.env.example.complete b/.env.example.complete index 6b4936648..25687aaac 100644 --- a/.env.example.complete +++ b/.env.example.complete @@ -56,6 +56,7 @@ APP_PROXIES=null # Database details # Host can contain a port (localhost:3306) or a separate DB_PORT option can be used. +# An ipv6 address can be used via the square bracket format ([::1]). DB_HOST=localhost DB_PORT=3306 DB_DATABASE=database_database diff --git a/app/Config/database.php b/app/Config/database.php index 59ac0f31b..8d38a86df 100644 --- a/app/Config/database.php +++ b/app/Config/database.php @@ -40,12 +40,16 @@ if (env('REDIS_SERVERS', false)) { // MYSQL // Split out port from host if set -$mysql_host = env('DB_HOST', 'localhost'); -$mysql_host_exploded = explode(':', $mysql_host); -$mysql_port = env('DB_PORT', 3306); -if (count($mysql_host_exploded) > 1) { - $mysql_host = $mysql_host_exploded[0]; - $mysql_port = intval($mysql_host_exploded[1]); +$mysqlHost = env('DB_HOST', 'localhost'); +$mysqlHostExploded = explode(':', $mysqlHost); +$mysqlPort = env('DB_PORT', 3306); +$mysqlHostIpv6 = str_starts_with($mysqlHost, '['); +if ($mysqlHostIpv6 && str_contains($mysqlHost, ']:')) { + $mysqlHost = implode(':', array_slice($mysqlHostExploded, 0, -1)); + $mysqlPort = intval(end($mysqlHostExploded)); +} else if (!$mysqlHostIpv6 && count($mysqlHostExploded) > 1) { + $mysqlHost = $mysqlHostExploded[0]; + $mysqlPort = intval($mysqlHostExploded[1]); } return [ @@ -61,12 +65,12 @@ return [ 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), - 'host' => $mysql_host, + 'host' => $mysqlHost, 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), - 'port' => $mysql_port, + 'port' => $mysqlPort, 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', // Prefixes are only semi-supported and may be unstable @@ -88,7 +92,7 @@ return [ 'database' => 'bookstack-test', 'username' => env('MYSQL_USER', 'bookstack-test'), 'password' => env('MYSQL_PASSWORD', 'bookstack-test'), - 'port' => $mysql_port, + 'port' => $mysqlPort, 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', diff --git a/tests/TestCase.php b/tests/TestCase.php index b63de3076..0fb899ea9 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -118,7 +118,7 @@ abstract class TestCase extends BaseTestCase * Database config is juggled so the value can be restored when * parallel testing are used, where multiple databases exist. */ - protected function runWithEnv(string $name, $value, callable $callback) + protected function runWithEnv(string $name, $value, callable $callback, bool $handleDatabase = true) { Env::disablePutenv(); $originalVal = $_SERVER[$name] ?? null; @@ -132,13 +132,17 @@ abstract class TestCase extends BaseTestCase $database = config('database.connections.mysql_testing.database'); $this->refreshApplication(); - DB::purge(); - config()->set('database.connections.mysql_testing.database', $database); - DB::beginTransaction(); + if ($handleDatabase) { + DB::purge(); + config()->set('database.connections.mysql_testing.database', $database); + DB::beginTransaction(); + } $callback(); - DB::rollBack(); + if ($handleDatabase) { + DB::rollBack(); + } if (is_null($originalVal)) { unset($_SERVER[$name]); diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index d5c74392f..2883fddb8 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -160,6 +160,27 @@ class ConfigTest extends TestCase $this->assertTrue($isMailTlsRequired()); } + public function test_mysql_host_parsed_as_expected() + { + $cases = [ + '127.0.0.1' => ['127.0.0.1', 3306], + '127.0.0.1:3307' => ['127.0.0.1', 3307], + 'a.example.com' => ['a.example.com', 3306], + 'a.example.com:3307' => ['a.example.com', 3307], + '[::1]' => ['[::1]', 3306], + '[::1]:123' => ['[::1]', 123], + '[2001:db8:3c4d:0015:0000:0000:1a2f]' => ['[2001:db8:3c4d:0015:0000:0000:1a2f]', 3306], + '[2001:db8:3c4d:0015:0000:0000:1a2f]:4567' => ['[2001:db8:3c4d:0015:0000:0000:1a2f]', 4567], + ]; + + foreach ($cases as $host => [$expectedHost, $expectedPort]) { + $this->runWithEnv("DB_HOST", $host, function () use ($expectedHost, $expectedPort) { + $this->assertEquals($expectedHost, config("database.connections.mysql.host")); + $this->assertEquals($expectedPort, config("database.connections.mysql.port")); + }, false); + } + } + /** * Set an environment variable of the given name and value * then check the given config key to see if it matches the given result.