Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,18 @@
import net.sf.saxon.s9api.XQueryExecutable;
import net.sf.saxon.s9api.SaxonApiException;

import java.util.regex.Pattern;

public class XQueryImplUtil {

private static final Pattern XML_CHARACTER_REFERENCES_PATTERN = Pattern.compile("['\"&<>]");
/** Shared across all calls. Thread-safe; expensive to construct. One instance per JVM is sufficient.
* See Saxon s9api {@link net.sf.saxon.s9api.Processor} Javadoc. */
private static final Processor PROCESSOR = new Processor(false);
Comment thread
yesamer marked this conversation as resolved.

/**
* Shared across all calls. Concurrent use is permitted, but error messages may not be
* attributed to the correct thread under concurrent error conditions.
* See Saxon s9api {@link net.sf.saxon.s9api.XQueryCompiler} Javadoc.
*/
private static final XQueryCompiler COMPILER = PROCESSOR.newXQueryCompiler();
Comment thread
yesamer marked this conversation as resolved.

private XQueryImplUtil() {
// Util class with static methods only.
Expand All @@ -50,9 +57,7 @@ public static String executeReplaceFunction(String input, String pattern, String

static <T> T evaluateXQueryExpression(String expression, Class<T> expectedTypeResult) {
try {
Processor processor = new Processor(false);
XQueryCompiler compiler = processor.newXQueryCompiler();
XQueryExecutable executable = compiler.compile(expression);
XQueryExecutable executable = COMPILER.compile(expression);
Comment thread
yesamer marked this conversation as resolved.
XQueryEvaluator queryEvaluator = executable.load();
XdmItem resultItem = queryEvaluator.evaluateSingle();

Expand All @@ -66,22 +71,38 @@ static <T> T evaluateXQueryExpression(String expression, Class<T> expectedTypeRe
} catch (SaxonApiException e) {
throw new IllegalArgumentException(e);
}
}
}

/**
* It replaces all the XML Character References (&, ", ', <, >) in a given input string with their "escaping" characters.
* This is required to run XPath functions containing XML Character References.
* @param input A string input representing one of the parameter of managed functions
* @return A sanitized string
* Escapes XML special characters ({@code & " ' < >}) so the value is safe to embed
* as an XPath string literal. Returns {@code null} unchanged; returns the original
* reference if no escaping is needed.
*/
static String escapeXmlCharactersReferencesForXPath(String input) {
Comment thread
yesamer marked this conversation as resolved.
if (input != null && XML_CHARACTER_REFERENCES_PATTERN.matcher(input).find()) {
input = input.contains("&") ? input.replace("&", "&amp;") : input;
input = input.contains("\"") ? input.replace("\"", "&quot;") : input;
input = input.contains("'") ? input.replace("'", "&apos;") : input;
input = input.contains("<") ? input.replace("<", "&lt;") : input;
input = input.contains(">") ? input.replace(">", "&gt;") : input;
if (input == null) {
return null;
}
StringBuilder sb = null;
Comment thread
yesamer marked this conversation as resolved.
for (int i = 0; i < input.length(); i++) {
char ch = input.charAt(i);
String replacement = switch (ch) {
case '&' -> "&amp;";
case '"' -> "&quot;";
case '\'' -> "&apos;";
case '<' -> "&lt;";
case '>' -> "&gt;";
default -> null;
};
if (replacement != null) {
if (sb == null) {
sb = new StringBuilder(input.length() + 16);
sb.append(input, 0, i);
}
sb.append(replacement);
} else if (sb != null) {
sb.append(ch);
}
}
return input;
return sb != null ? sb.toString() : input;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ private static Object[][] executeMatchesFunctionTestData() {
{ "test", "^test", "i", true },
{ "fo\nbar", "o.b", null, false },
{ "TEST", "test", "i", true },
// flags
{ "test", "^test", "", true }, // flags = "" explicitly
{ "FO\nBAR", "fo.bar", "si", true }, // dotall + case-insensitive
{ "hello\nworld", "^WORLD", "mi", true }, // multiline + case-insensitive
// XML special characters in input — exercises the escape→embed→evaluate pipeline
{ "it's", "it.s", "", true }, // single quote in input
{ "say \"hi\"", "say .hi.", "", true }, // double quote in input
{ "a&b", "a.b", "", true }, // ampersand in input
{ "a<b", "a.b", "", true }, // less-than in input
{ "a>b", "a.b", "", true }, // greater-than in input
// XML special characters in pattern
{ "<tag>", "<tag>", "", true }, // angle brackets in pattern
};
}

Expand Down Expand Up @@ -66,6 +78,16 @@ private static Object[][] executeReplaceFunctionTestData() {
return new Object[][] {
{ "testString", "^test", "ttt", "", "tttString" },
{ "fo\nbar", "o.b", "ttt", "s", "ftttar" },
// flags
{ "FO\nBAR", "fo.bar", "X", "si", "X" }, // dotall + case-insensitive
// XML special characters in input — exercises the escape→embed→evaluate pipeline
{ "a&b", "a.b", "X", "", "X" }, // ampersand in input
{ "it's", "it.s", "X", "", "X" }, // single quote in input
// XML special characters in replacement
{ "hello", "hello", "a&b", "", "a&b" }, // ampersand in replacement
{ "hello", "hello", "it's", "", "it's" }, // single quote in replacement
// backreference in replacement
{ "hello", "(h)", "$1$1", "", "hhello" }, // $1 backreference
};
}

Expand Down Expand Up @@ -122,12 +144,30 @@ void escapeXmlCharactersReferencesForXPathTest(String expression, String expecte

private static Object[][] escapeXmlCharactersReferencesForXPathTestData() {
return new Object[][] {
// null / empty
{ null, null },
{ "", "" },
// no special chars — original reference must be returned unchanged
{ "lolASD", "lolASD" },
// each of the five special characters in isolation (single-char string)
{ "&", "&amp;" },
{ "\"", "&quot;" },
{ "'", "&apos;" },
{ "<", "&lt;" },
{ ">", "&gt;" },
// mixed: < and > (no &, no quotes)
{ "List<String>", "List&lt;String&gt;" },
// mixed: " only
{ "\"Mr.Y\"", "&quot;Mr.Y&quot;" },
// mixed: all five present — ' < & > ' (missing " in a multi-char mix)
{ "'<&>'", "&apos;&lt;&amp;&gt;&apos;" },
// mixed: all five chars including " alongside others
{ "a&b\"c'<d>", "a&amp;b&quot;c&apos;&lt;d&gt;" },
// special char first, last, and in the middle
{ "&start", "&amp;start" },
{ "end&", "end&amp;" },
{ "mid&dle", "mid&amp;dle" },
{ "no special chars here 1234", "no special chars here 1234" },
};
}

Expand Down
2 changes: 1 addition & 1 deletion kie-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@
<version.net.byte-buddy>1.17.6</version.net.byte-buddy>
<version.net.java.dev.glazedlists>1.8.0</version.net.java.dev.glazedlists>
<version.net.minidev.jsonsmart>2.4.10</version.net.minidev.jsonsmart>
<version.net.sf.saxon.Saxon-HE>12.7</version.net.sf.saxon.Saxon-HE>
<version.net.sf.saxon.Saxon-HE>12.10</version.net.sf.saxon.Saxon-HE>
<version.net.thisptr.jackson-jq>1.0.0-preview.20240207</version.net.thisptr.jackson-jq>
<version.org.antlr>3.5.2</version.org.antlr>
<version.org.antlr.ST4>4.0.7</version.org.antlr.ST4>
Expand Down
Loading