Skip to content

Commit 94854a1

Browse files
committed
Fix XML.unescape rejecting valid whitespace numeric character references
XML.mustEscape() is shared by both XML.escape() (serialization) and XMLTokener.unescapeEntity() (deserialization). While the method's Javadoc and comment quote the W3C XML 1.0 valid-character range (#x9 | #xA | #xD | [#x20-#xD7FF] | ...), the implementation only checked [#x20-#xD7FF] in its range clause, omitting #x9/#xA/#xD. Although the ISO-control clause excluded those three codepoints, the negated range clause still marked them as 'must escape', so unescapeEntity() threw JSONException for the XML-allowed control characters TAB, LF and CR. This broke XML.unescape("&#10;") and XML.toJSONObject("<a>&#10;</a>"), both of which worked in v20251224 and regressed after #1045 (v20260522). Align the range clause with the W3C spec by explicitly allowing #x9, #xA and #xD, matching the comment that was already documented. Fixes #1059
1 parent 1795e8c commit 94854a1

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

src/main/java/org/json/XML.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,14 @@ static boolean mustEscape(int cp) {
172172
&& cp != 0xA
173173
&& cp != 0xD
174174
) || !(
175-
// valid the range of acceptable characters that aren't control
176-
(cp >= 0x20 && cp <= 0xD7FF)
175+
// Valid character range per W3C XML 1.0 spec:
176+
// #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
177+
// Previously omitted #x9/#xA/#xD, causing unescape("&#10;") etc.
178+
// to reject valid LF/TAB/CR as illegal (see #1059)
179+
cp == 0x9
180+
|| cp == 0xA
181+
|| cp == 0xD
182+
|| (cp >= 0x20 && cp <= 0xD7FF)
177183
|| (cp >= 0xE000 && cp <= 0xFFFD)
178184
|| (cp >= 0x10000 && cp <= 0x10FFFF)
179185
)

src/test/java/org/json/junit/XMLTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,6 +1537,43 @@ public void testValidUppercaseHexEntity() {
15371537
assertEquals("A", jsonObject.getString("a"));
15381538
}
15391539

1540+
/**
1541+
* Tests that valid XML numeric character references for whitespace
1542+
* control characters (TAB, LF, CR) are correctly unescaped. These
1543+
* codepoints are explicitly allowed by the XML 1.0 spec
1544+
* (https://www.w3.org/TR/REC-xml/#charsets) but were previously rejected
1545+
* as invalid. See issue #1059.
1546+
*/
1547+
@Test
1548+
public void testValidWhitespaceNumericEntityUnescape() {
1549+
// decimal references for the three allowed control characters
1550+
assertEquals("\t", XML.unescape("&#9;"));
1551+
assertEquals("\n", XML.unescape("&#10;"));
1552+
assertEquals("\r", XML.unescape("&#13;"));
1553+
// hex references for the same codepoints
1554+
assertEquals("\t", XML.unescape("&#x9;"));
1555+
assertEquals("\n", XML.unescape("&#xA;"));
1556+
assertEquals("\r", XML.unescape("&#xD;"));
1557+
}
1558+
1559+
/**
1560+
* Tests that {@code XML.toJSONObject} accepts numeric character references
1561+
* for the XML-allowed control characters (TAB, LF, CR) without throwing.
1562+
* Regression test for #1059, where {@code XML.toJSONObject("<a>&#10;</a>")}
1563+
* threw JSONException in versions after 20251224.
1564+
*/
1565+
@Test
1566+
public void testValidWhitespaceNumericEntityToJSONObject() {
1567+
// LF reference should round-trip through toJSONObject without throwing
1568+
JSONObject jsonObject = XML.toJSONObject("<a>&#10;</a>");
1569+
// the value is the LF character (possibly trimmed by the JSON path,
1570+
// but the call must not throw)
1571+
assertTrue(jsonObject.has("a"));
1572+
// TAB and CR references also accepted
1573+
XML.toJSONObject("<a>&#9;</a>");
1574+
XML.toJSONObject("<a>&#13;</a>");
1575+
}
1576+
15401577
}
15411578

15421579

0 commit comments

Comments
 (0)