This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/main by this push:
new bc86f631281 Improve JSON key parsing (#3141)
bc86f631281 is described below
commit bc86f631281edb356e1905b1398fee60a0f70196
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Mon May 25 15:50:03 2026 +0100
Improve JSON key parsing (#3141)
---
.../json/basic/JsonMapObjectReaderWriter.java | 46 +++++++++++++++++++++-
.../json/basic/JsonMapObjectReaderWriterTest.java | 27 +++++++++++++
2 files changed, 71 insertions(+), 2 deletions(-)
diff --git
a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
index f6c8abe4145..bbd6852302f 100644
---
a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
+++
b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java
@@ -190,9 +190,10 @@ public class JsonMapObjectReaderWriter {
continue;
}
- int closingQuote = json.indexOf(DQUOTE, i + 1);
+ int closingQuote = json.charAt(i) == DQUOTE
+ ? findClosingQuote(json, i) : json.indexOf(DQUOTE, i + 1);
int from = json.charAt(i) == DQUOTE ? i + 1 : i;
- String name = json.substring(from, closingQuote);
+ String name = unescapeKeyName(json.substring(from, closingQuote));
int sepIndex = json.indexOf(COLON, closingQuote + 1);
if (sepIndex == -1) {
throw new UncheckedIOException(new IOException("Error in
parsing json"));
@@ -398,6 +399,47 @@ public class JsonMapObjectReaderWriter {
}
+ /**
+ * Returns the index of the closing {@code "} that matches the opening
quote at
+ * {@code openQuoteIndex}, correctly skipping over escaped quotes ({@code
\"}) and
+ * escaped backslashes ({@code \\}) inside the string by counting
consecutive
+ * backslashes immediately before each candidate {@code "}: an odd count
means the
+ * quote is escaped; an even count means it is a real string delimiter.
+ */
+ private static int findClosingQuote(String json, int openQuoteIndex) {
+ for (int i = openQuoteIndex + 1; i < json.length(); i++) {
+ if (json.charAt(i) == DQUOTE) {
+ int backslashCount = 0;
+ int k = i - 1;
+ while (k > openQuoteIndex && json.charAt(k) == ESCAPE) {
+ backslashCount++;
+ k--;
+ }
+ if (backslashCount % 2 == 0) {
+ return i;
+ }
+ }
+ }
+ return json.length(); // malformed — treat end-of-string as sentinel
+ }
+
+ /**
+ * Decodes the JSON escape sequences that may appear in a key name:
+ * {@code \/} → {@code /}, {@code \"} → {@code "}, {@code \\} → {@code \}.
+ */
+ private static String unescapeKeyName(String name) {
+ if (name.contains("\\/")) {
+ name = name.replace("\\/", "/");
+ }
+ if (name.contains("\\\"")) {
+ name = name.replace("\\\"", "\"");
+ }
+ if (name.contains("\\\\")) {
+ name = name.replace("\\\\", "\\");
+ }
+ return name;
+ }
+
private String escapeJson(String value) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < value.length(); i++) {
diff --git
a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
index c2908b1a75d..a680ecba04d 100644
---
a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
+++
b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java
@@ -239,6 +239,33 @@ public class JsonMapObjectReaderWriterTest {
assertEquals(Boolean.TRUE, map.get("admin"));
}
+ /**
+ * Regression test for "Key Names With Escaped Quotes Parsed Incorrectly".
+ *
+ * <p>{@code readJsonObjectAsSettable} extracts a key name with a plain
+ * {@code json.indexOf(DQUOTE, i + 1)}, which stops at the first {@code "}
it finds
+ * regardless of whether that quote is escaped. A key that contains an
embedded
+ * escaped quote — e.g. {@code "foo\"bar"} — is therefore truncated: the
method
+ * finds the {@code "} in {@code \"} and returns {@code foo\} instead of
+ * {@code foo"bar}.
+ *
+ * <p>The parser then searches for the value separator {@code :} starting
from the
+ * wrong offset, so the remainder of the key ({@code bar}) and the colon
are
+ * consumed as a suffix of the (wrong) key name. The resulting map
contains an
+ * entry with the wrong key and the test assertion on {@code
map.get("foo\"bar")}
+ * returns {@code null}.
+ */
+ @Test
+ public void testKeyWithEscapedQuoteIsParsedCorrectly() throws Exception {
+ // JSON: {"foo\"bar":"value"} — key contains an embedded double-quote
character
+ // Bug: indexOf('"') stops at the \" inside the key, producing
truncated key "foo\"
+ // instead of the correct key foo"bar.
+ String json = "{\"foo\\\"bar\":\"value\"}";
+ Map<String, Object> map = new
JsonMapObjectReaderWriter().fromJson(json);
+ assertEquals(1, map.size());
+ assertEquals("value", map.get("foo\"bar"));
+ }
+
@Test
public void testAlreadyEscapedBackslash() throws Exception {
JsonMapObjectReaderWriter jsonMapObjectReaderWriter = new
JsonMapObjectReaderWriter();