From 94854a1dfafdf7ecf168a60593240ecce13e7634 Mon Sep 17 00:00:00 2001 From: dong0713 <417851790@qq.com> Date: Mon, 20 Jul 2026 17:42:02 +0800 Subject: [PATCH] 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(" ") and XML.toJSONObject(" "), 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 --- src/main/java/org/json/XML.java | 10 ++++-- src/test/java/org/json/junit/XMLTest.java | 37 +++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index 32475876c..f5846cc1e 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -172,8 +172,14 @@ static boolean mustEscape(int cp) { && cp != 0xA && cp != 0xD ) || !( - // valid the range of acceptable characters that aren't control - (cp >= 0x20 && cp <= 0xD7FF) + // Valid character range per W3C XML 1.0 spec: + // #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + // Previously omitted #x9/#xA/#xD, causing unescape(" ") etc. + // to reject valid LF/TAB/CR as illegal (see #1059) + cp == 0x9 + || cp == 0xA + || cp == 0xD + || (cp >= 0x20 && cp <= 0xD7FF) || (cp >= 0xE000 && cp <= 0xFFFD) || (cp >= 0x10000 && cp <= 0x10FFFF) ) diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index 589536fd2..44fdcccb4 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -1537,6 +1537,43 @@ public void testValidUppercaseHexEntity() { assertEquals("A", jsonObject.getString("a")); } + /** + * Tests that valid XML numeric character references for whitespace + * control characters (TAB, LF, CR) are correctly unescaped. These + * codepoints are explicitly allowed by the XML 1.0 spec + * (https://www.w3.org/TR/REC-xml/#charsets) but were previously rejected + * as invalid. See issue #1059. + */ + @Test + public void testValidWhitespaceNumericEntityUnescape() { + // decimal references for the three allowed control characters + assertEquals("\t", XML.unescape(" ")); + assertEquals("\n", XML.unescape(" ")); + assertEquals("\r", XML.unescape(" ")); + // hex references for the same codepoints + assertEquals("\t", XML.unescape(" ")); + assertEquals("\n", XML.unescape(" ")); + assertEquals("\r", XML.unescape(" ")); + } + + /** + * Tests that {@code XML.toJSONObject} accepts numeric character references + * for the XML-allowed control characters (TAB, LF, CR) without throwing. + * Regression test for #1059, where {@code XML.toJSONObject(" ")} + * threw JSONException in versions after 20251224. + */ + @Test + public void testValidWhitespaceNumericEntityToJSONObject() { + // LF reference should round-trip through toJSONObject without throwing + JSONObject jsonObject = XML.toJSONObject(" "); + // the value is the LF character (possibly trimmed by the JSON path, + // but the call must not throw) + assertTrue(jsonObject.has("a")); + // TAB and CR references also accepted + XML.toJSONObject(" "); + XML.toJSONObject(" "); + } + }