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) */