diff --git a/src/Database/Log/QueryLogger.php b/src/Database/Log/QueryLogger.php index d676ea6b41f..22aa34096a6 100644 --- a/src/Database/Log/QueryLogger.php +++ b/src/Database/Log/QueryLogger.php @@ -68,7 +68,19 @@ protected function _interpolate($query) return $p ? '1' : '0'; } - return is_string($p) ? "'$p'" : $p; + if (is_string($p)) { + $replacements = [ + '$' => '\\$', + '\\' => '\\\\\\\\', + "'" => "''", + ]; + + $p = strtr($p, $replacements); + + return "'$p'"; + } + + return $p; }, $query->params); $keys = []; diff --git a/tests/TestCase/Database/Log/QueryLoggerTest.php b/tests/TestCase/Database/Log/QueryLoggerTest.php index 217401dc13f..01e50778020 100644 --- a/tests/TestCase/Database/Log/QueryLoggerTest.php +++ b/tests/TestCase/Database/Log/QueryLoggerTest.php @@ -127,6 +127,26 @@ public function testStringInterpolationNamed() $this->assertEquals($expected, (string)$query); } + /** + * Tests that placeholders are replaced with correctly escaped strings + * + * @return void + */ + public function testStringInterpolationSpecialChars() + { + $logger = $this->getMockBuilder('\Cake\Database\Log\QueryLogger') + ->setMethods(['_log']) + ->getMock(); + $query = new LoggedQuery; + $query->query = 'SELECT a FROM b where a = :p1 AND b = :p2 AND c = :p3 AND d = :p4'; + $query->params = ['p1' => '$2y$10$dUAIj', 'p2' => '$0.23', 'p3' => 'a\\0b\\1c\\d', 'p4' => "a'b"]; + + $logger->expects($this->once())->method('_log')->with($query); + $logger->log($query); + $expected = "duration=0 rows=0 SELECT a FROM b where a = '\$2y\$10\$dUAIj' AND b = '\$0.23' AND c = 'a\\\\0b\\\\1c\\\\d' AND d = 'a''b'"; + $this->assertEquals($expected, (string)$query); + } + /** * Tests that the logged query object is passed to the built-in logger using * the correct scope