diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/CharUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/CharUtil.java
index 9d081a5a4..be612e6f6 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/text/CharUtil.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/CharUtil.java
@@ -312,6 +312,28 @@ public class CharUtil implements CharPool {
return SLASH == c || BACKSLASH == c;
}
+ /**
+ * 是否为零宽字符
+ *
+ * @param c 字符
+ * @return 是否为零宽字符
+ */
+ public static boolean isZeroWidthChar(final char c) {
+ switch (c) {
+ case '\u200B': // 零宽空格
+ case '\u200C': // 零宽非换行空格
+ case '\u200D': // 零宽连接符
+ case '\uFEFF': // 零宽无断空格
+ case '\u2060': // 零宽连字符
+ case '\u2063': // 零宽不连字符
+ case '\u2064': // 零宽连字符
+ case '\u2065': // 零宽不连字符
+ return true;
+ default:
+ return false;
+ }
+ }
+
/**
* 比较两个字符是否相同
*
diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/JSONConfig.java b/hutool-json/src/main/java/org/dromara/hutool/json/JSONConfig.java
index bfd2a2318..04f7b1a79 100644
--- a/hutool-json/src/main/java/org/dromara/hutool/json/JSONConfig.java
+++ b/hutool-json/src/main/java/org/dromara/hutool/json/JSONConfig.java
@@ -73,6 +73,18 @@ public class JSONConfig implements Serializable {
*/
private NumberWriteMode numberWriteMode = NumberWriteMode.NORMAL;
+ /**
+ * 是否忽略零宽字符,这些字符可能会导致解析安全问题,这些字符包括:
+ *
+ * - 零宽空格:{@code \u200B}
+ * - 零宽非换行空:{@code \u200C}
+ * - 零宽连接符:{@code \u200D}
+ * - 零宽无断空格:{@code \uFEFF}
+ *
+ * 如果此值为{@code false},则转义,否则去除
+ */
+ private boolean ignoreZeroWithChar = true;
+
/**
* 创建默认的配置项
*
@@ -289,6 +301,36 @@ public class JSONConfig implements Serializable {
return this;
}
+ /**
+ * 是否忽略零宽字符,这些字符可能会导致解析安全问题,这些字符包括:
+ *
+ * - 零宽空格:{@code \u200B}
+ * - 零宽非换行空:{@code \u200C}
+ * - 零宽连接符:{@code \u200D}
+ * - 零宽无断空格:{@code \uFEFF}
+ *
+ * @return 此值为{@code false},则转义,否则去除
+ */
+ public boolean isIgnoreZeroWithChar() {
+ return ignoreZeroWithChar;
+ }
+
+ /**
+ * 设置是否忽略零宽字符,这些字符可能会导致解析安全问题,这些字符包括:
+ *
+ * - 零宽空格:{@code \u200B}
+ * - 零宽非换行空:{@code \u200C}
+ * - 零宽连接符:{@code \u200D}
+ * - 零宽无断空格:{@code \uFEFF}
+ *
+ * @param ignoreZeroWithChar 此值为{@code false},则转义,否则去除
+ * @return this
+ */
+ public JSONConfig setIgnoreZeroWithChar(final boolean ignoreZeroWithChar) {
+ this.ignoreZeroWithChar = ignoreZeroWithChar;
+ return this;
+ }
+
/**
* 重复key或重复对象处理方式
* 只针对{@link JSONObject},检查在put时key的重复情况
diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/reader/JSONTokener.java b/hutool-json/src/main/java/org/dromara/hutool/json/reader/JSONTokener.java
index 53d513e47..b91da2ad0 100644
--- a/hutool-json/src/main/java/org/dromara/hutool/json/reader/JSONTokener.java
+++ b/hutool-json/src/main/java/org/dromara/hutool/json/reader/JSONTokener.java
@@ -47,6 +47,9 @@ public class JSONTokener extends ReaderWrapper {
*/
public static final int EOF = 0;
+ /**
+ * 当前字符
+ */
private long character;
/**
* 是否结尾 End of stream
@@ -68,6 +71,7 @@ public class JSONTokener extends ReaderWrapper {
* 是否使用前一个字符
*/
private boolean usePrevious;
+ private boolean ignoreZeroWithChar;
// ------------------------------------------------------------------------------------ Constructor start
@@ -75,27 +79,30 @@ public class JSONTokener extends ReaderWrapper {
* 从InputStream中构建,使用UTF-8编码
*
* @param inputStream InputStream
+ * @param ignoreZeroWithChar 是否忽略零宽字符
* @throws JSONException JSON异常,包装IO异常
*/
- public JSONTokener(final InputStream inputStream) throws JSONException {
- this(IoUtil.toUtf8Reader(inputStream));
+ public JSONTokener(final InputStream inputStream, final boolean ignoreZeroWithChar) throws JSONException {
+ this(IoUtil.toUtf8Reader(inputStream), ignoreZeroWithChar);
}
/**
* 从字符串中构建
*
- * @param s JSON字符串
+ * @param s JSON字符串
+ * @param ignoreZeroWithChar 是否忽略零宽字符
*/
- public JSONTokener(final CharSequence s) {
- this(new StringReader(Assert.notBlank(s).toString()));
+ public JSONTokener(final CharSequence s, final boolean ignoreZeroWithChar) {
+ this(new StringReader(Assert.notBlank(s).toString()), ignoreZeroWithChar);
}
/**
* 从Reader中构建
*
- * @param reader Reader
+ * @param reader Reader
+ * @param ignoreZeroWithChar 是否忽略零宽字符
*/
- public JSONTokener(final Reader reader) {
+ public JSONTokener(final Reader reader, final boolean ignoreZeroWithChar) {
super(IoUtil.toMarkSupport(Assert.notNull(reader)));
this.eof = false;
this.usePrevious = false;
@@ -103,6 +110,7 @@ public class JSONTokener extends ReaderWrapper {
this.index = 0;
this.character = 1;
this.line = 1;
+ this.ignoreZeroWithChar = ignoreZeroWithChar;
}
// ------------------------------------------------------------------------------------ Constructor end
@@ -132,8 +140,8 @@ public class JSONTokener extends ReaderWrapper {
* 检查是否到了结尾
* 如果读取完毕后还有未读的字符,报错
*/
- public void checkEnd(){
- if(EOF != nextClean()){
+ public void checkEnd() {
+ if (EOF != nextClean()) {
throw syntaxError("Invalid JSON, Unread data after end.");
}
}
@@ -160,34 +168,14 @@ public class JSONTokener extends ReaderWrapper {
* @throws JSONException JSON异常,包装IO异常
*/
public char next() throws JSONException {
- int c;
- if (this.usePrevious) {
- this.usePrevious = false;
- c = this.previous;
- } else {
- try {
- c = read();
- } catch (final IOException exception) {
- throw new JSONException(exception);
- }
-
- if (c <= EOF) { // End of stream
- this.eof = true;
- c = EOF;
+ char c;
+ while(true){
+ c = _next();
+ if(this.ignoreZeroWithChar && CharUtil.isZeroWidthChar(c)){
+ continue;
}
+ return c;
}
- this.index += 1;
- if (this.previous == '\r') {
- this.line += 1;
- this.character = c == '\n' ? 0 : 1;
- } else if (c == '\n') {
- this.line += 1;
- this.character = 0;
- } else {
- this.character += 1;
- }
- this.previous = (char) c;
- return this.previous;
}
/**
@@ -291,8 +279,8 @@ public class JSONTokener extends ReaderWrapper {
/**
* 获取下一个冒号,非冒号则抛出异常
*
- * @throws JSONException 非冒号字符
* @return 冒号字符
+ * @throws JSONException 非冒号字符
*/
public char nextColon() throws JSONException {
final char c = nextClean();
@@ -413,6 +401,43 @@ public class JSONTokener extends ReaderWrapper {
return " at " + this.index + " [character " + this.character + " line " + this.line + "]";
}
+ /**
+ * 获得源字符串中的下一个字符
+ *
+ * @return 下一个字符, or 0 if past the end of the source string.
+ * @throws JSONException JSON异常,包装IO异常
+ */
+ private char _next() throws JSONException {
+ int c;
+ if (this.usePrevious) {
+ this.usePrevious = false;
+ c = this.previous;
+ } else {
+ try {
+ c = read();
+ } catch (final IOException exception) {
+ throw new JSONException(exception);
+ }
+
+ if (c <= EOF) { // End of stream
+ this.eof = true;
+ c = EOF;
+ }
+ }
+ this.index += 1;
+ if (this.previous == '\r') {
+ this.line += 1;
+ this.character = c == '\n' ? 0 : 1;
+ } else if (c == '\n') {
+ this.line += 1;
+ this.character = 0;
+ } else {
+ this.character += 1;
+ }
+ this.previous = (char) c;
+ return this.previous;
+ }
+
/**
* 获取反转义的字符
*
diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/ArrayTypeAdapter.java b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/ArrayTypeAdapter.java
index c481a5d78..4ae701655 100644
--- a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/ArrayTypeAdapter.java
+++ b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/ArrayTypeAdapter.java
@@ -101,7 +101,8 @@ public class ArrayTypeAdapter implements MatcherJSONSerializer