mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-05 17:37:59 +08:00
修复JSON解析栈溢出部分问题
This commit is contained in:
parent
b0bfbd8ec3
commit
d8283fedb0
@ -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)
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
23
hutool-json/src/test/java/cn/hutool/json/Issue2746Test.java
Executable file
23
hutool-json/src/test/java/cn/hutool/json/Issue2746Test.java
Executable 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);
|
||||||
|
}
|
||||||
|
}
|
32
hutool-json/src/test/java/cn/hutool/json/Issue2749Test.java
Executable file
32
hutool-json/src/test/java/cn/hutool/json/Issue2749Test.java
Executable 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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user