mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
fix code
This commit is contained in:
parent
15c1237ecc
commit
45df318b56
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个字符是否相同
|
||||
*
|
||||
|
@ -73,6 +73,18 @@ public class JSONConfig implements Serializable {
|
||||
*/
|
||||
private NumberWriteMode numberWriteMode = NumberWriteMode.NORMAL;
|
||||
|
||||
/**
|
||||
* 是否忽略零宽字符,这些字符可能会导致解析安全问题,这些字符包括:
|
||||
* <ul>
|
||||
* <li>零宽空格:{@code \u200B}</li>
|
||||
* <li>零宽非换行空:{@code \u200C}</li>
|
||||
* <li>零宽连接符:{@code \u200D}</li>
|
||||
* <li>零宽无断空格:{@code \uFEFF}</li>
|
||||
* </ul>
|
||||
* 如果此值为{@code false},则转义,否则去除
|
||||
*/
|
||||
private boolean ignoreZeroWithChar = true;
|
||||
|
||||
/**
|
||||
* 创建默认的配置项
|
||||
*
|
||||
@ -289,6 +301,36 @@ public class JSONConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否忽略零宽字符,这些字符可能会导致解析安全问题,这些字符包括:
|
||||
* <ul>
|
||||
* <li>零宽空格:{@code \u200B}</li>
|
||||
* <li>零宽非换行空:{@code \u200C}</li>
|
||||
* <li>零宽连接符:{@code \u200D}</li>
|
||||
* <li>零宽无断空格:{@code \uFEFF}</li>
|
||||
* </ul>
|
||||
* @return 此值为{@code false},则转义,否则去除
|
||||
*/
|
||||
public boolean isIgnoreZeroWithChar() {
|
||||
return ignoreZeroWithChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否忽略零宽字符,这些字符可能会导致解析安全问题,这些字符包括:
|
||||
* <ul>
|
||||
* <li>零宽空格:{@code \u200B}</li>
|
||||
* <li>零宽非换行空:{@code \u200C}</li>
|
||||
* <li>零宽连接符:{@code \u200D}</li>
|
||||
* <li>零宽无断空格:{@code \uFEFF}</li>
|
||||
* </ul>
|
||||
* @param ignoreZeroWithChar 此值为{@code false},则转义,否则去除
|
||||
* @return this
|
||||
*/
|
||||
public JSONConfig setIgnoreZeroWithChar(final boolean ignoreZeroWithChar) {
|
||||
this.ignoreZeroWithChar = ignoreZeroWithChar;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重复key或重复对象处理方式<br>
|
||||
* 只针对{@link JSONObject},检查在put时key的重复情况
|
||||
|
@ -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 {
|
||||
* 检查是否到了结尾<br>
|
||||
* 如果读取完毕后还有未读的字符,报错
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取反转义的字符
|
||||
*
|
||||
|
@ -101,7 +101,8 @@ public class ArrayTypeAdapter implements MatcherJSONSerializer<Object>, 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ public class CharSequenceTypeAdapter implements MatcherJSONSerializer<CharSequen
|
||||
}
|
||||
|
||||
// 按照JSON字符串解析
|
||||
return context.getFactory().ofParser(new JSONTokener(jsonStr)).parse();
|
||||
return context.getFactory().ofParser(new JSONTokener(jsonStr, context.config().isIgnoreZeroWithChar())).parse();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -44,7 +44,7 @@ public class ResourceSerializer implements MatcherJSONSerializer<Resource> {
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,9 +53,9 @@ public class TokenerSerializer implements MatcherJSONSerializer<Object> {
|
||||
} 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);
|
||||
|
@ -49,7 +49,7 @@ public class XMLTokener extends JSONTokener {
|
||||
* @param s A source string.
|
||||
*/
|
||||
public XMLTokener(final CharSequence s) {
|
||||
super(s);
|
||||
super(s, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -1 +1 @@
|
||||
{"recommend_text":"✅宁波,一座历史悠久的文化名城"}
|
||||
{"recommend_text":"✅宁波,一座历史悠久的文化名城\n你好", ,一: "aaa"}
|
||||
|
Loading…
x
Reference in New Issue
Block a user