mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2026-05-29 18:57:11 +08:00
Prepare release
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,12 +1,24 @@
|
||||
|
||||
# 🚀Changelog
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.8.45(2026-03-20)
|
||||
# 5.8.46(2026-05-25)
|
||||
### 🐣新特性
|
||||
* 【core 】 `AnnotationUtil`新增两级缓存架构,提升高频注解解析性能(pr#1434@Gitee)
|
||||
* 【core 】 `RegexPool.PLATE_NUMBER`新增粤AP号段支持(issue#IJNDJR@Gitee)
|
||||
|
||||
### 🐞Bug修复
|
||||
* 【db 】 修复`Page`和`PageResult`首页调用问题(issue#IH7A18@Gitee)
|
||||
* 【ai 】 修复AI SPI classloader找不到实现问题(issue#4241@Github)
|
||||
* 【extra 】 修复`ExpressionEngine`中SpELEngine、MVEL白名单无效问题(issue#4249@Github)
|
||||
* 【core 】 修复`JNDIUtil`远程加载漏洞(issue#4249@Github)
|
||||
* 【core 】 修复`ValidateObjectInputStream`白名单规则问题(issue#4249@Github)
|
||||
* 【core 】 修复`VersionUtil`比对null时结果异常问题(issue#IJNFQZ@Gitee)
|
||||
* 【core 】 修复`BeanConverter`和`MapConverter`源Bean判断问题(pr#4252@Github)
|
||||
|
||||
# 5.8.45(2026-05-19)
|
||||
### 🐣新特性
|
||||
* 此版本发布出现问题,跳过!
|
||||
### 🐞Bug修复
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.8.44(2026-03-11)
|
||||
|
||||
@@ -135,18 +135,18 @@ Each module can be introduced individually, or all modules can be introduced by
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐Gradle
|
||||
```
|
||||
implementation 'cn.hutool:hutool-all:5.8.45'
|
||||
implementation 'cn.hutool:hutool-all:5.8.46'
|
||||
```
|
||||
|
||||
## 📥Download
|
||||
|
||||
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.45/)
|
||||
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.46/)
|
||||
|
||||
> 🔔️note:
|
||||
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.
|
||||
|
||||
@@ -125,20 +125,20 @@ Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐Gradle
|
||||
```
|
||||
implementation 'cn.hutool:hutool-all:5.8.45'
|
||||
implementation 'cn.hutool:hutool-all:5.8.46'
|
||||
```
|
||||
|
||||
### 📥下载jar
|
||||
|
||||
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
|
||||
|
||||
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.45/)
|
||||
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.46/)
|
||||
|
||||
> 🔔️注意
|
||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
|
||||
@@ -1 +1 @@
|
||||
5.8.45
|
||||
5.8.46
|
||||
|
||||
@@ -1 +1 @@
|
||||
var version = '5.8.45'
|
||||
var version = '5.8.46'
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-ai</artifactId>
|
||||
|
||||
@@ -22,7 +22,6 @@ import cn.hutool.ai.core.AIServiceProvider;
|
||||
import cn.hutool.core.util.ServiceLoaderUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
@@ -37,9 +36,12 @@ public class AIServiceFactory {
|
||||
|
||||
// 加载所有 AIModelProvider 实现类
|
||||
static {
|
||||
final ServiceLoader<AIServiceProvider> loader = ServiceLoaderUtil.load(AIServiceProvider.class);
|
||||
for (final AIServiceProvider provider : loader) {
|
||||
providers.put(provider.getServiceName().toLowerCase(), provider);
|
||||
for (final AIServiceProvider provider : ServiceLoaderUtil.load(AIServiceProvider.class)) {
|
||||
providers.putIfAbsent(provider.getServiceName().toLowerCase(), provider);
|
||||
}
|
||||
// issue#4241@github,多线程和Spring环境下可能导致SPI文件找不到问题
|
||||
for (final AIServiceProvider provider : ServiceLoaderUtil.load(AIServiceProvider.class, AIServiceProvider.class.getClassLoader())) {
|
||||
providers.putIfAbsent(provider.getServiceName().toLowerCase(), provider);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ package cn.hutool.ai.core;
|
||||
import cn.hutool.core.util.ServiceLoaderUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
@@ -34,9 +33,12 @@ public class AIConfigRegistry {
|
||||
|
||||
// 加载所有 AIConfig 实现类
|
||||
static {
|
||||
final ServiceLoader<AIConfig> loader = ServiceLoaderUtil.load(AIConfig.class);
|
||||
for (final AIConfig config : loader) {
|
||||
configClasses.put(config.getModelName().toLowerCase(), config.getClass());
|
||||
for (final AIConfig config : ServiceLoaderUtil.load(AIConfig.class)) {
|
||||
configClasses.putIfAbsent(config.getModelName().toLowerCase(), config.getClass());
|
||||
}
|
||||
// issue#4241@github,多线程和Spring环境下可能导致SPI文件找不到问题
|
||||
for (final AIConfig config : ServiceLoaderUtil.load(AIConfig.class, AIConfig.class.getClassLoader())) {
|
||||
configClasses.putIfAbsent(config.getModelName().toLowerCase(), config.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
||||
@@ -79,7 +79,7 @@ public class BeanConverter<T> extends AbstractConverter<T> {
|
||||
|
||||
if(value instanceof Map ||
|
||||
value instanceof ValueProvider ||
|
||||
BeanUtil.isBean(value.getClass())) {
|
||||
BeanUtil.isReadableBean(value.getClass())) {
|
||||
if(value instanceof Map && this.beanClass.isInterface()) {
|
||||
// 将Map动态代理为Bean
|
||||
return MapProxy.create((Map<?, ?>)value).toProxyBean(this.beanClass);
|
||||
|
||||
@@ -73,7 +73,7 @@ public class MapConverter extends AbstractConverter<Map<?, ?>> {
|
||||
map = MapUtil.createMap(mapClass);
|
||||
}
|
||||
convertMapToMap((Map) value, map);
|
||||
} else if (BeanUtil.isBean(value.getClass())) {
|
||||
} else if (BeanUtil.isReadableBean(value.getClass())) {
|
||||
if(value.getClass().getName().equals("cn.hutool.json.JSONArray")){
|
||||
// issue#3795 增加JSONArray转Map错误检查
|
||||
throw new UnsupportedOperationException(StrUtil.format("Unsupported {} to Map.", value.getClass().getName()));
|
||||
|
||||
@@ -85,14 +85,7 @@ public class ValidateObjectInputStream extends ObjectInputStream {
|
||||
}
|
||||
}
|
||||
|
||||
if(CollUtil.isEmpty(this.whiteClassSet)){
|
||||
return;
|
||||
}
|
||||
if(className.startsWith("java.")){
|
||||
// java中的类默认在白名单中
|
||||
return;
|
||||
}
|
||||
if(this.whiteClassSet.contains(className)){
|
||||
if(CollUtil.isEmpty(this.whiteClassSet) || this.whiteClassSet.contains(className)){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ public interface RegexPool {
|
||||
*/
|
||||
String PLATE_NUMBER =
|
||||
//https://gitee.com/chinabugotech/hutool/issues/I1B77H?from=project-issue
|
||||
"^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[ABCDEFGHJK])|([ABCDEFGHJK]([A-HJ-NP-Z0-9])[0-9]{4})))|" +
|
||||
"^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[ABCDEFGHJK])|([ABCDEFGHJKP]([A-HJ-NP-Z0-9])[0-9]{4})))|" +
|
||||
//https://gitee.com/chinabugotech/hutool/issues/I1BJHE?from=project-issue
|
||||
"([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]\\d{3}\\d{1,3}[领])|" +
|
||||
"([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$";
|
||||
|
||||
@@ -8,7 +8,9 @@ import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.InitialDirContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -20,15 +22,20 @@ import java.util.Map;
|
||||
* 见:https://blog.csdn.net/u010430304/article/details/54601302
|
||||
* </p>
|
||||
*
|
||||
* @author loolY
|
||||
* @author looly
|
||||
* @since 5.7.7
|
||||
*/
|
||||
public class JNDIUtil {
|
||||
|
||||
/**
|
||||
* 创建{@link InitialDirContext}
|
||||
* 建议在应用启动时设置系统属性(禁用远程 codebase 加载)
|
||||
* <pre>{@code
|
||||
* System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false");
|
||||
* System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false");
|
||||
* }</pre>
|
||||
*
|
||||
* @param environment 环境参数,{@code null}表示无参数
|
||||
* @param environment 环境参数,如@{code java.naming.factory.initial}和{@code java.naming.provider.url},{@code null}表示无参数
|
||||
* @return {@link InitialDirContext}
|
||||
*/
|
||||
public static InitialDirContext createInitialDirContext(Map<String, String> environment) {
|
||||
@@ -36,6 +43,10 @@ public class JNDIUtil {
|
||||
if (MapUtil.isEmpty(environment)) {
|
||||
return new InitialDirContext();
|
||||
}
|
||||
|
||||
// issue#4249 修复JNDI注入漏洞
|
||||
validateEnvironment(environment);
|
||||
|
||||
return new InitialDirContext(Convert.convert(Hashtable.class, environment));
|
||||
} catch (NamingException e) {
|
||||
throw new UtilException(e);
|
||||
@@ -43,9 +54,14 @@ public class JNDIUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建{@link InitialContext}
|
||||
* 创建{@link InitialContext}<br>
|
||||
* 建议在应用启动时设置系统属性(禁用远程 codebase 加载)
|
||||
* <pre>{@code
|
||||
* System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false");
|
||||
* System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false");
|
||||
* }</pre>
|
||||
*
|
||||
* @param environment 环境参数,{@code null}表示无参数
|
||||
* @param environment 环境参数,如@{code java.naming.factory.initial}和{@code java.naming.provider.url},{@code null}表示无参数
|
||||
* @return {@link InitialContext}
|
||||
*/
|
||||
public static InitialContext createInitialContext(Map<String, String> environment) {
|
||||
@@ -53,6 +69,10 @@ public class JNDIUtil {
|
||||
if (MapUtil.isEmpty(environment)) {
|
||||
return new InitialContext();
|
||||
}
|
||||
|
||||
// issue#4249 修复JNDI注入漏洞
|
||||
validateEnvironment(environment);
|
||||
|
||||
return new InitialContext(Convert.convert(Hashtable.class, environment));
|
||||
} catch (NamingException e) {
|
||||
throw new UtilException(e);
|
||||
@@ -74,4 +94,63 @@ public class JNDIUtil {
|
||||
throw new UtilException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<String> SAFE_PROTOCOLS = Arrays.asList(
|
||||
"java:",
|
||||
"dns:"
|
||||
);
|
||||
|
||||
/**
|
||||
* 验证并过滤environment中的危险属性
|
||||
*
|
||||
* @param environment 原始环境参数
|
||||
* @return 过滤后的环境参数
|
||||
*/
|
||||
private static Map<String, String> validateEnvironment(Map<String, String> environment) {
|
||||
if (MapUtil.isNotEmpty(environment)) {
|
||||
// 检查 PROVIDER_URL
|
||||
String providerUrl = environment.get("java.naming.provider.url");
|
||||
if (StrUtil.isNotBlank(providerUrl) && !isSafeProtocol(providerUrl)) {
|
||||
throw new UtilException("JNDI protocol not allowed: " + providerUrl);
|
||||
}
|
||||
|
||||
// 检查 INITIAL_CONTEXT_FACTORY
|
||||
String factory = environment.get("java.naming.factory.initial");
|
||||
if (StrUtil.isNotBlank(factory)) {
|
||||
// 只允许安全的工厂类
|
||||
if (!factory.startsWith("com.sun.jndi.dns.") &&
|
||||
!factory.startsWith("com.sun.jndi.ldap.") &&
|
||||
!factory.startsWith("com.sun.jndi.rmi.")) {
|
||||
throw new UtilException("JNDI factory not allowed: " + factory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查URL是否在协议白名单内
|
||||
*
|
||||
* @param url 要检查的URL
|
||||
* @param allowedProtocols 允许的协议列表,{@code null}或空表示使用默认安全协议
|
||||
* @return 是否安全
|
||||
*/
|
||||
private static boolean isSafeProtocol(String url, String... allowedProtocols) {
|
||||
if (StrUtil.isBlank(url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> protocols = (allowedProtocols != null && allowedProtocols.length > 0)
|
||||
? Arrays.asList(allowedProtocols)
|
||||
: SAFE_PROTOCOLS;
|
||||
|
||||
String lowerUrl = url.toLowerCase();
|
||||
for (String protocol : protocols) {
|
||||
if (lowerUrl.startsWith(protocol.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ public class VersionUtil {
|
||||
throw new UtilException("非法的版本分隔符:" + versionsDelimiter);
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(versionEl) || StrUtil.isBlank(currentVersion)) {
|
||||
if (StrUtil.isBlank(versionEl)) {
|
||||
return false;
|
||||
}
|
||||
String trimmedVersion = StrUtil.trim(currentVersion);
|
||||
@@ -158,6 +158,9 @@ public class VersionUtil {
|
||||
if (matcher.find()) {
|
||||
String op = matcher.group();
|
||||
String ver = StrUtil.removePrefix(el, op);
|
||||
if("null".equalsIgnoreCase( ver)){
|
||||
ver = null;
|
||||
}
|
||||
switch (op) {
|
||||
case ">=":
|
||||
case "≥":
|
||||
|
||||
@@ -974,4 +974,85 @@ public class BeanUtilTest {
|
||||
final boolean bean = BeanUtil.isBean(Dict.class);
|
||||
assertFalse(bean);
|
||||
}
|
||||
|
||||
// ============ Test for GitHub issue #4245 ============
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Order4245 {
|
||||
private Long orderId;
|
||||
private String orderNo;
|
||||
private List<OrderItem4245> orderItemList;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter(AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@NoArgsConstructor
|
||||
public static class OrderItem4245 {
|
||||
private Long itemId;
|
||||
private String productName;
|
||||
private Integer quantity;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class OrderDto4245 {
|
||||
private Long orderId;
|
||||
private String orderNo;
|
||||
private List<OrderItemDto4245> orderItemList;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class OrderItemDto4245 {
|
||||
private Long itemId;
|
||||
private String productName;
|
||||
private Integer quantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* GitHub issue#4245: BeanUtil.copyProperties throws ConvertException
|
||||
* when source list element has protected setters.
|
||||
*
|
||||
* <p>Root cause: BeanConverter used isBean() (which checks for public setters)
|
||||
* on the SOURCE object instead of isReadableBean() (which checks for public getters).
|
||||
* A source bean only needs to be readable, not writable.</p>
|
||||
*/
|
||||
@Test
|
||||
public void issue4245Test() {
|
||||
final Order4245 order = new Order4245(
|
||||
1L,
|
||||
"01",
|
||||
new ArrayList<>()
|
||||
);
|
||||
order.getOrderItemList().add(
|
||||
new OrderItem4245(1L, "aa", 1)
|
||||
);
|
||||
|
||||
// This should not throw ConvertException
|
||||
final OrderDto4245 dto = BeanUtil.copyProperties(order, OrderDto4245.class);
|
||||
|
||||
assertNotNull(dto);
|
||||
assertEquals(order.getOrderId(), dto.getOrderId());
|
||||
assertEquals(order.getOrderNo(), dto.getOrderNo());
|
||||
assertNotNull(dto.getOrderItemList());
|
||||
assertEquals(1, dto.getOrderItemList().size());
|
||||
assertEquals(Long.valueOf(1L), dto.getOrderItemList().get(0).getItemId());
|
||||
assertEquals("aa", dto.getOrderItemList().get(0).getProductName());
|
||||
assertEquals(Integer.valueOf(1), dto.getOrderItemList().get(0).getQuantity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test map conversion with protected setter bean
|
||||
*/
|
||||
@Test
|
||||
public void issue4245MapConvertTest() {
|
||||
final OrderItem4245 item = new OrderItem4245(1L, "aa", 1);
|
||||
// Should not throw UnsupportedOperationException
|
||||
final Map<String, Object> map = BeanUtil.beanToMap(item);
|
||||
assertNotNull(map);
|
||||
assertEquals(1L, map.get("itemId"));
|
||||
assertEquals("aa", map.get("productName"));
|
||||
assertEquals(1, map.get("quantity"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,15 @@ public class ValidatorTest {
|
||||
assertTrue(Validator.isPlateNumber("闽20401领"));
|
||||
//issue#3979
|
||||
assertTrue(Validator.isPlateNumber("沪AE22075"));
|
||||
|
||||
// issue#IJNDJR
|
||||
assertTrue(Validator.isPlateNumber("粤AP00000"));
|
||||
assertTrue(Validator.isPlateNumber("粤AP00001"));
|
||||
assertTrue(Validator.isPlateNumber("粤AP10000"));
|
||||
assertTrue(Validator.isPlateNumber("粤AP20000"));
|
||||
assertTrue(Validator.isPlateNumber("粤AP30000"));
|
||||
assertTrue(Validator.isPlateNumber("粤AP40000"));
|
||||
assertTrue(Validator.isPlateNumber("粤AP50000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.hutool.core.util;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.comparator.VersionComparator;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -104,4 +105,10 @@ class VersionUtilTest {
|
||||
assertTrue(VersionUtil.matchEl("999.999.999", "-"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void issueIJNFQZTest(){
|
||||
assertEquals(1, VersionComparator.INSTANCE.compare("1.0", null));
|
||||
assertEquals(1, StrUtil.compareVersion("1.0", null));
|
||||
assertTrue(VersionUtil.isGreaterThan("1.0", null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-db</artifactId>
|
||||
@@ -145,7 +145,7 @@
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.7.3</version>
|
||||
<version>42.7.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
@@ -24,7 +24,7 @@
|
||||
<rythm.version>1.4.2</rythm.version>
|
||||
<freemarker.version>2.3.32</freemarker.version>
|
||||
<enjoy.version>5.1.3</enjoy.version>
|
||||
<thymeleaf.version>3.1.2.RELEASE</thymeleaf.version>
|
||||
<thymeleaf.version>3.1.5.RELEASE</thymeleaf.version>
|
||||
<mail.version>1.6.2</mail.version>
|
||||
<jsch.version>0.1.55</jsch.version>
|
||||
<sshj.version>0.38.0</sshj.version>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package cn.hutool.extra.expression.engine.mvel;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.extra.expression.ExpressionEngine;
|
||||
import cn.hutool.extra.expression.ExpressionException;
|
||||
import org.mvel2.MVEL;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -27,6 +29,16 @@ public class MvelEngine implements ExpressionEngine {
|
||||
|
||||
@Override
|
||||
public Object eval(String expression, Map<String, Object> context, Collection<Class<?>> allowClassSet) {
|
||||
|
||||
// issue#4249 检查context的value类型是否在白名单中,不在则抛出异常
|
||||
if(CollUtil.isNotEmpty(allowClassSet)){
|
||||
context.values().forEach(value -> {
|
||||
if(!allowClassSet.contains(value.getClass())){
|
||||
throw new ExpressionException("Value type [{}] is not in allowClassSet [{}]", value.getClass(), allowClassSet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return MVEL.eval(expression, context);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package cn.hutool.extra.expression.engine.rhino;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.extra.expression.ExpressionEngine;
|
||||
import cn.hutool.extra.expression.ExpressionException;
|
||||
import org.mozilla.javascript.Context;
|
||||
import org.mozilla.javascript.Scriptable;
|
||||
import org.mozilla.javascript.ScriptableObject;
|
||||
@@ -24,6 +26,16 @@ public class RhinoEngine implements ExpressionEngine {
|
||||
|
||||
@Override
|
||||
public Object eval(String expression, Map<String, Object> context, Collection<Class<?>> allowClassSet) {
|
||||
|
||||
// issue#4249 检查context的value类型是否在白名单中,不在则抛出异常
|
||||
if(CollUtil.isNotEmpty(allowClassSet)){
|
||||
context.values().forEach(value -> {
|
||||
if(!allowClassSet.contains(value.getClass())){
|
||||
throw new ExpressionException("Value type [{}] is not in allowClassSet [{}]", value.getClass(), allowClassSet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Context ctx = Context.enter();
|
||||
final Scriptable scope = ctx.initStandardObjects();
|
||||
if (MapUtil.isNotEmpty(context)) {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package cn.hutool.extra.expression.engine.spel;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.extra.expression.ExpressionEngine;
|
||||
import cn.hutool.extra.expression.ExpressionException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
@@ -29,7 +31,22 @@ public class SpELEngine implements ExpressionEngine {
|
||||
|
||||
@Override
|
||||
public Object eval(String expression, Map<String, Object> context, Collection<Class<?>> allowClassSet) {
|
||||
final EvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
// final EvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
|
||||
// issue#4249 检查context的value类型是否在白名单中,不在则抛出异常
|
||||
if(CollUtil.isNotEmpty(allowClassSet)){
|
||||
context.values().forEach(value -> {
|
||||
if(!allowClassSet.contains(value.getClass())){
|
||||
throw new ExpressionException("Value type [{}] is not in allowClassSet [{}]", value.getClass(), allowClassSet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
EvaluationContext evaluationContext = SimpleEvaluationContext
|
||||
.forReadOnlyDataBinding()
|
||||
.withInstanceMethods() // 仅允许调用白名单类的实例方法
|
||||
.build();
|
||||
|
||||
context.forEach(evaluationContext::setVariable);
|
||||
return parser.parseExpression(expression).getValue(evaluationContext);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-http</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-json</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-jwt</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-log</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-script</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-system</artifactId>
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -8,7 +8,7 @@
|
||||
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.45</version>
|
||||
<version>5.8.46</version>
|
||||
<name>hutool</name>
|
||||
<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description>
|
||||
<url>https://github.com/chinabugotech/hutool</url>
|
||||
|
||||
Reference in New Issue
Block a user