diff --git a/CHANGELOG.md b/CHANGELOG.md index c6613efe5..d952d3518 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.8.17.M1 (2023-04-02) +# 5.8.17.M1 (2023-04-05) ### 🐣新特性 * 【core 】 SerializeUtil.deserialize增加白名单类,避免RCE vulnerability(issue#3021@Github) @@ -18,6 +18,7 @@ * 【core 】 CollUtil.split优化切割列表参数判断,避免OOM(pr#3026@Github) * 【core 】 修复FileUtil.move传入相同目录或子目录丢失源目录的问题(pr#3032@Github) * 【core 】 修复SafeConcurrentHashMap.computeIfAbsent可能存在的结果为null的情况(issue#I6RVMY@Gitee) +* 【json 】 修复Pair反序列化报错问题(issue#I6SZYB@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.8.16 (2023-03-26) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java b/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java index b64d55532..dfe288a78 100755 --- a/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java @@ -6,12 +6,7 @@ import cn.hutool.core.convert.impl.MapConverter; import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.text.UnicodeUtil; -import cn.hutool.core.util.ByteUtil; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.ClassUtil; -import cn.hutool.core.util.HexUtil; -import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.*; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -19,14 +14,7 @@ import java.math.BigInteger; import java.nio.charset.Charset; import java.time.Instant; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.TimeUnit; /** diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java index 3bb96bf87..394c60821 100755 --- a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java @@ -1,48 +1,13 @@ package cn.hutool.core.convert; import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.convert.impl.ArrayConverter; -import cn.hutool.core.convert.impl.AtomicBooleanConverter; -import cn.hutool.core.convert.impl.AtomicIntegerArrayConverter; -import cn.hutool.core.convert.impl.AtomicLongArrayConverter; -import cn.hutool.core.convert.impl.AtomicReferenceConverter; -import cn.hutool.core.convert.impl.BeanConverter; -import cn.hutool.core.convert.impl.BooleanConverter; -import cn.hutool.core.convert.impl.CalendarConverter; -import cn.hutool.core.convert.impl.CharacterConverter; -import cn.hutool.core.convert.impl.CharsetConverter; -import cn.hutool.core.convert.impl.ClassConverter; -import cn.hutool.core.convert.impl.CollectionConverter; -import cn.hutool.core.convert.impl.CurrencyConverter; -import cn.hutool.core.convert.impl.DateConverter; -import cn.hutool.core.convert.impl.DurationConverter; -import cn.hutool.core.convert.impl.EnumConverter; -import cn.hutool.core.convert.impl.LocaleConverter; -import cn.hutool.core.convert.impl.MapConverter; -import cn.hutool.core.convert.impl.NumberConverter; -import cn.hutool.core.convert.impl.OptConverter; -import cn.hutool.core.convert.impl.OptionalConverter; -import cn.hutool.core.convert.impl.PathConverter; -import cn.hutool.core.convert.impl.PeriodConverter; -import cn.hutool.core.convert.impl.PrimitiveConverter; -import cn.hutool.core.convert.impl.ReferenceConverter; -import cn.hutool.core.convert.impl.StackTraceElementConverter; -import cn.hutool.core.convert.impl.StringConverter; -import cn.hutool.core.convert.impl.TemporalAccessorConverter; -import cn.hutool.core.convert.impl.TimeZoneConverter; -import cn.hutool.core.convert.impl.URIConverter; -import cn.hutool.core.convert.impl.URLConverter; -import cn.hutool.core.convert.impl.UUIDConverter; +import cn.hutool.core.convert.impl.*; import cn.hutool.core.date.DateTime; import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.Pair; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.SafeConcurrentHashMap; -import cn.hutool.core.util.ClassUtil; -import cn.hutool.core.util.ObjUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.ReflectUtil; -import cn.hutool.core.util.ServiceLoaderUtil; -import cn.hutool.core.util.TypeUtil; +import cn.hutool.core.util.*; import java.io.Serializable; import java.lang.ref.SoftReference; @@ -54,35 +19,10 @@ import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; -import java.time.DayOfWeek; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.Month; -import java.time.MonthDay; -import java.time.OffsetDateTime; -import java.time.OffsetTime; -import java.time.Period; -import java.time.ZonedDateTime; +import java.time.*; import java.time.temporal.TemporalAccessor; -import java.util.Calendar; -import java.util.Collection; -import java.util.Currency; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.TimeZone; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicIntegerArray; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicLongArray; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.atomic.DoubleAdder; -import java.util.concurrent.atomic.LongAdder; +import java.util.*; +import java.util.concurrent.atomic.*; /** * 转换器登记中心 @@ -357,6 +297,12 @@ public class ConverterRegistry implements Serializable { return (T) mapConverter.convert(value, (Map) defaultValue); } + // Map类型(不可以默认强转) + if (Map.Entry.class.isAssignableFrom(rowType)) { + final EntryConverter mapConverter = new EntryConverter(type); + return (T) mapConverter.convert(value, (Map.Entry) defaultValue); + } + // 默认强转 if (rowType.isInstance(value)) { return (T) value; @@ -462,6 +408,7 @@ public class ConverterRegistry implements Serializable { defaultConverterMap.put(StackTraceElement.class, new StackTraceElementConverter());// since 4.5.2 defaultConverterMap.put(Optional.class, new OptionalConverter());// since 5.0.0 defaultConverterMap.put(Opt.class, new OptConverter());// since 5.7.16 + defaultConverterMap.put(Pair.class, new PairConverter(Pair.class));// since 5.8.17 return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/EntryConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/EntryConverter.java new file mode 100644 index 000000000..8267af698 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/EntryConverter.java @@ -0,0 +1,115 @@ +package cn.hutool.core.convert.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.convert.AbstractConverter; +import cn.hutool.core.convert.ConvertException; +import cn.hutool.core.convert.ConverterRegistry; +import cn.hutool.core.lang.Pair; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.TypeUtil; + +import java.lang.reflect.Type; +import java.util.Map; + +public class EntryConverter extends AbstractConverter> { + + /** Pair类型 */ + private final Type pairType; + /** 键类型 */ + private final Type keyType; + /** 值类型 */ + private final Type valueType; + + /** + * 构造,Pair的key和value泛型类型自动获取 + * + * @param entryType Map类型 + */ + public EntryConverter(Type entryType) { + this(entryType, TypeUtil.getTypeArgument(entryType, 0), TypeUtil.getTypeArgument(entryType, 1)); + } + + /** + * 构造 + * + * @param entryType Pair类型 + * @param keyType 键类型 + * @param valueType 值类型 + */ + public EntryConverter(Type entryType, Type keyType, Type valueType) { + this.pairType = entryType; + this.keyType = keyType; + this.valueType = valueType; + } + + @SuppressWarnings("rawtypes") + @Override + protected Map.Entry convertInternal(Object value) { + Map map = null; + if (value instanceof Pair) { + final Pair pair = (Pair) value; + map = MapUtil.of(pair.getKey(), pair.getValue()); + }else if (value instanceof Map) { + map = (Map) value; + } else if (value instanceof CharSequence) { + final CharSequence str = (CharSequence) value; + map = strToMap(str); + } else if (BeanUtil.isReadableBean(value.getClass())) { + map = BeanUtil.beanToMap(value); + } + + if (null != map) { + return mapToEntry(pairType, keyType, valueType, map); + } + + throw new ConvertException("Unsupported to map from [{}] of type: {}", value, value.getClass().getName()); + } + + /** + * 字符串转单个键值对的Map,支持分隔符{@code :}、{@code =}、{@code ,} + * + * @param str 字符串 + * @return map or null + */ + private static Map strToMap(final CharSequence str) { + // key:value key=value key,value + final int index = StrUtil.indexOf(str, '=', 0, str.length()); + + if (index > -1) { + return MapUtil.of(str.subSequence(0, index + 1), str.subSequence(index, str.length())); + } + return null; + } + + /** + * Map转Entry + * + * @param targetType 目标的Map类型 + * @param keyType 键类型 + * @param valueType 值类型 + * @param map 被转换的map + * @return Entry + */ + @SuppressWarnings("rawtypes") + private static Map.Entry mapToEntry(final Type targetType, final Type keyType, final Type valueType, final Map map) { + + Object key = null; + Object value = null; + if (1 == map.size()) { + final Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); + key = entry.getKey(); + value = entry.getValue(); + } else if (2 == map.size()) { + key = map.get("key"); + value = map.get("value"); + } + + final ConverterRegistry convert = ConverterRegistry.getInstance(); + return (Map.Entry) ReflectUtil.newInstance(TypeUtil.getClass(targetType), + TypeUtil.isUnknown(keyType) ? key : convert.convert(keyType, key), + TypeUtil.isUnknown(valueType) ? value : convert.convert(valueType, value) + ); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/PairConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/PairConverter.java new file mode 100644 index 000000000..7411b89ec --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/PairConverter.java @@ -0,0 +1,115 @@ +package cn.hutool.core.convert.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.convert.AbstractConverter; +import cn.hutool.core.convert.ConvertException; +import cn.hutool.core.convert.ConverterRegistry; +import cn.hutool.core.lang.Pair; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.TypeUtil; + +import java.lang.reflect.Type; +import java.util.Map; + +public class PairConverter extends AbstractConverter> { + + /** Pair类型 */ + private final Type pairType; + /** 键类型 */ + private final Type keyType; + /** 值类型 */ + private final Type valueType; + + /** + * 构造,Pair的key和value泛型类型自动获取 + * + * @param pairType Map类型 + */ + public PairConverter(Type pairType) { + this(pairType, null, null); + } + + /** + * 构造 + * + * @param pairType Pair类型 + * @param keyType 键类型 + * @param valueType 值类型 + */ + public PairConverter(Type pairType, Type keyType, Type valueType) { + this.pairType = pairType; + this.keyType = keyType; + this.valueType = valueType; + } + + @SuppressWarnings("rawtypes") + @Override + protected Pair convertInternal(Object value) { + Map map = null; + if (value instanceof Map.Entry) { + final Map.Entry entry = (Map.Entry) value; + map = MapUtil.of(entry.getKey(), entry.getValue()); + }else if (value instanceof Map) { + map = (Map) value; + } else if (value instanceof CharSequence) { + final CharSequence str = (CharSequence) value; + map = strToMap(str); + } else if (BeanUtil.isReadableBean(value.getClass())) { + map = BeanUtil.beanToMap(value); + } + + if (null != map) { + return mapToPair(pairType, keyType, valueType, map); + } + + throw new ConvertException("Unsupported to map from [{}] of type: {}", value, value.getClass().getName()); + } + + /** + * 字符串转单个键值对的Map,支持分隔符{@code :}、{@code =}、{@code ,} + * + * @param str 字符串 + * @return map or null + */ + private static Map strToMap(final CharSequence str) { + // key:value key=value key,value + final int index = StrUtil.indexOf(str, '=', 0, str.length()); + + if (index > -1) { + return MapUtil.of(str.subSequence(0, index + 1), str.subSequence(index, str.length())); + } + return null; + } + + /** + * Map转Entry + * + * @param targetType 目标的Map类型 + * @param keyType 键类型 + * @param valueType 值类型 + * @param map 被转换的map + * @return Entry + */ + @SuppressWarnings("rawtypes") + private static Pair mapToPair(final Type targetType, final Type keyType, final Type valueType, final Map map) { + + Object key = null; + Object value = null; + if (1 == map.size()) { + final Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); + key = entry.getKey(); + value = entry.getValue(); + } else if (2 == map.size()) { + key = map.get("key"); + value = map.get("value"); + } + + final ConverterRegistry convert = ConverterRegistry.getInstance(); + return (Pair) ReflectUtil.newInstance(TypeUtil.getClass(targetType), + TypeUtil.isUnknown(keyType) ? key : convert.convert(keyType, key), + TypeUtil.isUnknown(valueType) ? value : convert.convert(valueType, value) + ); + } +} diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConverter.java b/hutool-json/src/main/java/cn/hutool/json/JSONConverter.java index 6c79a87ef..f17d66427 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConverter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConverter.java @@ -17,6 +17,7 @@ import cn.hutool.json.serialize.JSONDeserializer; import java.lang.reflect.Type; import java.util.List; +import java.util.Map; /** * JSON转换器 @@ -122,7 +123,11 @@ public class JSONConverter implements Converter { // issue#2212@Github // 在JSONObject转Bean时,读取JSONObject本身的配置文件 if(value instanceof JSONGetter - && targetType instanceof Class && BeanUtil.hasSetter((Class) targetType)){ + && targetType instanceof Class + // Map.Entry特殊处理 + && (false == Map.Entry.class.isAssignableFrom((Class)targetType) + && BeanUtil.hasSetter((Class) targetType))){ + final JSONConfig config = ((JSONGetter) value).getConfig(); final Converter converter = new BeanConverter<>(targetType, InternalJSONUtil.toCopyOptions(config).setIgnoreError(ignoreError)); diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI6SZYBTest.java b/hutool-json/src/test/java/cn/hutool/json/IssueI6SZYBTest.java new file mode 100644 index 000000000..82cd025a5 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI6SZYBTest.java @@ -0,0 +1,30 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.Pair; +import org.junit.Assert; +import org.junit.Test; + +import java.util.AbstractMap; +import java.util.Map; + +public class IssueI6SZYBTest { + @Test + public void pairTest() { + Pair pair = Pair.of(1, 2); + String jsonStr = JSONUtil.toJsonStr(pair); + Assert.assertEquals("{\"key\":1,\"value\":2}", jsonStr); + + final Pair bean = JSONUtil.toBean(jsonStr, Pair.class); + Assert.assertEquals(pair, bean); + } + + @Test + public void entryTest() { + Map.Entry pair = new AbstractMap.SimpleEntry<>("1", 2); + String jsonStr = JSONUtil.toJsonStr(pair); + Assert.assertEquals("{\"1\":2}", jsonStr); + + final Map.Entry bean = JSONUtil.toBean(jsonStr, AbstractMap.SimpleEntry.class); + Assert.assertEquals(pair, bean); + } +}