Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
49 changes: 27 additions & 22 deletions src/main/java/org/apache/commons/jxpath/ri/model/NodePointer.java
Original file line number Diff line number Diff line change
Expand Up @@ -398,38 +398,43 @@ public NodePointer createPath(final JXPathContext context, final Object value) {
}

/**
* Return a string escaping single and double quotes.
* Return a string escaping single and double quotes, as well as any ampersand that would otherwise be read back as the start of an {@code '},
* {@code "} or {@code &} sequence. Without the latter, two distinct names, one containing a quote and one containing the corresponding entity
* sequence, would produce the same path.
*
* @param string string to treat
* @return string with any necessary changes made.
*/
protected String escape(final String string) {
final char[] c = { '\'', '"' };
final String[] esc = { "'", """ };
StringBuilder sb = null;
for (int i = 0; sb == null && i < c.length; i++) {
if (string.indexOf(c[i]) >= 0) {
sb = new StringBuilder(string);
}
}
if (sb == null) {
return string;
}
for (int i = 0; i < c.length; i++) {
if (string.indexOf(c[i]) < 0) {
continue;
for (int i = 0; i < string.length(); i++) {
final char c = string.charAt(i);
String entity = null;
switch (c) {
case '\'':
entity = "&apos;";
break;
case '"':
entity = "&quot;";
break;
case '&':
if (string.startsWith("apos;", i + 1) || string.startsWith("quot;", i + 1) || string.startsWith("amp;", i + 1)) {
entity = "&amp;";
}
break;
default:
break;
}
int pos = 0;
while (pos < sb.length()) {
if (sb.charAt(pos) == c[i]) {
sb.replace(pos, pos + 1, esc[i]);
pos += esc[i].length();
} else {
pos++;
if (entity != null) {
if (sb == null) {
sb = new StringBuilder(string.length() * 2).append(string, 0, i);
}
sb.append(entity);
} else if (sb != null) {
sb.append(c);
}
}
return sb.toString();
return sb == null ? string : sb.toString();
}

/**
Expand Down
33 changes: 25 additions & 8 deletions src/main/java/org/apache/commons/jxpath/ri/parser/XPath.jj
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,34 @@ PARSER_BEGIN(XPathParser)
}

private String unescape(String string){
int index = string.indexOf("&apos;");
while (index != -1){
string = string.substring(0, index) + "\'" + string.substring(index + 6);
index = string.indexOf("&apos;");
int index = string.indexOf('&');
if (index == -1){
return string;
}
index = string.indexOf("&quot;");
StringBuilder sb = new StringBuilder(string.length());
int pos = 0;
while (index != -1){
string = string.substring(0, index) + "\"" + string.substring(index + 6);
index = string.indexOf("&quot;");
sb.append(string, pos, index);
if (string.startsWith("&apos;", index)){
sb.append('\'');
pos = index + 6;
}
else if (string.startsWith("&quot;", index)){
sb.append('"');
pos = index + 6;
}
else if (string.startsWith("&amp;", index)){
sb.append('&');
pos = index + 5;
}
else {
sb.append('&');
pos = index + 1;
}
index = string.indexOf('&', pos);
}
return string;
sb.append(string, pos, string.length());
return sb.toString();
}
}

Expand Down
30 changes: 22 additions & 8 deletions src/main/java/org/apache/commons/jxpath/ri/parser/XPathParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -3813,17 +3813,31 @@ final public Object UnaryExpr() throws ParseException {
}

private String unescape(String string) {
int index = string.indexOf("&apos;");
while (index != -1) {
string = string.substring(0, index) + "\'" + string.substring(index + 6);
index = string.indexOf("&apos;");
int index = string.indexOf('&');
if (index == -1) {
return string;
}
index = string.indexOf("&quot;");
final StringBuilder sb = new StringBuilder(string.length());
int pos = 0;
while (index != -1) {
string = string.substring(0, index) + "\"" + string.substring(index + 6);
index = string.indexOf("&quot;");
sb.append(string, pos, index);
if (string.startsWith("&apos;", index)) {
sb.append('\'');
pos = index + 6;
} else if (string.startsWith("&quot;", index)) {
sb.append('"');
pos = index + 6;
} else if (string.startsWith("&amp;", index)) {
sb.append('&');
pos = index + 5;
} else {
sb.append('&');
pos = index + 1;
}
index = string.indexOf('&', pos);
}
return string;
sb.append(string, pos, string.length());
return sb.toString();
}

/* [18] UnionExpr ::= PathExpr | UnionExpr '|' PathExpr */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ void testAttributeName() {
assertXPathValueAndPointer(context, "/map[@name='Key:4:5']", null, "/map[@name='Key:4:5']");
}

/**
* A property name containing a literal "&amp;apos;" sequence must produce a pointer path distinct from the path of a property name containing an actual
* quote, and each path must evaluate back to its own entry.
*/
@Test
void testAttributeNameEntityRoundTrip() {
final Map map = (Map) context.getValue("map");
map.put("Key'7", "quoted");
map.put("Key&apos;7", "entity");
map.put("Tom & Jerry", "cartoon");
assertXPathValueAndPointer(context, "map[@name = \"Key'7\"]", "quoted", "/map[@name='Key&apos;7']");
assertXPathValueAndPointer(context, "map[@name = 'Key&amp;apos;7']", "entity", "/map[@name='Key&amp;apos;7']");
// a lone ampersand needs no escaping
assertXPathValueAndPointer(context, "map[@name = 'Tom & Jerry']", "cartoon", "/map[@name='Tom & Jerry']");
assertEquals("quoted", context.getValue(context.getPointer("map[@name = \"Key'7\"]").asPath()));
assertEquals("entity", context.getValue(context.getPointer("map[@name = 'Key&amp;apos;7']").asPath()));
}

@Test
void testAxisChild() {
assertXPathValue(context, "map/Key1", "Value 1");
Expand Down