> predicate) {
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();
diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java
index a3ecf3c9f..5955f59ae 100755
--- a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java
+++ b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java
@@ -162,6 +162,15 @@ public class JSONTokener {
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;
+ }
+
/**
* 读取下一个字符,并比对是否和指定字符匹配
*
@@ -334,10 +343,18 @@ public class JSONTokener {
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);
+ }
}
/*
diff --git a/hutool-json/src/main/java/cn/hutool/json/xml/XMLTokener.java b/hutool-json/src/main/java/cn/hutool/json/xml/XMLTokener.java
index 548ac934f..4546432aa 100644
--- a/hutool-json/src/main/java/cn/hutool/json/xml/XMLTokener.java
+++ b/hutool-json/src/main/java/cn/hutool/json/xml/XMLTokener.java
@@ -113,9 +113,38 @@ public class XMLTokener extends JSONTokener {
throw syntaxError("Missing ';' in XML entity: &" + sb);
}
}
- final String string = sb.toString();
- final 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();
}
/**
diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2746Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2746Test.java
new file mode 100755
index 000000000..f01439f5d
--- /dev/null
+++ b/hutool-json/src/test/java/cn/hutool/json/Issue2746Test.java
@@ -0,0 +1,24 @@
+package cn.hutool.json;
+
+import cn.hutool.core.text.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);
+ }
+}
diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2749Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2749Test.java
new file mode 100755
index 000000000..98b9a9bfa
--- /dev/null
+++ b/hutool-json/src/test/java/cn/hutool/json/Issue2749Test.java
@@ -0,0 +1,37 @@
+package cn.hutool.json;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * https://github.com/dromara/hutool/issues/2749
+ *
+ * 由于使用了递归方式解析和写出,导致JSON太长的话容易栈溢出。
+ */
+public class Issue2749Test {
+
+ @Test
+ @Ignore
+ public void jsonObjectTest() {
+ final Map map = new HashMap<>(1, 1f);
+ Map node = map;
+ for (int i = 0; i < 1000; i++) {
+ //noinspection unchecked
+ node = (Map) node.computeIfAbsent("a", k -> new HashMap(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();
+ }
+}