diff --git a/lib/Service/SqlGuard.php b/lib/Service/SqlGuard.php index 7af03af..392b415 100644 --- a/lib/Service/SqlGuard.php +++ b/lib/Service/SqlGuard.php @@ -122,7 +122,14 @@ class SqlGuard while ($i < $len) { $end = self::literalEnd($sql, $i); if ($end !== null) { - $result .= $replaceLiterals ? ($sql[$i] === '"' ? '""' : "''") : substr($sql, $i, $end - $i); + $literal = substr($sql, $i, $end - $i); + if (preg_match('/^"([A-Za-z_][A-Za-z0-9_$]*)"$/', $literal, $m) && preg_match('/\G\s*\(/', $sql, $unused, 0, $end)) { + // A quoted function name ("setval"(...)) calls the same + // function as the bare word, so check it as one + $result .= $m[1]; + } else { + $result .= $replaceLiterals ? ($sql[$i] === '"' ? '""' : "''") : $literal; + } $i = $end; continue; } diff --git a/tests/Unit/Service/SqlGuardTest.php b/tests/Unit/Service/SqlGuardTest.php index 9091708..9c23193 100644 --- a/tests/Unit/Service/SqlGuardTest.php +++ b/tests/Unit/Service/SqlGuardTest.php @@ -24,6 +24,8 @@ class SqlGuardTest extends TestCase ["SELECT lo_import('/etc/passwd')"], ["SELECT dblink_connect('host=x')"], ["SELECT dblink_exec('host=x', 'DROP TABLE t')"], + ["SELECT \"pg_read_file\"('/etc/passwd')"], + ["SELECT pg_catalog.\"pg_read_file\"('/etc/passwd')"], ["SELECT dblink_send_query('c', 'SELECT 1')"], ["SELECT * FROM dblink('host=x', 'SELECT 1') AS t(a int)"], ["SELECT * FROM t INTO OUTFILE '/tmp/x'"], @@ -122,6 +124,9 @@ class SqlGuardTest extends TestCase ["SELECT set_config('x', 'y', false)"], ["SELECT setval('seq', 1)"], ["SELECT nextval('seq')"], + ["SELECT \"setval\"('seq', 1)"], + ["SELECT pg_catalog.\"setval\"('seq', 1)"], + ["SELECT pg_catalog.setval('seq', 1)"], ['MERGE INTO t USING s ON true WHEN MATCHED THEN DELETE'], ['VACUUM'], ['PRAGMA journal_mode = WAL'],