mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
DOMXPath::quote(string $str): string (#13456)
Method to quote strings in XPath, similar to PDO::quote() / mysqli::real_escape_string. Sample usage: $xp->query("//span[contains(text()," . $xp->quote($string) . ")]") The algorithm is derived from Robert Rossney's research into XPath quoting published at https://stackoverflow.com/a/1352556/1067003 But using an improved implementation I wrote myself, originally for https://github.com/chrome-php/chrome/pull/575
This commit is contained in:
parent
9603199547
commit
2f9320c00f
4 changed files with 148 additions and 1 deletions
|
@ -934,6 +934,8 @@ namespace
|
||||||
public function registerPhpFunctions(string|array|null $restrict = null): void {}
|
public function registerPhpFunctions(string|array|null $restrict = null): void {}
|
||||||
|
|
||||||
public function registerPhpFunctionNS(string $namespaceURI, string $name, callable $callable): void {}
|
public function registerPhpFunctionNS(string $namespaceURI, string $name, callable $callable): void {}
|
||||||
|
|
||||||
|
public static function quote(string $str): string {}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
12
ext/dom/php_dom_arginfo.h
generated
12
ext/dom/php_dom_arginfo.h
generated
|
@ -1,5 +1,5 @@
|
||||||
/* This is a generated file, edit the .stub.php file instead.
|
/* This is a generated file, edit the .stub.php file instead.
|
||||||
* Stub hash: 184308dfd1a133145d170c467e7600a12b14e327 */
|
* Stub hash: 498e4aa2e670454b78808215e8efaedb2ce7d251 */
|
||||||
|
|
||||||
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0)
|
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0)
|
||||||
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
|
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
|
||||||
|
@ -459,6 +459,12 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMXPath_registerPhpFuncti
|
||||||
ZEND_END_ARG_INFO()
|
ZEND_END_ARG_INFO()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(LIBXML_XPATH_ENABLED)
|
||||||
|
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMXPath_quote, 0, 1, IS_STRING, 0)
|
||||||
|
ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0)
|
||||||
|
ZEND_END_ARG_INFO()
|
||||||
|
#endif
|
||||||
|
|
||||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DOM_Document_createAttribute, 0, 0, 1)
|
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DOM_Document_createAttribute, 0, 0, 1)
|
||||||
ZEND_ARG_TYPE_INFO(0, localName, IS_STRING, 0)
|
ZEND_ARG_TYPE_INFO(0, localName, IS_STRING, 0)
|
||||||
ZEND_END_ARG_INFO()
|
ZEND_END_ARG_INFO()
|
||||||
|
@ -748,6 +754,9 @@ ZEND_METHOD(DOMXPath, registerPhpFunctions);
|
||||||
#if defined(LIBXML_XPATH_ENABLED)
|
#if defined(LIBXML_XPATH_ENABLED)
|
||||||
ZEND_METHOD(DOMXPath, registerPhpFunctionNS);
|
ZEND_METHOD(DOMXPath, registerPhpFunctionNS);
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(LIBXML_XPATH_ENABLED)
|
||||||
|
ZEND_METHOD(DOMXPath, quote);
|
||||||
|
#endif
|
||||||
ZEND_METHOD(DOM_Document, createAttribute);
|
ZEND_METHOD(DOM_Document, createAttribute);
|
||||||
ZEND_METHOD(DOM_Document, createAttributeNS);
|
ZEND_METHOD(DOM_Document, createAttributeNS);
|
||||||
ZEND_METHOD(DOM_Document, createCDATASection);
|
ZEND_METHOD(DOM_Document, createCDATASection);
|
||||||
|
@ -1002,6 +1011,7 @@ static const zend_function_entry class_DOMXPath_methods[] = {
|
||||||
ZEND_ME(DOMXPath, registerNamespace, arginfo_class_DOMXPath_registerNamespace, ZEND_ACC_PUBLIC)
|
ZEND_ME(DOMXPath, registerNamespace, arginfo_class_DOMXPath_registerNamespace, ZEND_ACC_PUBLIC)
|
||||||
ZEND_ME(DOMXPath, registerPhpFunctions, arginfo_class_DOMXPath_registerPhpFunctions, ZEND_ACC_PUBLIC)
|
ZEND_ME(DOMXPath, registerPhpFunctions, arginfo_class_DOMXPath_registerPhpFunctions, ZEND_ACC_PUBLIC)
|
||||||
ZEND_ME(DOMXPath, registerPhpFunctionNS, arginfo_class_DOMXPath_registerPhpFunctionNS, ZEND_ACC_PUBLIC)
|
ZEND_ME(DOMXPath, registerPhpFunctionNS, arginfo_class_DOMXPath_registerPhpFunctionNS, ZEND_ACC_PUBLIC)
|
||||||
|
ZEND_ME(DOMXPath, quote, arginfo_class_DOMXPath_quote, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
|
||||||
ZEND_FE_END
|
ZEND_FE_END
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
88
ext/dom/tests/DOMXPath_quote.phpt
Normal file
88
ext/dom/tests/DOMXPath_quote.phpt
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
--TEST--
|
||||||
|
Test DOMXPath::quote with various inputs
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--SKIPIF--
|
||||||
|
<?php if (!class_exists('DOMXPath')) die('skip DOMXPath not available.'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
$dom = new DOMDocument();
|
||||||
|
$xpath = new DOMXPath($dom);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quote a string for use in an XPath expression.
|
||||||
|
*
|
||||||
|
* Example: $xp->query("//span[contains(text()," . $xp->quote($string) . ")]")
|
||||||
|
*
|
||||||
|
* @param string $string string to quote.
|
||||||
|
* @return string quoted string.
|
||||||
|
*/
|
||||||
|
function UserlandDOMXPathQuote(string $string): string
|
||||||
|
{
|
||||||
|
if (false === \strpos($string, '\'')) {
|
||||||
|
return '\'' . $string . '\'';
|
||||||
|
}
|
||||||
|
if (false === \strpos($string, '"')) {
|
||||||
|
return '"' . $string . '"';
|
||||||
|
}
|
||||||
|
// if the string contains both single and double quotes, construct an
|
||||||
|
// expression that concatenates all non-double-quote substrings with
|
||||||
|
// the quotes, e.g.:
|
||||||
|
// 'foo'"bar => concat("'foo'", '"bar")
|
||||||
|
$sb = [];
|
||||||
|
while ($string !== '') {
|
||||||
|
$bytesUntilSingleQuote = \strcspn($string, '\'');
|
||||||
|
$bytesUntilDoubleQuote = \strcspn($string, '"');
|
||||||
|
$quoteMethod = ($bytesUntilSingleQuote > $bytesUntilDoubleQuote) ? "'" : '"';
|
||||||
|
$bytesUntilQuote = \max($bytesUntilSingleQuote, $bytesUntilDoubleQuote);
|
||||||
|
$sb[] = $quoteMethod . \substr($string, 0, $bytesUntilQuote) . $quoteMethod;
|
||||||
|
$string = \substr($string, $bytesUntilQuote);
|
||||||
|
}
|
||||||
|
$sb = \implode(',', $sb);
|
||||||
|
return 'concat(' . $sb . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$tests = [
|
||||||
|
'' => "''", // empty string
|
||||||
|
'foo' => "'foo'", // no quotes
|
||||||
|
'"foo' => '\'"foo\'', // double quotes only
|
||||||
|
'\'foo' => '"\'foo"', // single quotes only
|
||||||
|
'\'foo"bar' => 'concat("\'foo",\'"bar\')', // both; double quotes in mid-string
|
||||||
|
'\'foo"bar"baz' => 'concat("\'foo",\'"bar"baz\')', // multiple double quotes in mid-string
|
||||||
|
'\'foo"' => 'concat("\'foo",\'"\')', // string ends with double quotes
|
||||||
|
'\'foo""' => 'concat("\'foo",\'""\')', // string ends with run of double quotes
|
||||||
|
'"\'foo' => 'concat(\'"\',"\'foo")', // string begins with double quotes
|
||||||
|
'""\'foo' => 'concat(\'""\',"\'foo")', // string begins with run of double quotes
|
||||||
|
'\'foo""bar' => 'concat("\'foo",\'""bar\')', // run of double quotes in mid-string
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($tests as $input => $expected) {
|
||||||
|
$result = $xpath->quote($input);
|
||||||
|
if ($result === $expected) {
|
||||||
|
echo "Pass: {$input} => {$result}\n";
|
||||||
|
} else {
|
||||||
|
echo 'Fail: ';
|
||||||
|
var_dump([
|
||||||
|
'input' => $input,
|
||||||
|
'expected' => $expected,
|
||||||
|
'result' => $result,
|
||||||
|
'userland_implementation_result' => UserlandDOMXPathQuote($input),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Pass: => ''
|
||||||
|
Pass: foo => 'foo'
|
||||||
|
Pass: "foo => '"foo'
|
||||||
|
Pass: 'foo => "'foo"
|
||||||
|
Pass: 'foo"bar => concat("'foo",'"bar')
|
||||||
|
Pass: 'foo"bar"baz => concat("'foo",'"bar"baz')
|
||||||
|
Pass: 'foo" => concat("'foo",'"')
|
||||||
|
Pass: 'foo"" => concat("'foo",'""')
|
||||||
|
Pass: "'foo => concat('"',"'foo")
|
||||||
|
Pass: ""'foo => concat('""',"'foo")
|
||||||
|
Pass: 'foo""bar => concat("'foo",'""bar')
|
|
@ -446,6 +446,53 @@ PHP_METHOD(DOMXPath, registerPhpFunctionNS)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* {{{ */
|
||||||
|
PHP_METHOD(DOMXPath, quote) {
|
||||||
|
const char *input;
|
||||||
|
size_t input_len;
|
||||||
|
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &input, &input_len) == FAILURE) {
|
||||||
|
RETURN_THROWS();
|
||||||
|
}
|
||||||
|
if (memchr(input, '\'', input_len) == NULL) {
|
||||||
|
zend_string *const output = zend_string_safe_alloc(1, input_len, 2, false);
|
||||||
|
output->val[0] = '\'';
|
||||||
|
memcpy(output->val + 1, input, input_len);
|
||||||
|
output->val[input_len + 1] = '\'';
|
||||||
|
output->val[input_len + 2] = '\0';
|
||||||
|
RETURN_STR(output);
|
||||||
|
} else if (memchr(input, '"', input_len) == NULL) {
|
||||||
|
zend_string *const output = zend_string_safe_alloc(1, input_len, 2, false);
|
||||||
|
output->val[0] = '"';
|
||||||
|
memcpy(output->val + 1, input, input_len);
|
||||||
|
output->val[input_len + 1] = '"';
|
||||||
|
output->val[input_len + 2] = '\0';
|
||||||
|
RETURN_STR(output);
|
||||||
|
} else {
|
||||||
|
smart_str output = {0};
|
||||||
|
// need to use the concat() trick published by Robert Rossney at https://stackoverflow.com/a/1352556/1067003
|
||||||
|
smart_str_appendl(&output, "concat(", 7);
|
||||||
|
const char *ptr = input;
|
||||||
|
const char *const end = input + input_len;
|
||||||
|
while (ptr < end) {
|
||||||
|
const char *const single_quote_ptr = memchr(ptr, '\'', end - ptr);
|
||||||
|
const char *const double_quote_ptr = memchr(ptr, '"', end - ptr);
|
||||||
|
const size_t distance_to_single_quote = single_quote_ptr ? single_quote_ptr - ptr : end - ptr;
|
||||||
|
const size_t distance_to_double_quote = double_quote_ptr ? double_quote_ptr - ptr : end - ptr;
|
||||||
|
const size_t bytes_until_quote = MAX(distance_to_single_quote, distance_to_double_quote);
|
||||||
|
const char quote_method = (distance_to_single_quote > distance_to_double_quote) ? '\'' : '"';
|
||||||
|
smart_str_appendc(&output, quote_method);
|
||||||
|
smart_str_appendl(&output, ptr, bytes_until_quote);
|
||||||
|
smart_str_appendc(&output, quote_method);
|
||||||
|
ptr += bytes_until_quote;
|
||||||
|
smart_str_appendc(&output, ',');
|
||||||
|
}
|
||||||
|
ZEND_ASSERT(ptr == end);
|
||||||
|
output.s->val[output.s->len - 1] = ')';
|
||||||
|
RETURN_STR(smart_str_extract(&output));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* }}} */
|
||||||
|
|
||||||
#endif /* LIBXML_XPATH_ENABLED */
|
#endif /* LIBXML_XPATH_ENABLED */
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue