From 33e9634b5f8385bf6e1930d2a9de972211cc1239 Mon Sep 17 00:00:00 2001
From: simpleqt <2015900818@qq.com>
Date: Thu, 5 Nov 2026 00:58:42 +0800
Subject: [PATCH] JSONPointer: reject array index tokens that RFC 6901 does not
allow
JSONPointer.readByIndexToken resolved array elements with
Integer.parseInt(indexToken), so tokens which are not array indexes per
RFC 6901 section 4 (array-index = %x30 / ( %x31-39 *(%x30-39) )) were
accepted whenever Java's number parsing accepted them.
On a JSONArray, "/01" therefore returned element 1 and "/00" returned
element 0 instead of failing, i.e. a pointer that does not address the
element it names silently selected a different one. The token "+1" was
accepted as well, and a token longer than Integer.MAX_VALUE reported
"2147483648 is not an array index" for what is really an out of range
index.
Parse the token only when it matches the grammar, in a new toArrayIndex()
helper used by readByIndexToken(). Valid indexes such as "/0" and "/1"
behave as before, out of range indexes keep their existing message, and
non-conforming tokens now raise a JSONPointerException. Member names of a
JSONObject are unaffected: they are not array indexes, so "/01" still
addresses the member named "01".
This matches Jackson's JsonPointer (com.fasterxml.jackson.core) and its
JsonPointer._parseIndex(), which rejects exactly the same tokens.
---
src/main/java/org/json/JSONPointer.java | 48 +++++++++++++++----
.../java/org/json/junit/JSONPointerTest.java | 26 ++++++++++
2 files changed, 64 insertions(+), 10 deletions(-)
diff --git a/src/main/java/org/json/JSONPointer.java b/src/main/java/org/json/JSONPointer.java
index a38043d7b..13af84e78 100644
--- a/src/main/java/org/json/JSONPointer.java
+++ b/src/main/java/org/json/JSONPointer.java
@@ -224,18 +224,46 @@ public Object queryFrom(Object document) throws JSONPointerException {
* @throws JSONPointerException is thrown if the index is out of bounds
*/
private static Object readByIndexToken(Object current, String indexToken) throws JSONPointerException {
+ int index = toArrayIndex(indexToken);
+ JSONArray currentArr = (JSONArray) current;
+ if (index >= currentArr.length()) {
+ throw new JSONPointerException(format("index %s is out of bounds - the array has %d elements", indexToken,
+ Integer.valueOf(currentArr.length())));
+ }
try {
- int index = Integer.parseInt(indexToken);
- JSONArray currentArr = (JSONArray) current;
- if (index >= currentArr.length()) {
- throw new JSONPointerException(format("index %s is out of bounds - the array has %d elements", indexToken,
- Integer.valueOf(currentArr.length())));
+ return currentArr.get(index);
+ } catch (JSONException e) {
+ throw new JSONPointerException("Error reading value at index position " + index, e);
+ }
+ }
+
+ /**
+ * Converts an array index token to its ordinal position. RFC 6901 section 4 restricts an array
+ * index to {@code "0"} or to a digit string which does not start with a zero:
+ * {@code array-index = %x30 / ( %x31-39 *(%x30-39) )}. Number spellings which Java accepts but
+ * the grammar does not, such as {@code "01"}, {@code "+1"} or {@code "-0"}, are rejected so that
+ * they cannot be read as a different element than the one that was asked for.
+ *
+ * @param indexToken the array index in string form
+ * @return the ordinal position the token denotes
+ * @throws JSONPointerException if the token is not a valid array index
+ * @see rfc6901 section 4
+ */
+ private static int toArrayIndex(String indexToken) throws JSONPointerException {
+ char first = indexToken.charAt(0);
+ if (first != '0' && (first < '1' || first > '9')) {
+ throw new JSONPointerException(format("%s is not an array index", indexToken));
+ }
+ if (first == '0' && indexToken.length() > 1) {
+ throw new JSONPointerException(format("%s is not an array index", indexToken));
+ }
+ for (int i = 1; i < indexToken.length(); i++) {
+ if (indexToken.charAt(i) < '0' || indexToken.charAt(i) > '9') {
+ throw new JSONPointerException(format("%s is not an array index", indexToken));
}
- try {
- return currentArr.get(index);
- } catch (JSONException e) {
- throw new JSONPointerException("Error reading value at index position " + index, e);
- }
+ }
+ try {
+ return Integer.parseInt(indexToken);
} catch (NumberFormatException e) {
throw new JSONPointerException(format("%s is not an array index", indexToken), e);
}
diff --git a/src/test/java/org/json/junit/JSONPointerTest.java b/src/test/java/org/json/junit/JSONPointerTest.java
index 3d70e23c0..7db585069 100644
--- a/src/test/java/org/json/junit/JSONPointerTest.java
+++ b/src/test/java/org/json/junit/JSONPointerTest.java
@@ -306,6 +306,32 @@ public void tokenListIsCopiedInConstructor() {
}
}
+ /**
+ * RFC 6901 section 4 restricts an array index to "0", or to a digit string that does not
+ * start with a zero: {@code array-index = %x30 / ( %x31-39 *(%x30-39) )}. Tokens such as
+ * {@code "01"} and {@code "+1"} are therefore not array indexes, and navigating into an
+ * array with them must fail instead of silently resolving to a different element.
+ *
+ * @see rfc6901 section 4
+ */
+ @Test
+ public void arrayIndexTokenMustMatchRfc6901Grammar() {
+ JSONArray array = new JSONArray("[10,20,30,40,50,60]");
+ assertEquals(10, array.query("/0"));
+ assertEquals(20, array.query("/1"));
+ for (String token : new String[] { "01", "00", "+1", "-0", "1.0", "(1)", "0000000000000000001" }) {
+ try {
+ Object value = array.query("/" + token);
+ fail("Expected JSONPointerException for array index token '" + token + "', got " + value);
+ } catch (JSONPointerException e) {
+ assertTrue("Expected index message for token '" + token + "', got: " + e.getMessage(),
+ e.getMessage().contains("is not an array index"));
+ }
+ }
+ // the same rule applies to JSONArray.optQuery, which reports a failed query as null
+ assertTrue("Expected null", array.optQuery("/01") == null);
+ }
+
/**
* Coverage for JSONObject query(String)
*/