Skip to content

Commit

Permalink
修复JSON解析栈溢出部分问题
Browse files Browse the repository at this point in the history
  • Loading branch information
looly committed Nov 29, 2022
1 parent b0bfbd8 commit d8283fe
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* 【core 】 修复ArrayUtil.insert()不支持原始类型数组的问题(pr#874@Gitee)
* 【core 】 修复HexUtil.isHexNumber()判断逻辑超出long的精度问题(issue#I62H7K@Gitee)
* 【core 】 修复BiMap中未重写computeIfAbsent和putIfAbsent导致双向查找出问题(issue#I62X8O@Gitee)
* 【json 】 修复JSON解析栈溢出部分问题(issue#2746@Github)

-------------------------------------------------------------------------------------------------------------

Expand Down
13 changes: 10 additions & 3 deletions hutool-json/src/main/java/cn/hutool/json/JSONParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,26 @@ public JSONParser(JSONTokener tokener) {
public void parseTo(JSONObject jsonObject, Filter<MutablePair<String, Object>> filter) {
final JSONTokener tokener = this.tokener;

char c;
String key;

if (tokener.nextClean() != '{') {
throw tokener.syntaxError("A JSONObject text must begin with '{'");
}

char prev;
char c;
String key;
while (true) {
prev = tokener.getPrevious();
c = tokener.nextClean();
switch (c) {
case 0:
throw tokener.syntaxError("A JSONObject text must end with '}'");
case '}':
return;
case '{':
case '[':
if (prev == '{') {
throw tokener.syntaxError("A JSONObject can not directly nest another JSONObject or JSONArray.");
}
default:
tokener.back();
key = tokener.nextValue().toString();
Expand Down
23 changes: 20 additions & 3 deletions hutool-json/src/main/java/cn/hutool/json/JSONTokener.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,15 @@ public char next() throws JSONException {
return this.previous;
}

/**
* Get the last character read from the input or '\0' if nothing has been read yet.
*
* @return the last character read from the input.
*/
protected char getPrevious() {
return this.previous;
}

/**
* 读取下一个字符,并比对是否和指定字符匹配
*
Expand Down Expand Up @@ -329,10 +338,18 @@ public Object nextValue() throws JSONException {
return this.nextString(c);
case '{':
this.back();
return new JSONObject(this, this.config);
try {
return new JSONObject(this, this.config);
} catch (final StackOverflowError e) {
throw new JSONException("JSONObject depth too large to process.", e);
}
case '[':
this.back();
return new JSONArray(this, this.config);
try {
return new JSONArray(this, this.config);
} catch (final StackOverflowError e) {
throw new JSONException("JSONArray depth too large to process.", e);
}
}

/*
Expand All @@ -341,7 +358,7 @@ public Object nextValue() throws JSONException {
* characters until we reach the end of the text or a formatting character.
*/

StringBuilder sb = new StringBuilder();
final StringBuilder sb = new StringBuilder();
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
sb.append(c);
c = this.next();
Expand Down
44 changes: 37 additions & 7 deletions hutool-json/src/main/java/cn/hutool/json/XMLTokener.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public XMLTokener(CharSequence s, JSONConfig config) {
public String nextCDATA() throws JSONException {
char c;
int i;
StringBuilder sb = new StringBuilder();
final StringBuilder sb = new StringBuilder();
for (; ; ) {
c = next();
if (end()) {
Expand All @@ -65,7 +65,7 @@ public String nextCDATA() throws JSONException {
*/
public Object nextContent() throws JSONException {
char c;
StringBuilder sb;
final StringBuilder sb;
do {
c = next();
} while (Character.isWhitespace(c));
Expand Down Expand Up @@ -98,9 +98,10 @@ public Object nextContent() throws JSONException {
* @throws JSONException If missing ';' in XML entity.
*/
public Object nextEntity(char ampersand) throws JSONException {
StringBuilder sb = new StringBuilder();
final StringBuilder sb = new StringBuilder();
char c;
for (; ; ) {
char c = next();
c = next();
if (Character.isLetterOrDigit(c) || c == '#') {
sb.append(Character.toLowerCase(c));
} else if (c == ';') {
Expand All @@ -109,9 +110,38 @@ public Object nextEntity(char ampersand) throws JSONException {
throw syntaxError("Missing ';' in XML entity: &" + sb);
}
}
String string = sb.toString();
Object object = entity.get(string);
return object != null ? object : ampersand + string + ";";
return unescapeEntity(sb.toString());
}

/**
* Unescape an XML entity encoding;
*
* @param e entity (only the actual entity value, not the preceding & or ending ;
* @return Unescape str
*/
static String unescapeEntity(final String e) {
// validate
if (e == null || e.isEmpty()) {
return "";
}
// if our entity is an encoded unicode point, parse it.
if (e.charAt(0) == '#') {
final int cp;
if (e.charAt(1) == 'x' || e.charAt(1) == 'X') {
// hex encoded unicode
cp = Integer.parseInt(e.substring(2), 16);
} else {
// decimal encoded unicode
cp = Integer.parseInt(e.substring(1));
}
return new String(new int[]{cp}, 0, 1);
}
final Character knownEntity = entity.get(e);
if (knownEntity == null) {
// we don't know the entity so keep it encoded
return '&' + e + ';';
}
return knownEntity.toString();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package cn.hutool.json;

import cn.hutool.json.JSON;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
Expand Down
23 changes: 23 additions & 0 deletions hutool-json/src/test/java/cn/hutool/json/Issue2746Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package cn.hutool.json;

import cn.hutool.core.util.StrUtil;
import org.junit.Assert;
import org.junit.Test;

public class Issue2746Test {
@Test
public void parseObjTest() {
final String str = StrUtil.repeat("{", 1500) + StrUtil.repeat("}", 1500);
try{
JSONUtil.parseObj(str);
} catch (final JSONException e){
Assert.assertTrue(e.getMessage().startsWith("A JSONObject can not directly nest another JSONObject or JSONArray"));
}
}

@Test(expected = JSONException.class)
public void parseTest() {
final String str = StrUtil.repeat("[", 1500) + StrUtil.repeat("]", 1500);
JSONUtil.parseArray(str);
}
}
32 changes: 32 additions & 0 deletions hutool-json/src/test/java/cn/hutool/json/Issue2749Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cn.hutool.json;

import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

public class Issue2749Test {

@Test
@Ignore
public void jsonObjectTest() {
final Map<String, Object> map = new HashMap<>(1, 1f);
Map<String, Object> node = map;
for (int i = 0; i < 1000; i++) {
//noinspection unchecked
node = (Map<String, Object>) node.computeIfAbsent("a", k -> new HashMap<String, Object>(1, 1f));
}
node.put("a", 1);
final String jsonStr = JSONUtil.toJsonStr(map);

@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
final JSONObject jsonObject = new JSONObject(jsonStr);
Assert.assertNotNull(jsonObject);

// 栈溢出
//noinspection ResultOfMethodCallIgnored
jsonObject.toString();
}
}

0 comments on commit d8283fe

Please sign in to comment.