Browse Source

Merge pull request #11024 from cakephp/issue-11019

Replace parse_url() with a regex when parsing DSN strings.
Mark Story 8 years ago
parent
commit
c38c90e716

+ 15 - 10
src/Core/StaticConfigTrait.php

@@ -237,7 +237,7 @@ trait StaticConfigTrait
      *
      * @param string $dsn The DSN string to convert to a configuration array
      * @return array The configuration array to be stored after parsing the DSN
-     * @throws \InvalidArgumentException If not passed a string
+     * @throws \InvalidArgumentException If not passed a string, or passed an invalid string
      */
     public static function parseDsn($dsn)
     {
@@ -249,18 +249,23 @@ trait StaticConfigTrait
             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 (!$parsed) {
+            throw new InvalidArgumentException("The DSN string '{$dsn}' could not be parsed.");
+        }
+        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'])) {

+ 0 - 183
tests/TestCase/Core/StaticConfigTraitTest.php

@@ -17,53 +17,6 @@ use Cake\Core\StaticConfigTrait;
 use Cake\TestSuite\TestCase;
 
 /**
- * TestConnectionManagerStaticConfig
- */
-class TestConnectionManagerStaticConfig
-{
-
-    use StaticConfigTrait {
-        parseDsn as protected _parseDsn;
-    }
-
-    /**
-     * Parse a DSN
-     *
-     * @param string $config The config to parse.
-     * @return array
-     */
-    public static function parseDsn($config = null)
-    {
-        $config = static::_parseDsn($config);
-
-        if (isset($config['path']) && empty($config['database'])) {
-            $config['database'] = substr($config['path'], 1);
-        }
-
-        if (empty($config['driver'])) {
-            $config['driver'] = $config['className'];
-            $config['className'] = 'Cake\Database\Connection';
-        }
-
-        unset($config['path']);
-
-        return $config;
-    }
-
-    /**
-     * Database driver class map.
-     *
-     * @var array
-     */
-    protected static $_dsnClassMap = [
-        'mysql' => 'Cake\Database\Driver\Mysql',
-        'postgres' => 'Cake\Database\Driver\Postgres',
-        'sqlite' => 'Cake\Database\Driver\Sqlite',
-        'sqlserver' => 'Cake\Database\Driver\Sqlserver',
-    ];
-}
-
-/**
  * TestCacheStaticConfig
  */
 class TestCacheStaticConfig
@@ -179,142 +132,6 @@ class StaticConfigTraitTest extends TestCase
     }
 
     /**
-     * Tests parsing different DSNs
-     *
-     * @return void
-     */
-    public function testCustomParseDsn()
-    {
-        $dsn = 'mysql://localhost:3306/database';
-        $expected = [
-            'className' => 'Cake\Database\Connection',
-            'driver' => 'Cake\Database\Driver\Mysql',
-            'host' => 'localhost',
-            'database' => 'database',
-            'port' => 3306,
-            'scheme' => 'mysql',
-        ];
-        $this->assertEquals($expected, TestConnectionManagerStaticConfig::parseDsn($dsn));
-
-        $dsn = 'mysql://user:password@localhost:3306/database';
-        $expected = [
-            'className' => 'Cake\Database\Connection',
-            'driver' => 'Cake\Database\Driver\Mysql',
-            'host' => 'localhost',
-            'password' => 'password',
-            'database' => 'database',
-            'port' => 3306,
-            'scheme' => 'mysql',
-            'username' => 'user',
-        ];
-        $this->assertEquals($expected, TestConnectionManagerStaticConfig::parseDsn($dsn));
-
-        $dsn = 'sqlite:///:memory:';
-        $expected = [
-            'className' => 'Cake\Database\Connection',
-            'driver' => 'Cake\Database\Driver\Sqlite',
-            'database' => ':memory:',
-            'scheme' => 'sqlite',
-        ];
-        $this->assertEquals($expected, TestConnectionManagerStaticConfig::parseDsn($dsn));
-
-        $dsn = 'sqlite:////absolute/path';
-        $expected = [
-            'className' => 'Cake\Database\Connection',
-            'driver' => 'Cake\Database\Driver\Sqlite',
-            'database' => '/absolute/path',
-            'scheme' => 'sqlite',
-        ];
-        $this->assertEquals($expected, TestConnectionManagerStaticConfig::parseDsn($dsn));
-
-        $dsn = 'sqlite:///?database=:memory:';
-        $expected = [
-            'className' => 'Cake\Database\Connection',
-            'driver' => 'Cake\Database\Driver\Sqlite',
-            'database' => ':memory:',
-            'scheme' => 'sqlite',
-        ];
-        $this->assertEquals($expected, TestConnectionManagerStaticConfig::parseDsn($dsn));
-
-        $dsn = 'sqlserver://sa:Password12!@.\SQL2012SP1/cakephp?MultipleActiveResultSets=false';
-        $expected = [
-            'className' => 'Cake\Database\Connection',
-            'driver' => 'Cake\Database\Driver\Sqlserver',
-            'host' => '.\SQL2012SP1',
-            'MultipleActiveResultSets' => false,
-            'password' => 'Password12!',
-            'database' => 'cakephp',
-            'scheme' => 'sqlserver',
-            'username' => 'sa',
-        ];
-        $this->assertEquals($expected, TestConnectionManagerStaticConfig::parseDsn($dsn));
-    }
-
-    /**
-     * Tests className/driver value setting
-     *
-     * @return void
-     */
-    public function testParseDsnClassnameDriver()
-    {
-        $dsn = 'mysql://localhost:3306/database';
-        $expected = [
-            'className' => 'Cake\Database\Connection',
-            'database' => 'database',
-            'driver' => 'Cake\Database\Driver\Mysql',
-            'host' => 'localhost',
-            'port' => 3306,
-            'scheme' => 'mysql',
-        ];
-        $this->assertEquals($expected, TestConnectionManagerStaticConfig::parseDsn($dsn));
-
-        $dsn = 'mysql://user:password@localhost:3306/database';
-        $expected = [
-            'className' => 'Cake\Database\Connection',
-            'database' => 'database',
-            'driver' => 'Cake\Database\Driver\Mysql',
-            'host' => 'localhost',
-            'password' => 'password',
-            'port' => 3306,
-            'scheme' => 'mysql',
-            'username' => 'user',
-        ];
-        $this->assertEquals($expected, TestConnectionManagerStaticConfig::parseDsn($dsn));
-
-        $dsn = 'mysql://localhost/database?className=Custom\Driver';
-        $expected = [
-            'className' => 'Cake\Database\Connection',
-            'database' => 'database',
-            'driver' => 'Custom\Driver',
-            'host' => 'localhost',
-            'scheme' => 'mysql',
-        ];
-        $this->assertEquals($expected, TestConnectionManagerStaticConfig::parseDsn($dsn));
-
-        $dsn = 'mysql://localhost:3306/database?className=Custom\Driver';
-        $expected = [
-            'className' => 'Cake\Database\Connection',
-            'database' => 'database',
-            'driver' => 'Custom\Driver',
-            'host' => 'localhost',
-            'scheme' => 'mysql',
-            'port' => 3306,
-        ];
-        $this->assertEquals($expected, TestConnectionManagerStaticConfig::parseDsn($dsn));
-
-        $dsn = 'Cake\Database\Connection://localhost:3306/database?driver=Cake\Database\Driver\Mysql';
-        $expected = [
-            'className' => 'Cake\Database\Connection',
-            'database' => 'database',
-            'driver' => 'Cake\Database\Driver\Mysql',
-            'host' => 'localhost',
-            'scheme' => 'Cake\Database\Connection',
-            'port' => 3306,
-        ];
-        $this->assertEquals($expected, TestConnectionManagerStaticConfig::parseDsn($dsn));
-    }
-
-    /**
      * Tests parsing querystring values
      *
      * @return void

+ 149 - 13
tests/TestCase/Datasource/ConnectionManagerTest.php

@@ -263,28 +263,164 @@ class ConnectionManagerTest extends TestCase
     }
 
     /**
+     * provider for DSN strings.
+     *
+     * @return array
+     */
+    public function dsnProvider()
+    {
+        return [
+            'no user' => [
+                'mysql://localhost:3306/database',
+                [
+                    'className' => 'Cake\Database\Connection',
+                    'driver' => 'Cake\Database\Driver\Mysql',
+                    'host' => 'localhost',
+                    'database' => 'database',
+                    'port' => 3306,
+                    'scheme' => 'mysql',
+                ]
+            ],
+            'subdomain host' => [
+                'mysql://my.host-name.com:3306/database',
+                [
+                    'className' => 'Cake\Database\Connection',
+                    'driver' => 'Cake\Database\Driver\Mysql',
+                    'host' => 'my.host-name.com',
+                    'database' => 'database',
+                    'port' => 3306,
+                    'scheme' => 'mysql',
+                ]
+            ],
+            'user & pass' => [
+                'mysql://root:secret@localhost:3306/database?log=1',
+                [
+                    'scheme' => 'mysql',
+                    'className' => 'Cake\Database\Connection',
+                    'driver' => 'Cake\Database\Driver\Mysql',
+                    'host' => 'localhost',
+                    'username' => 'root',
+                    'password' => 'secret',
+                    'port' => 3306,
+                    'database' => 'database',
+                    'log' => '1'
+                ]
+            ],
+            'sqlite memory' => [
+                'sqlite:///:memory:',
+                [
+                    'className' => 'Cake\Database\Connection',
+                    'driver' => 'Cake\Database\Driver\Sqlite',
+                    'database' => ':memory:',
+                    'scheme' => 'sqlite',
+                ]
+            ],
+            'sqlite path' => [
+                'sqlite:////absolute/path',
+                [
+                    'className' => 'Cake\Database\Connection',
+                    'driver' => 'Cake\Database\Driver\Sqlite',
+                    'database' => '/absolute/path',
+                    'scheme' => 'sqlite',
+                ]
+            ],
+            'sqlite database query' => [
+                'sqlite:///?database=:memory:',
+                [
+                    'className' => 'Cake\Database\Connection',
+                    'driver' => 'Cake\Database\Driver\Sqlite',
+                    'database' => ':memory:',
+                    'scheme' => 'sqlite',
+                ]
+            ],
+            'sqlserver' => [
+                'sqlserver://sa:Password12!@.\SQL2012SP1/cakephp?MultipleActiveResultSets=false',
+                [
+                    'className' => 'Cake\Database\Connection',
+                    'driver' => 'Cake\Database\Driver\Sqlserver',
+                    'host' => '.\SQL2012SP1',
+                    'MultipleActiveResultSets' => false,
+                    'password' => 'Password12!',
+                    'database' => 'cakephp',
+                    'scheme' => 'sqlserver',
+                    'username' => 'sa',
+                ]
+            ],
+            'classname query arg' => [
+                'mysql://localhost/database?className=Custom\Driver',
+                [
+                    'className' => 'Cake\Database\Connection',
+                    'database' => 'database',
+                    'driver' => 'Custom\Driver',
+                    'host' => 'localhost',
+                    'scheme' => 'mysql',
+                ]
+            ],
+            'classname and port' => [
+                'mysql://localhost:3306/database?className=Custom\Driver',
+                [
+                    'className' => 'Cake\Database\Connection',
+                    'database' => 'database',
+                    'driver' => 'Custom\Driver',
+                    'host' => 'localhost',
+                    'scheme' => 'mysql',
+                    'port' => 3306,
+                ]
+            ],
+            'custom connection class' => [
+                'Cake\Database\Connection://localhost:3306/database?driver=Cake\Database\Driver\Mysql',
+                [
+                    'className' => 'Cake\Database\Connection',
+                    'database' => 'database',
+                    'driver' => 'Cake\Database\Driver\Mysql',
+                    'host' => 'localhost',
+                    'scheme' => 'Cake\Database\Connection',
+                    'port' => 3306,
+                ]
+            ],
+            'complex password' => [
+                'mysql://user:pas#][{}$%20@!@localhost:3306/database?log=1&quoteIdentifiers=1',
+                [
+                    '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,
+                ]
+            ]
+        ];
+    }
+
+    /**
      * Test parseDsn method.
      *
+     * @dataProvider dsnProvider
      * @return void
      */
-    public function testParseDsn()
+    public function testParseDsn($dsn, $expected)
     {
-        $result = ConnectionManager::parseDsn('mysql://root:secret@localhost:3306/database?log=1');
-        $expected = [
-            'scheme' => 'mysql',
-            'className' => 'Cake\Database\Connection',
-            'driver' => 'Cake\Database\Driver\Mysql',
-            'host' => 'localhost',
-            'username' => 'root',
-            'password' => 'secret',
-            'port' => 3306,
-            'database' => 'database',
-            'log' => '1'
-        ];
+        $result = ConnectionManager::parseDsn($dsn);
         $this->assertEquals($expected, $result);
     }
 
     /**
+     * Test parseDsn invalid.
+     *
+     * @expectedException InvalidArgumentException
+     * @expectedExceptionMessage The DSN string 'bagof:nope' could not be parsed.
+     * @return void
+     */
+    public function testParseDsnInvalid()
+    {
+        $result = ConnectionManager::parseDsn('bagof:nope');
+    }
+
+    /**
      * Tests that directly setting an instance in a config, will not return a different
      * instance later on
      *