修复JSON解析栈溢出部分问题

This commit is contained in:
Looly 2022-11-29 23:11:36 +08:00
parent b0bfbd8ec3
commit d8283fedb0
7 changed files with 125 additions and 13 deletions

View File

@ -17,6 +17,7 @@
* 【core 】 修复ArrayUtil.insert()不支持原始类型数组的问题pr#874@Gitee * 【core 】 修复ArrayUtil.insert()不支持原始类型数组的问题pr#874@Gitee
* 【core 】 修复HexUtil.isHexNumber()判断逻辑超出long的精度问题issue#I62H7K@Gitee * 【core 】 修复HexUtil.isHexNumber()判断逻辑超出long的精度问题issue#I62H7K@Gitee
* 【core 】 修复BiMap中未重写computeIfAbsent和putIfAbsent导致双向查找出问题issue#I62X8O@Gitee * 【core 】 修复BiMap中未重写computeIfAbsent和putIfAbsent导致双向查找出问题issue#I62X8O@Gitee
* 【json 】 修复JSON解析栈溢出部分问题issue#2746@Github
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@ -44,19 +44,26 @@ public class JSONParser {
public void parseTo(JSONObject jsonObject, Filter<MutablePair<String, Object>> filter) { public void parseTo(JSONObject jsonObject, Filter<MutablePair<String, Object>> filter) {
final JSONTokener tokener = this.tokener; final JSONTokener tokener = this.tokener;
char c;
String key;
if (tokener.nextClean() != '{') { if (tokener.nextClean() != '{') {
throw tokener.syntaxError("A JSONObject text must begin with '{'"); throw tokener.syntaxError("A JSONObject text must begin with '{'");
} }
char prev;
char c;
String key;
while (true) { while (true) {
prev = tokener.getPrevious();
c = tokener.nextClean(); c = tokener.nextClean();
switch (c) { switch (c) {
case 0: case 0:
throw tokener.syntaxError("A JSONObject text must end with '}'"); throw tokener.syntaxError("A JSONObject text must end with '}'");
case '}': case '}':
return; return;
case '{':
case '[':
if (prev == '{') {
throw tokener.syntaxError("A JSONObject can not directly nest another JSONObject or JSONArray.");
}
default: default:
tokener.back(); tokener.back();
key = tokener.nextValue().toString(); key = tokener.nextValue().toString();

View File

@ -158,6 +158,15 @@ public class JSONTokener {
return this.previous; 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;
}
/** /**
* 读取下一个字符并比对是否和指定字符匹配 * 读取下一个字符并比对是否和指定字符匹配
* *
@ -329,10 +338,18 @@ public class JSONTokener {
return this.nextString(c); return this.nextString(c);
case '{': case '{':
this.back(); 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 '[': case '[':
this.back(); 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);
}
} }
/* /*
@ -341,7 +358,7 @@ public class JSONTokener {
* characters until we reach the end of the text or a formatting character. * 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) { while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
sb.append(c); sb.append(c);
c = this.next(); c = this.next();

View File

@ -41,7 +41,7 @@ public class XMLTokener extends JSONTokener {
public String nextCDATA() throws JSONException { public String nextCDATA() throws JSONException {
char c; char c;
int i; int i;
StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
for (; ; ) { for (; ; ) {
c = next(); c = next();
if (end()) { if (end()) {
@ -65,7 +65,7 @@ public class XMLTokener extends JSONTokener {
*/ */
public Object nextContent() throws JSONException { public Object nextContent() throws JSONException {
char c; char c;
StringBuilder sb; final StringBuilder sb;
do { do {
c = next(); c = next();
} while (Character.isWhitespace(c)); } while (Character.isWhitespace(c));
@ -98,9 +98,10 @@ public class XMLTokener extends JSONTokener {
* @throws JSONException If missing ';' in XML entity. * @throws JSONException If missing ';' in XML entity.
*/ */
public Object nextEntity(char ampersand) throws JSONException { public Object nextEntity(char ampersand) throws JSONException {
StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
char c;
for (; ; ) { for (; ; ) {
char c = next(); c = next();
if (Character.isLetterOrDigit(c) || c == '#') { if (Character.isLetterOrDigit(c) || c == '#') {
sb.append(Character.toLowerCase(c)); sb.append(Character.toLowerCase(c));
} else if (c == ';') { } else if (c == ';') {
@ -109,9 +110,38 @@ public class XMLTokener extends JSONTokener {
throw syntaxError("Missing ';' in XML entity: &" + sb); throw syntaxError("Missing ';' in XML entity: &" + sb);
} }
} }
String string = sb.toString(); return unescapeEntity(sb.toString());
Object object = entity.get(string); }
return object != null ? object : ampersand + string + ";";
/**
* 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();
} }
/** /**

View File

@ -1,3 +1,5 @@
package cn.hutool.json;
import cn.hutool.json.JSON; import cn.hutool.json.JSON;
import cn.hutool.json.JSONConfig; import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;

View File

@ -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);
}
}

View File

@ -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();
}
}