diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Core/SystemTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Core/SystemTest.php index b1efad7bf..dd3fbfae1 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Core/SystemTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Core/SystemTest.php @@ -171,4 +171,54 @@ class SystemTest extends TestCase $this->assertArrayHasKey("proxy_pass", $result); } + + /** + * This represents a set of connection strings that make up the dsn string. + * https://processmaker.atlassian.net/browse/PMCORE-574 + * @return array + */ + public function dsnConections() + { + return [ + ["oci8", "user1", "das#4dba", "localhost", "1521", "testDatabase?encoding=utf8"], + ["mssql", "user1", "Sample12345!@#", "localhost", "1433", "testDatabase?encoding=utf8"], + ["mysqli", "user1", "123*/.abc-+", "localhost", "3306", "testDatabase?encoding=utf8"], + ["mysqli", "user1", "123*/.abc-+", "localhost", "", "testDatabase?encoding=utf8"], + ["sqlite", "user1", "das#4dba", "localhost", "", "testDatabase?encoding=utf8"], + ["sybase", "user1", "123!das#4dba", "localhost", "1433", "testDatabase?encoding=utf8"], + ["sybase", "user1", "123!das@#4dba", "localhost", "1433", "testDatabase?encoding=utf8"], + ["sybase", "user1", "123!das@#4db@a", "localhost", "1433", "testDatabase?encoding=utf8"], + ]; + } + + /** + * This tests the parsing of the dsn string. + * @test + * @dataProvider dsnConections + * @covers \Creole::parseDSN() + * @covers \ProcessMaker\Core\System::parseUrlWithNotEncodedPassword() + */ + public function it_should_return_parse_url_for_dsn_string_with_special_characters($scheme, $user, $password, $host, $port, $database) + { + $hostname = $host; + if (!empty($port)) { + $hostname = $host . ":" . $port; + } + $dsn = $scheme . "://" . $user . ":" . $password . "@" . $hostname . "/" . $database; + $result = System::parseUrlWithNotEncodedPassword($dsn); + $this->assertEquals($scheme, $result["scheme"]); + $this->assertEquals($user, $result["user"]); + $this->assertEquals($password, $result["pass"]); + $this->assertEquals($host, $result["host"]); + if (!empty($port)) { + $this->assertEquals($port, $result["port"]); + } + + $dsn = $scheme; + $result = System::parseUrlWithNotEncodedPassword($dsn); + $this->assertEmpty($result["scheme"]); + $this->assertEmpty($result["user"]); + $this->assertEmpty($result["pass"]); + $this->assertEmpty($result["host"]); + } } diff --git a/thirdparty/creole/Creole.php b/thirdparty/creole/Creole.php index 72b5e1683..c3ffd7ea2 100644 --- a/thirdparty/creole/Creole.php +++ b/thirdparty/creole/Creole.php @@ -28,6 +28,8 @@ include_once 'creole/Connection.php'; @ini_set('track_errors', true); + use ProcessMaker\Core\System; + /** * This is the class that manages the database drivers. * @@ -309,6 +311,10 @@ class Creole { ); $info = parse_url($dsn); + if ($info === false) { + $info = System::parseUrlWithNotEncodedPassword($dsn); + } + $info['pass'] = urldecode($info['pass']); if (count($info) === 1) { // if there's only one element in result, then it must be the phptype $parsed['phptype'] = array_pop($info); diff --git a/workflow/engine/src/ProcessMaker/Core/System.php b/workflow/engine/src/ProcessMaker/Core/System.php index c517da11c..ff9aea65a 100644 --- a/workflow/engine/src/ProcessMaker/Core/System.php +++ b/workflow/engine/src/ProcessMaker/Core/System.php @@ -1695,4 +1695,50 @@ class System } return (object) $result; } + + /** + * Parse an url with not encoded password that break the native “parse_url” function. + * @param string $dsn + * @return array + */ + public static function parseUrlWithNotEncodedPassword(string $dsn): array + { + $default = [ + 'scheme' => '', + 'host' => '', + 'port' => '', + 'user' => '', + 'pass' => '', + 'path' => '', + 'query' => '', + ]; + $separator = "://"; + $colon = ":"; + $at = "@"; + + $result = explode($separator, $dsn, 2); + if (empty($result[0]) || empty($result[1])) { + return $default; + } + $scheme = $result[0]; + $urlWithoutScheme = $result[1]; + + $colonPosition = strpos($urlWithoutScheme, $colon); + $user = substr($urlWithoutScheme, 0, $colonPosition); + + $withoutUser = substr($urlWithoutScheme, $colonPosition + 1); + $atPosition = strrpos($withoutUser, $at); + $pass = substr($urlWithoutScheme, $colonPosition + 1, $atPosition); + + $withoutPass = substr($withoutUser, $atPosition + 1); + + $fixedDsn = $scheme . $separator . $user . $colon . urlencode($pass) . $at . $withoutPass; + + $parseDsn = parse_url($fixedDsn); + if ($parseDsn === false) { + return $default; + } + $parseDsn["pass"] = urldecode($parseDsn["pass"]); + return $parseDsn; + } }