From 45df318b5615421f191e5a1a8d3268264c80cf5c Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 8 Dec 2024 18:17:41 +0800 Subject: [PATCH] fix code --- .../dromara/hutool/core/text/CharUtil.java | 22 +++++ .../org/dromara/hutool/json/JSONConfig.java | 42 ++++++++ .../hutool/json/reader/JSONTokener.java | 97 ++++++++++++------- .../serializer/impl/ArrayTypeAdapter.java | 3 +- .../impl/CharSequenceTypeAdapter.java | 2 +- .../serializer/impl/ResourceSerializer.java | 2 +- .../serializer/impl/TokenerSerializer.java | 4 +- .../dromara/hutool/json/xml/XMLTokener.java | 2 +- .../dromara/hutool/json/JSONTokenerTest.java | 4 +- .../hutool/json/reader/Issue3808Test.java | 14 ++- .../hutool/json/reader/JSONParserTest.java | 6 +- hutool-json/src/test/resources/issue3808.json | 2 +- 12 files changed, 149 insertions(+), 51 deletions(-) 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 false},则转义,否则去除 + */ + private boolean ignoreZeroWithChar = true; + /** * 创建默认的配置项 * @@ -289,6 +301,36 @@ public class JSONConfig implements Serializable { return this; } + /** + * 是否忽略零宽字符,这些字符可能会导致解析安全问题,这些字符包括: + * + * @return 此值为{@code false},则转义,否则去除 + */ + public boolean isIgnoreZeroWithChar() { + return ignoreZeroWithChar; + } + + /** + * 设置是否忽略零宽字符,这些字符可能会导致解析安全问题,这些字符包括: + * + * @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, MatcherJ switch (bytes[0]) { case '{': case '[': - return context.getFactory().ofParser(new JSONTokener(IoUtil.toStream(bytes))).parse(); + return context.getFactory().ofParser( + new JSONTokener(IoUtil.toStream(bytes), context.config().isIgnoreZeroWithChar())).parse(); } } diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/CharSequenceTypeAdapter.java b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/CharSequenceTypeAdapter.java index 35035a689..017df856e 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/CharSequenceTypeAdapter.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/CharSequenceTypeAdapter.java @@ -85,7 +85,7 @@ public class CharSequenceTypeAdapter implements MatcherJSONSerializer { @Override public JSON serialize(final Resource bean, final JSONContext context) { - return context.getFactory().ofParser(new JSONTokener(bean.getStream())).parse(); + return context.getFactory().ofParser(new JSONTokener(bean.getStream(), context.config().isIgnoreZeroWithChar())).parse(); } /** diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/TokenerSerializer.java b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/TokenerSerializer.java index 499acdf5f..967a2252c 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/TokenerSerializer.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/serializer/impl/TokenerSerializer.java @@ -53,9 +53,9 @@ public class TokenerSerializer implements MatcherJSONSerializer { } else if (bean instanceof JSONParser) { return ((JSONParser) bean).parse(); } else if (bean instanceof Reader) { - return mapFromTokener(new JSONTokener((Reader) bean), context.getFactory()); + return mapFromTokener(new JSONTokener((Reader) bean, context.config().isIgnoreZeroWithChar()), context.getFactory()); } else if (bean instanceof InputStream) { - return mapFromTokener(new JSONTokener((InputStream) bean), context.getFactory()); + return mapFromTokener(new JSONTokener((InputStream) bean, context.config().isIgnoreZeroWithChar()), context.getFactory()); } throw new IllegalArgumentException("Unsupported source: " + bean); diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/xml/XMLTokener.java b/hutool-json/src/main/java/org/dromara/hutool/json/xml/XMLTokener.java index e5847c35b..82f730900 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/xml/XMLTokener.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/xml/XMLTokener.java @@ -49,7 +49,7 @@ public class XMLTokener extends JSONTokener { * @param s A source string. */ public XMLTokener(final CharSequence s) { - super(s); + super(s, true); } /** diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/JSONTokenerTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/JSONTokenerTest.java index 0b098d4b5..0cfbefadf 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/JSONTokenerTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/JSONTokenerTest.java @@ -34,7 +34,7 @@ public class JSONTokenerTest { @Test void nextTest() { - final JSONTokener jsonTokener = new JSONTokener("{\"ab\": \"abc\"}"); + final JSONTokener jsonTokener = new JSONTokener("{\"ab\": \"abc\"}", true); final char c = jsonTokener.nextTokenChar(); assertEquals('{', c); assertEquals("ab", jsonTokener.nextString()); @@ -51,7 +51,7 @@ public class JSONTokenerTest { */ @Test void nextWithoutWrapperTest() { - final JSONTokener jsonTokener = new JSONTokener("{ab: abc}"); + final JSONTokener jsonTokener = new JSONTokener("{ab: abc}", true); final char c = jsonTokener.nextTokenChar(); assertEquals('{', c); assertEquals("ab", jsonTokener.nextString()); diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/reader/Issue3808Test.java b/hutool-json/src/test/java/org/dromara/hutool/json/reader/Issue3808Test.java index f0b13282b..d21a027bb 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/reader/Issue3808Test.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/reader/Issue3808Test.java @@ -3,15 +3,23 @@ package org.dromara.hutool.json.reader; import org.dromara.hutool.core.io.resource.ResourceUtil; import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.json.JSON; +import org.dromara.hutool.json.JSONConfig; import org.dromara.hutool.json.JSONUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class Issue3808Test { @Test - void parseTest() { + void parseEscapeZeroWithCharTest() { final String str = ResourceUtil.readStr("issue3808.json", CharsetUtil.UTF_8); - final JSON parse = JSONUtil.parse(str); - Assertions.assertNotNull(parse); + final JSON parse = JSONUtil.parse(str, JSONConfig.of().setIgnoreZeroWithChar(false)); + Assertions.assertEquals("{\"recommend_text\":\"✅宁波,\\u200c一座历史悠久的文化名城\\n你好\",\",\\u200c一\":\"aaa\"}", parse.toString()); + } + + @Test + void parseIgnoreZeroWithCharTest() { + final String str = ResourceUtil.readStr("issue3808.json", CharsetUtil.UTF_8); + final JSON parse = JSONUtil.parse(str, JSONConfig.of().setIgnoreZeroWithChar(true)); + Assertions.assertEquals("{\"recommend_text\":\"✅宁波,一座历史悠久的文化名城\\n你好\",\",一\":\"aaa\"}", parse.toString()); } } diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/reader/JSONParserTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/reader/JSONParserTest.java index 43befaf73..6f2372bc0 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/reader/JSONParserTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/reader/JSONParserTest.java @@ -24,7 +24,7 @@ public class JSONParserTest { @Test void parseTest() { final String jsonStr = " {\"a\": 1} "; - final JSONParser jsonParser = JSONParser.of(new JSONTokener(jsonStr), JSONFactory.getInstance()); + final JSONParser jsonParser = JSONParser.of(new JSONTokener(jsonStr, true), JSONFactory.getInstance()); final JSON parse = jsonParser.parse(); Assertions.assertEquals("{\"a\":1}", parse.toString()); } @@ -34,14 +34,14 @@ public class JSONParserTest { final String jsonStr = "{\"a\": 1}"; final JSONObject jsonObject = JSONUtil.ofObj(); - JSONParser.of(new JSONTokener(jsonStr), JSONFactory.getInstance()).parseTo(jsonObject); + JSONParser.of(new JSONTokener(jsonStr, true), JSONFactory.getInstance()).parseTo(jsonObject); Assertions.assertEquals("{\"a\":1}", jsonObject.toString()); } @Test void parseToArrayTest() { final String jsonStr = "[{},2,3]"; - final JSONParser jsonParser = JSONParser.of(new JSONTokener(jsonStr), JSONFactory.getInstance()); + final JSONParser jsonParser = JSONParser.of(new JSONTokener(jsonStr, true), JSONFactory.getInstance()); final JSONArray jsonArray = new JSONArray(); jsonParser.parseTo(jsonArray); diff --git a/hutool-json/src/test/resources/issue3808.json b/hutool-json/src/test/resources/issue3808.json index 587d22d79..1549d4658 100644 --- a/hutool-json/src/test/resources/issue3808.json +++ b/hutool-json/src/test/resources/issue3808.json @@ -1 +1 @@ -{"recommend_text":"✅宁波,‌一座历史悠久的文化名城"} +{"recommend_text":"✅宁波,‌一座历史悠久的文化名城\n你好", ,‌一: "aaa"}