Skip to content

Commit

Permalink
Replace parse_url() with a regex.
Browse files Browse the repository at this point in the history
parse_url() fails on passwords that contain # and other special URL
characters. I've re-implemented a DSN parser with a regular expression.
It handles all the DSN flavours in the core tests, and should be
transparent to userland code.

Refs #11019
  • Loading branch information
markstory committed Aug 10, 2017
1 parent f582340 commit 519b0ce
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 9 deletions.
23 changes: 14 additions & 9 deletions src/Core/StaticConfigTrait.php
Expand Up @@ -249,18 +249,23 @@ public static function parseDsn($dsn)
throw new InvalidArgumentException('Only strings can be passed to parseDsn');
}

$scheme = '';
if (preg_match("/^([\w\\\]+)/", $dsn, $matches)) {
$scheme = $matches[1];
$dsn = preg_replace("/^([\w\\\]+)/", 'file', $dsn);
}
$pattern = '/^(?P<scheme>[\w\\\\]+):\/\/((?P<user>.*?)(:(?P<password>.*?))?@)?' .
'((?P<host>[.\w\\\\]+)(:(?P<port>\d+))?)?' .
'(?P<path>\/[^?]*)?(\?(?P<query>.*))?$/';
preg_match($pattern, $dsn, $parsed);

$parsed = parse_url($dsn);
if ($parsed === false) {
return $dsn;
if (empty($parsed)) {
return false;
}
foreach ($parsed as $k => $v) {
if (is_int($k)) {
unset($parsed[$k]);
}
if ($v === '') {
unset($parsed[$k]);
}
}

$parsed['scheme'] = $scheme;
$query = '';

if (isset($parsed['query'])) {
Expand Down
23 changes: 23 additions & 0 deletions tests/TestCase/Datasource/ConnectionManagerTest.php
Expand Up @@ -420,6 +420,29 @@ public function testParseDsnClassnameDriver()
$this->assertEquals($expected, ConnectionManager::parseDsn($dsn));
}

/**
* Test parseDsn with special characters in the password.
*
* @return void
*/
public function testParseDsnSpecialPassword()
{
$dsn = 'mysql://user:pas#][{}$%20@!@localhost:3306/database?log=1&quoteIdentifiers=1';
$expected = [
'className' => 'Cake\Database\Connection',
'database' => 'database',
'driver' => 'Cake\Database\Driver\Mysql',
'host' => 'localhost',
'password' => 'pas#][{}$%20@!',
'port' => 3306,
'scheme' => 'mysql',
'username' => 'user',
'log' => 1,
'quoteIdentifiers' => 1
];
$this->assertEquals($expected, ConnectionManager::parseDsn($dsn));
}

/**
* Tests that directly setting an instance in a config, will not return a different
* instance later on
Expand Down

0 comments on commit 519b0ce

Please sign in to comment.