diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -372,6 +372,7 @@ 'PhutilPygmentsSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php', 'PhutilPythonFragmentLexer' => 'lexer/PhutilPythonFragmentLexer.php', 'PhutilQsprintfInterface' => 'xsprintf/PhutilQsprintfInterface.php', + 'PhutilQueryString' => 'xsprintf/PhutilQueryString.php', 'PhutilQueryStringParser' => 'parser/PhutilQueryStringParser.php', 'PhutilQueryStringParserTestCase' => 'parser/__tests__/PhutilQueryStringParserTestCase.php', 'PhutilRainbowSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php', @@ -1023,6 +1024,7 @@ 'PhutilPygmentizeParserTestCase' => 'PhutilTestCase', 'PhutilPygmentsSyntaxHighlighter' => 'Phobject', 'PhutilPythonFragmentLexer' => 'PhutilLexer', + 'PhutilQueryString' => 'Phobject', 'PhutilQueryStringParser' => 'Phobject', 'PhutilQueryStringParserTestCase' => 'PhutilTestCase', 'PhutilRainbowSyntaxHighlighter' => 'Phobject', diff --git a/src/xsprintf/PhutilQueryString.php b/src/xsprintf/PhutilQueryString.php new file mode 100644 --- /dev/null +++ b/src/xsprintf/PhutilQueryString.php @@ -0,0 +1,39 @@ +escaper = $escaper; + $this->argv = $argv; + + // This makes sure we throw immediately if there are errors in the + // parameters. + $this->getMaskedString(); + } + + public function __toString() { + return $this->getMaskedString(); + } + + public function getUnmaskedString() { + return $this->renderString(true); + } + + public function getMaskedString() { + return $this->renderString(false); + } + + private function renderString($unmasked) { + return xsprintf( + 'xsprintf_query', + array( + 'escaper' => $this->escaper, + 'unmasked' => $unmasked, + ), + $this->argv); + } + +} diff --git a/src/xsprintf/qsprintf.php b/src/xsprintf/qsprintf.php --- a/src/xsprintf/qsprintf.php +++ b/src/xsprintf/qsprintf.php @@ -61,12 +61,12 @@ function qsprintf(PhutilQsprintfInterface $escaper, $pattern /* , ... */) { $args = func_get_args(); array_shift($args); - return xsprintf('xsprintf_query', $escaper, $args); + return new PhutilQueryString($escaper, $args); } function vqsprintf(PhutilQsprintfInterface $escaper, $pattern, array $argv) { array_unshift($argv, $pattern); - return xsprintf('xsprintf_query', $escaper, $argv); + return new PhutilQueryString($escaper, $argv); } /** @@ -74,12 +74,19 @@ * @{function:qsprintf}. */ function xsprintf_query($userdata, &$pattern, &$pos, &$value, &$length) { - $type = $pattern[$pos]; - $escaper = $userdata; - $next = (strlen($pattern) > $pos + 1) ? $pattern[$pos + 1] : null; + $type = $pattern[$pos]; + if (is_array($userdata)) { + $escaper = $userdata['escaper']; + $unmasked = $userdata['unmasked']; + } else { + $escaper = $userdata; + $unmasked = false; + } + + $next = (strlen($pattern) > $pos + 1) ? $pattern[$pos + 1] : null; $nullable = false; - $done = false; + $done = false; $prefix = ''; @@ -190,6 +197,9 @@ break; case 'Q': // Query Fragment + if ($value instanceof PhutilQueryString) { + $value = $value->getUnmaskedString(); + } $type = 's'; break; @@ -287,6 +297,24 @@ function qsprintf_check_scalar_type($value, $type, $query) { switch ($type) { case 'Q': + // TODO: See T13217. Remove this eventually. + if (is_string($value)) { + phlog( + pht( + 'UNSAFE: Raw string ("%s") passed to query ("%s") for "%%Q" '. + 'conversion. %%Q should be passed a query string.', + $value, + $query)); + break; + } + + if (!($value instanceof PhutilQueryString)) { + throw new AphrontParameterQueryException( + $query, + pht('Expected a PhutilQueryString for %%%s conversion.', $type)); + } + break; + case 'LC': case 'T': case 'C': diff --git a/src/xsprintf/queryfx.php b/src/xsprintf/queryfx.php --- a/src/xsprintf/queryfx.php +++ b/src/xsprintf/queryfx.php @@ -4,6 +4,8 @@ $argv = func_get_args(); $query = call_user_func_array('qsprintf', $argv); + $query = $query->getUnmaskedString(); + $conn->setLastActiveEpoch(time()); $conn->executeRawQuery($query); }