Skip to content

Commit e21f72b

Browse files
committed
Support quoted strings in config string parser
1 parent 64a99d0 commit e21f72b

File tree

2 files changed

+78
-31
lines changed

2 files changed

+78
-31
lines changed

src/SqlConfig.php

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ abstract class SqlConfig
1212
'dbname' => 'db',
1313
];
1414

15+
private const KEY_VALUE_PAIR_REGEXP = <<<'REGEXP'
16+
[\G\s*(\w+)=((['"])(?:\\(?:\\|\3)|(?!\3).)*+\3|[^ '";]\S*?)(?:\s+|;|$)]
17+
REGEXP;
18+
1519
private string $host;
1620

1721
private int $port;
@@ -33,26 +37,48 @@ abstract class SqlConfig
3337
protected static function parseConnectionString(string $connectionString, array $keymap = self::KEY_MAP): array
3438
{
3539
$values = [];
40+
$connectionString = \trim($connectionString);
3641

37-
$params = \explode(";", $connectionString);
42+
if ($connectionString === '') {
43+
throw new \ValueError("Empty connection string");
44+
}
3845

39-
if (\count($params) === 1) { // Attempt to explode on a space if no ';' are found.
40-
$params = \explode(" ", $connectionString);
46+
if (!\preg_match_all(
47+
self::KEY_VALUE_PAIR_REGEXP,
48+
$connectionString,
49+
$matches,
50+
flags: \PREG_SET_ORDER | \PREG_UNMATCHED_AS_NULL,
51+
)) {
52+
throw new \ValueError("Invalid connection string");
4153
}
4254

43-
foreach ($params as $param) {
44-
/** @psalm-suppress PossiblyInvalidArgument */
45-
[$key, $value] = \array_map(\trim(...), \explode("=", $param, 2) + [1 => ""]);
46-
if ($key === '') {
47-
throw new \ValueError("Empty key name in connection string");
55+
$offset = 0;
56+
foreach ($matches as [$pair, $key, $value, $quote]) {
57+
if ($quote) {
58+
$value = \stripslashes(\substr($value, 1, -1));
59+
60+
if ($value === '') {
61+
throw new \ValueError("Empty connection string value for key '{$key}'");
62+
}
63+
}
64+
65+
$key = $keymap[$key] ?? $key;
66+
if (\array_key_exists($key, $values)) {
67+
throw new \ValueError("Duplicate connection string key '{$key}'");
4868
}
4969

50-
$values[$keymap[$key] ?? $key] = $value;
70+
$values[$key] = $value;
71+
72+
$offset += \strlen($pair);
73+
}
74+
75+
if ($offset !== \strlen($connectionString)) {
76+
throw new \ValueError("Trailing characters in connection string");
5177
}
5278

53-
if (\preg_match('/^(?<host>.+):(?<port>\d{1,5})$/', $values["host"] ?? "", $matches)) {
54-
$values["host"] = $matches["host"];
55-
$values["port"] = $matches["port"];
79+
if (\preg_match('[^(?<host>.+):(?<port>\d{1,5})$]', $values["host"] ?? "", $match)) {
80+
$values["host"] = $match["host"];
81+
$values["port"] = $match["port"];
5682
}
5783

5884
return $values;

test/SqlConfigTest.php

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,25 @@ public function __construct(string $connectionString)
2525
};
2626
}
2727

28-
public function testPortInHost(): void
28+
public function provideValidConnectionStrings(): array
2929
{
30-
$config = $this->createConfigFromString("host=localhost:5432 user=user database=test");
31-
32-
self::assertSame("localhost", $config->getHost());
33-
self::assertSame(5432, $config->getPort());
34-
self::assertSame("user", $config->getUser());
35-
self::assertSame("", $config->getPassword());
36-
self::assertSame("test", $config->getDatabase());
30+
return [
31+
'basic' => ["host=localhost port=5432 user=user pass=test db=test"],
32+
'alternative' => ["host=localhost;port=5432;user=user;password=test;db=test"],
33+
'port-in-host' => ["host=localhost:5432 user=user pass=test db=test"],
34+
'whitespace' => [" host=localhost port=5432 user=user pass=test db=test "],
35+
'whitespace-after-semicolon' => ["host=localhost; port=5432; user=user; password=test; db=test; "],
36+
'quotes' => ['host="localhost" port=5432 user="user" pass="test" db="test"'],
37+
'alternative-with-whitespace' => ["host=localhost; port=5432; user=user; password=test; db=test;"],
38+
];
3739
}
3840

39-
public function testBasicSyntax(): void
41+
/**
42+
* @dataProvider provideValidConnectionStrings
43+
*/
44+
public function testValidStrings(string $connectionString): void
4045
{
41-
$config = $this->createConfigFromString("host=localhost port=5432 user=user pass=test db=test");
46+
$config = $this->createConfigFromString($connectionString);
4247

4348
self::assertSame("localhost", $config->getHost());
4449
self::assertSame(5432, $config->getPort());
@@ -47,21 +52,37 @@ public function testBasicSyntax(): void
4752
self::assertSame("test", $config->getDatabase());
4853
}
4954

50-
public function testAlternativeSyntax(): void
55+
public function testQuoteInQuotedValue(): void
5156
{
52-
$config = $this->createConfigFromString("host=localhost;port=3306;user=user;password=test;db=test");
57+
$config = $this->createConfigFromString(
58+
<<<'CS'
59+
host="local\"host:3306" database='test'
60+
CS
61+
);
5362

54-
self::assertSame("localhost", $config->getHost());
63+
self::assertSame('local"host', $config->getHost());
5564
self::assertSame(3306, $config->getPort());
56-
self::assertSame("user", $config->getUser());
57-
self::assertSame("test", $config->getPassword());
58-
self::assertSame("test", $config->getDatabase());
65+
self::assertSame('test', $config->getDatabase());
66+
}
67+
68+
public function provideInvalidConnectionStrings(): array
69+
{
70+
return [
71+
'missing-value' => ['host='],
72+
'empty-value' => ['host=""'],
73+
'leading-characters' => ['test host=localhost'],
74+
'trailing-characters' => ['host=localhost test'],
75+
'invalid-whitespace' => ['host= localhost'],
76+
'duplicate-key' => ['host=localhost port=5432 port=5433']
77+
];
5978
}
6079

61-
public function testInvalidString(): void
80+
/**
81+
* @dataProvider provideInvalidConnectionStrings
82+
*/
83+
public function testInvalidStrings(string $connectionString): void
6284
{
6385
$this->expectException(\ValueError::class);
64-
$this->expectExceptionMessage("Empty key name in connection string");
65-
$this->createConfigFromString("invalid =connection string");
86+
$this->createConfigFromString($connectionString);
6687
}
6788
}

0 commit comments

Comments
 (0)