This commit is contained in:
Looly 2023-04-01 01:04:54 +08:00
parent 470e5415f0
commit f0fb98c584
24 changed files with 457 additions and 462 deletions

View File

@ -12,32 +12,21 @@
package cn.hutool.core.annotation;
import cn.hutool.core.array.ArrayUtil;
import cn.hutool.core.classloader.ClassLoaderUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.func.LambdaInfo;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.lang.func.SerFunction;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.map.WeakConcurrentMap;
import cn.hutool.core.reflect.FieldUtil;
import cn.hutool.core.reflect.MethodUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.array.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
@ -70,7 +59,7 @@ public class AnnotationUtil {
* @since 6.0.0
*/
public static Annotation[] getDeclaredAnnotations(final AnnotatedElement element) {
return MapUtil.computeIfAbsent(DECLARED_ANNOTATIONS_CACHE, element, AnnotatedElement::getDeclaredAnnotations);
return DECLARED_ANNOTATIONS_CACHE.computeIfAbsent(element, AnnotatedElement::getDeclaredAnnotations);
}
/**

View File

@ -12,12 +12,11 @@
package cn.hutool.core.annotation;
import cn.hutool.core.array.ArrayUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.map.WeakConcurrentMap;
import cn.hutool.core.reflect.MethodUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.array.ArrayUtil;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
@ -354,9 +353,7 @@ public interface RepeatableAnnotationCollector {
*/
@Override
protected List<Method> resolveRepeatableMethod(final Annotation annotation) {
final Object cache = MapUtil.computeIfAbsent(
repeatableMethodCache, annotation.annotationType(), this::resolveRepeatableMethodFromType
);
final Object cache = repeatableMethodCache.computeIfAbsent(annotation.annotationType(), this::resolveRepeatableMethodFromType);
return (cache == NONE) ? null : Collections.singletonList((Method)cache);
}
@ -467,9 +464,7 @@ public interface RepeatableAnnotationCollector {
@SuppressWarnings("unchecked")
@Override
protected List<Method> resolveRepeatableMethod(final Annotation annotation) {
final Object cache = MapUtil.computeIfAbsent(
repeatableMethodCache, annotation.annotationType(), this::resolveRepeatableMethodFromType
);
final Object cache = repeatableMethodCache.computeIfAbsent(annotation.annotationType(), this::resolveRepeatableMethodFromType);
return (cache == NONE) ? null : (List<Method>)cache;
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package cn.hutool.core.compress;
import cn.hutool.core.io.IoUtil;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* {@link ZipFile} 资源包装
*
* @author looly
* @since 6.0.0
*/
public class ZipFileResource implements ZipResource {
private final ZipFile zipFile;
/**
* 构造
*
* @param zipFile {@link ZipFile}
*/
public ZipFileResource(final ZipFile zipFile) {
this.zipFile = zipFile;
}
@Override
public void read(final Consumer<ZipEntry> consumer, final int maxSizeDiff) {
final Enumeration<? extends ZipEntry> em = zipFile.entries();
while (em.hasMoreElements()) {
consumer.accept(ZipSecurityUtil.checkZipBomb(em.nextElement(), maxSizeDiff));
}
}
@Override
public InputStream get(final String path){
final ZipFile zipFile = this.zipFile;
final ZipEntry entry = zipFile.getEntry(path);
if (null != entry) {
return ZipUtil.getStream(zipFile, entry);
}
return null;
}
@Override
public InputStream get(final ZipEntry entry) {
return ZipUtil.getStream(this.zipFile, entry);
}
@Override
public void close() throws IOException {
IoUtil.closeQuietly(this.zipFile);
}
}

View File

@ -12,18 +12,15 @@
package cn.hutool.core.compress;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.text.StrUtil;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
@ -39,10 +36,7 @@ import java.util.zip.ZipInputStream;
public class ZipReader implements Closeable {
// size of uncompressed zip entry shouldn't be bigger of compressed in MAX_SIZE_DIFF times
private static final int MAX_SIZE_DIFF = 100;
private ZipFile zipFile;
private ZipInputStream in;
private static final int DEFAULT_MAX_SIZE_DIFF = 100;
/**
* 创建ZipReader
@ -52,7 +46,7 @@ public class ZipReader implements Closeable {
* @return ZipReader
*/
public static ZipReader of(final File zipFile, final Charset charset) {
return new ZipReader(zipFile, charset);
return new ZipReader(ZipUtil.toZipFile(zipFile, charset));
}
/**
@ -63,18 +57,14 @@ public class ZipReader implements Closeable {
* @return ZipReader
*/
public static ZipReader of(final InputStream in, final Charset charset) {
return new ZipReader(in, charset);
return new ZipReader(new ZipInputStream(in, charset));
}
private final ZipResource resource;
/**
* 构造
*
* @param zipFile 读取的的Zip文件
* @param charset 编码
* 检查ZipBomb文件差异倍数-1表示不检查ZipBomb
*/
public ZipReader(final File zipFile, final Charset charset) {
this.zipFile = ZipUtil.toZipFile(zipFile, charset);
}
private int maxSizeDiff = DEFAULT_MAX_SIZE_DIFF;
/**
* 构造
@ -82,17 +72,7 @@ public class ZipReader implements Closeable {
* @param zipFile 读取的的Zip文件
*/
public ZipReader(final ZipFile zipFile) {
this.zipFile = zipFile;
}
/**
* 构造
*
* @param in 读取的的Zip文件流
* @param charset 编码
*/
public ZipReader(final InputStream in, final Charset charset) {
this.in = new ZipInputStream(in, charset);
this(new ZipFileResource(zipFile));
}
/**
@ -101,7 +81,28 @@ public class ZipReader implements Closeable {
* @param zin 读取的的Zip文件流
*/
public ZipReader(final ZipInputStream zin) {
this.in = zin;
this(new ZipStreamResource(zin));
}
/**
* 构造
*
* @param resource 读取的的Zip文件流
*/
public ZipReader(final ZipResource resource) {
this.resource = resource;
}
/**
* 设置检查ZipBomb文件差异倍数-1表示不检查ZipBomb
*
* @param maxSizeDiff 检查ZipBomb文件差异倍数-1表示不检查ZipBomb
* @return this
* @since 6.0.0
*/
public ZipReader setMaxSizeDiff(final int maxSizeDiff) {
this.maxSizeDiff = maxSizeDiff;
return this;
}
/**
@ -112,27 +113,7 @@ public class ZipReader implements Closeable {
* @return 文件流
*/
public InputStream get(final String path) {
if (null != this.zipFile) {
final ZipFile zipFile = this.zipFile;
final ZipEntry entry = zipFile.getEntry(path);
if (null != entry) {
return ZipUtil.getStream(zipFile, entry);
}
} else {
try {
this.in.reset();
ZipEntry zipEntry;
while (null != (zipEntry = in.getNextEntry())) {
if (zipEntry.getName().equals(path)) {
return this.in;
}
}
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
return null;
return this.resource.get(path);
}
/**
@ -155,31 +136,11 @@ public class ZipReader implements Closeable {
* @throws IORuntimeException IO异常
* @since 5.7.12
*/
@SuppressWarnings("resource")
public File readTo(final File outFile, final Predicate<ZipEntry> entryFilter) throws IORuntimeException {
read((zipEntry) -> {
if (null == entryFilter || entryFilter.test(zipEntry)) {
//gitee issue #I4ZDQI
String path = zipEntry.getName();
if (FileUtil.isWindows()) {
// Win系统下
path = StrUtil.replace(path, "*", "_");
}
// FileUtil.file会检查slip漏洞漏洞说明见http://blog.nsfocus.net/zip-slip-2/
final File outItemFile = FileUtil.file(outFile, path);
if (zipEntry.isDirectory()) {
// 目录
//noinspection ResultOfMethodCallIgnored
outItemFile.mkdirs();
} else {
final InputStream in;
if (null != this.zipFile) {
in = ZipUtil.getStream(this.zipFile, zipEntry);
} else {
in = this.in;
}
// 文件
FileUtil.writeFromStream(in, outItemFile, false);
}
readEntry(zipEntry, outFile);
}
});
return outFile;
@ -193,70 +154,37 @@ public class ZipReader implements Closeable {
* @throws IORuntimeException IO异常
*/
public ZipReader read(final Consumer<ZipEntry> consumer) throws IORuntimeException {
if (null != this.zipFile) {
readFromZipFile(consumer);
} else {
readFromStream(consumer);
}
resource.read(consumer, this.maxSizeDiff);
return this;
}
@Override
public void close() throws IORuntimeException {
if (null != this.zipFile) {
IoUtil.closeQuietly(this.zipFile);
IoUtil.closeQuietly(this.resource);
}
/**
* 读取一个ZipEntry的数据到目标目录下如果entry是个目录则创建对应目录否则解压并写出到文件
*
* @param zipEntry entry
* @param outFile 写出到的目录
*/
private void readEntry(final ZipEntry zipEntry, final File outFile) {
//gitee issue #I4ZDQI
String path = zipEntry.getName();
if (FileUtil.isWindows()) {
// Win系统下
path = StrUtil.replace(path, "*", "_");
}
// FileUtil.file会检查slip漏洞漏洞说明见http://blog.nsfocus.net/zip-slip-2/
final File outItemFile = FileUtil.file(outFile, path);
if (zipEntry.isDirectory()) {
// 目录
//noinspection ResultOfMethodCallIgnored
outItemFile.mkdirs();
} else {
IoUtil.closeQuietly(this.in);
// 文件
FileUtil.writeFromStream(this.resource.get(zipEntry), outItemFile, false);
}
}
/**
* 读取并处理Zip文件中的每一个{@link ZipEntry}
*
* @param consumer {@link ZipEntry}处理器
*/
private void readFromZipFile(final Consumer<ZipEntry> consumer) {
final Enumeration<? extends ZipEntry> em = zipFile.entries();
while (em.hasMoreElements()) {
consumer.accept(checkZipBomb(em.nextElement()));
}
}
/**
* 读取并处理Zip流中的每一个{@link ZipEntry}
*
* @param consumer {@link ZipEntry}处理器
* @throws IORuntimeException IO异常
*/
private void readFromStream(final Consumer<ZipEntry> consumer) throws IORuntimeException {
try {
ZipEntry zipEntry;
while (null != (zipEntry = checkZipBomb(in.getNextEntry()))) {
consumer.accept(zipEntry);
}
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 检查Zip bomb漏洞
*
* @param entry {@link ZipEntry}
* @return 检查后的{@link ZipEntry}
*/
private static ZipEntry checkZipBomb(final ZipEntry entry) {
if (null == entry) {
return null;
}
final long compressedSize = entry.getCompressedSize();
final long uncompressedSize = entry.getSize();
if (compressedSize < 0 || uncompressedSize < 0 ||
// 默认压缩比例是100倍一旦发现压缩率超过这个阈值被认为是Zip bomb
compressedSize * MAX_SIZE_DIFF < uncompressedSize) {
throw new ValidateException("Zip bomb attack detected, invalid sizes: compressed {}, uncompressed {}, name {}",
compressedSize, uncompressedSize, entry.getName());
}
return entry;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package cn.hutool.core.compress;
import java.io.Closeable;
import java.io.InputStream;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
/**
* Zip资源表示如Zip流资源或Zip文件资源
*
* @author looly
* @since 6.0.0
*/
public interface ZipResource extends Closeable {
/**
* 读取并处理Zip文件中的每一个{@link ZipEntry}
*
* @param consumer {@link ZipEntry}处理器
* @param maxSizeDiff 检查ZipBomb文件差异倍数-1表示不检查ZipBomb
*/
void read(final Consumer<ZipEntry> consumer, final int maxSizeDiff);
/**
* 获取指定路径的文件流<br>
* 如果是文件模式则直接获取Entry对应的流如果是流模式则遍历entry后找到对应流返回
*
* @param path 路径
* @return 文件流
*/
InputStream get(String path);
/**
* 获取指定{@link ZipEntry}对应的文件流
*
* @param entry @link ZipEntry}
* @return 文件流
*/
InputStream get(ZipEntry entry);
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package cn.hutool.core.compress;
import cn.hutool.core.exceptions.ValidateException;
import java.util.zip.ZipEntry;
/**
* Zip安全相关工具类如检查Zip bomb漏洞等
*
* @author looly
* @since 6.0.0
*/
public class ZipSecurityUtil {
/**
* 检查Zip bomb漏洞
*
* @param entry {@link ZipEntry}
* @param maxSizeDiff 检查ZipBomb文件差异倍数-1表示不检查ZipBomb
* @return 检查后的{@link ZipEntry}
*/
public static ZipEntry checkZipBomb(final ZipEntry entry, final int maxSizeDiff) {
if (null == entry) {
return null;
}
if (maxSizeDiff < 0 || entry.isDirectory()) {
// 目录不检查
return entry;
}
final long compressedSize = entry.getCompressedSize();
final long uncompressedSize = entry.getSize();
//Console.log(entry.getName(), compressedSize, uncompressedSize);
if (compressedSize < 0 || uncompressedSize < 0 ||
// 默认压缩比例是100倍一旦发现压缩率超过这个阈值被认为是Zip bomb
compressedSize * maxSizeDiff < uncompressedSize) {
throw new ValidateException("Zip bomb attack detected, invalid sizes: compressed {}, uncompressed {}, name {}",
compressedSize, uncompressedSize, entry.getName());
}
return entry;
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package cn.hutool.core.compress;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import java.io.IOException;
import java.io.InputStream;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* {@link ZipInputStream} 资源包装
*
* @author looly
* @since 6.0.0
*/
public class ZipStreamResource implements ZipResource {
private final ZipInputStream in;
/**
* 构造
*
* @param in {@link ZipInputStream}
*/
public ZipStreamResource(final ZipInputStream in) {
this.in = in;
}
@Override
public void read(final Consumer<ZipEntry> consumer, final int maxSizeDiff) {
try {
ZipEntry zipEntry;
while (null != (zipEntry = in.getNextEntry())) {
consumer.accept(zipEntry);
// 检查ZipBomb放在读取内容之后以便entry中的信息正常读取
ZipSecurityUtil.checkZipBomb(zipEntry, maxSizeDiff);
}
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
@Override
public InputStream get(final String path) {
try {
this.in.reset();
ZipEntry zipEntry;
while (null != (zipEntry = in.getNextEntry())) {
if (zipEntry.getName().equals(path)) {
return this.in;
}
}
} catch (final IOException e) {
throw new IORuntimeException(e);
}
return null;
}
@Override
public InputStream get(final ZipEntry entry) {
return this.in;
}
@Override
public void close() throws IOException {
IoUtil.closeQuietly(this.in);
}
}

View File

@ -794,7 +794,7 @@ public class ZipUtil {
* @return 解压后的字符串
* @throws UtilException IO异常
*/
public static String unGzip(final byte[] buf, final String charset) throws UtilException {
public static String unGzip(final byte[] buf, final Charset charset) throws UtilException {
return StrUtil.str(unGzip(buf), charset);
}
@ -915,7 +915,7 @@ public class ZipUtil {
* @return 解压后的字符串
* @since 4.1.4
*/
public static String unZlib(final byte[] buf, final String charset) {
public static String unZlib(final byte[] buf, final Charset charset) {
return StrUtil.str(unZlib(buf), charset);
}

View File

@ -292,8 +292,9 @@ public class ZipWriter implements Closeable {
* @throws IORuntimeException IO异常
*/
private ZipWriter putEntry(final String path, final InputStream in) throws IORuntimeException {
final ZipEntry entry = new ZipEntry(path);
try {
out.putNextEntry(new ZipEntry(path));
out.putNextEntry(entry);
if (null != in) {
IoUtil.copy(in, out);
}

View File

@ -204,18 +204,6 @@ public class NioUtil {
return read(fileChannel, CharsetUtil.UTF_8);
}
/**
* 从FileChannel中读取内容读取完毕后并不关闭Channel
*
* @param fileChannel 文件管道
* @param charsetName 字符集
* @return 内容
* @throws IORuntimeException IO异常
*/
public static String read(final FileChannel fileChannel, final String charsetName) throws IORuntimeException {
return read(fileChannel, CharsetUtil.charset(charsetName));
}
/**
* 从FileChannel中读取内容
*

View File

@ -29,7 +29,10 @@ import cn.hutool.core.regex.ReUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.text.split.SplitUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.*;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.SystemUtil;
import java.io.*;
import java.net.URI;
@ -137,6 +140,7 @@ public class FileUtil extends PathUtil {
return isDirEmpty(dir.toPath());
}
// region ----- loop and walk
/**
* 递归遍历目录以及子目录中的所有文件<br>
* 如果提供file为文件直接返回过滤结果
@ -265,6 +269,7 @@ public class FileUtil extends PathUtil {
IoUtil.closeQuietly(jarFile);
}
}
// endregion
// region ----- file and newFile
/**
@ -1707,20 +1712,6 @@ public class FileUtil extends PathUtil {
return readLines(path, CharsetUtil.UTF_8, collection);
}
/**
* 从文件中读取每一行数据
*
* @param <T> 集合类型
* @param path 文件路径
* @param charset 字符集
* @param collection 集合
* @return 文件中的每行内容的集合
* @throws IORuntimeException IO异常
*/
public static <T extends Collection<String>> T readLines(final String path, final String charset, final T collection) throws IORuntimeException {
return readLines(file(path), charset, collection);
}
/**
* 从文件中读取每一行数据
*
@ -1749,20 +1740,6 @@ public class FileUtil extends PathUtil {
return readLines(file, CharsetUtil.UTF_8, collection);
}
/**
* 从文件中读取每一行数据
*
* @param <T> 集合类型
* @param file 文件路径
* @param charset 字符集
* @param collection 集合
* @return 文件中的每行内容的集合
* @throws IORuntimeException IO异常
*/
public static <T extends Collection<String>> T readLines(final File file, final String charset, final T collection) throws IORuntimeException {
return FileReader.of(file, CharsetUtil.charset(charset)).readLines(collection);
}
/**
* 从文件中读取每一行数据
*
@ -1848,18 +1825,6 @@ public class FileUtil extends PathUtil {
return readLines(path, CharsetUtil.UTF_8);
}
/**
* 从文件中读取每一行数据
*
* @param path 文件路径
* @param charset 字符集
* @return 文件中的每行内容的集合List
* @throws IORuntimeException IO异常
*/
public static List<String> readLines(final String path, final String charset) throws IORuntimeException {
return readLines(path, charset, new ArrayList<>());
}
/**
* 从文件中读取每一行数据
*
@ -1885,18 +1850,6 @@ public class FileUtil extends PathUtil {
return readLines(file, CharsetUtil.UTF_8);
}
/**
* 从文件中读取每一行数据
*
* @param file 文件
* @param charset 字符集
* @return 文件中的每行内容的集合List
* @throws IORuntimeException IO异常
*/
public static List<String> readLines(final File file, final String charset) throws IORuntimeException {
return readLines(file, charset, new ArrayList<>());
}
/**
* 从文件中读取每一行数据
*
@ -2176,19 +2129,6 @@ public class FileUtil extends PathUtil {
return writeString(content, file, CharsetUtil.UTF_8);
}
/**
* 将String写入文件覆盖模式
*
* @param content 写入的内容
* @param path 文件路径
* @param charset 字符集
* @return 写入的文件
* @throws IORuntimeException IO异常
*/
public static File writeString(final String content, final String path, final String charset) throws IORuntimeException {
return writeString(content, touch(path), charset);
}
/**
* 将String写入文件覆盖模式
*
@ -2202,19 +2142,6 @@ public class FileUtil extends PathUtil {
return writeString(content, touch(path), charset);
}
/**
* 将String写入文件覆盖模式
*
* @param content 写入的内容
* @param file 文件
* @param charset 字符集
* @return 被写入的文件
* @throws IORuntimeException IO异常
*/
public static File writeString(final String content, final File file, final String charset) throws IORuntimeException {
return FileWriter.of(file, CharsetUtil.charset(charset)).write(content);
}
/**
* 将String写入文件覆盖模式
*
@ -2241,19 +2168,6 @@ public class FileUtil extends PathUtil {
return appendString(content, path, CharsetUtil.UTF_8);
}
/**
* 将String写入文件追加模式
*
* @param content 写入的内容
* @param path 文件路径
* @param charset 字符集
* @return 写入的文件
* @throws IORuntimeException IO异常
*/
public static File appendString(final String content, final String path, final String charset) throws IORuntimeException {
return appendString(content, touch(path), charset);
}
/**
* 将String写入文件追加模式
*
@ -2280,19 +2194,6 @@ public class FileUtil extends PathUtil {
return appendString(content, file, CharsetUtil.UTF_8);
}
/**
* 将String写入文件追加模式
*
* @param content 写入的内容
* @param file 文件
* @param charset 字符集
* @return 写入的文件
* @throws IORuntimeException IO异常
*/
public static File appendString(final String content, final File file, final String charset) throws IORuntimeException {
return FileWriter.of(file, CharsetUtil.charset(charset)).append(content);
}
/**
* 将String写入文件追加模式
*
@ -2334,20 +2235,6 @@ public class FileUtil extends PathUtil {
return writeLines(list, file, CharsetUtil.UTF_8);
}
/**
* 将列表写入文件覆盖模式
*
* @param <T> 集合元素类型
* @param list 列表
* @param path 绝对路径
* @param charset 字符集
* @return 目标文件
* @throws IORuntimeException IO异常
*/
public static <T> File writeLines(final Collection<T> list, final String path, final String charset) throws IORuntimeException {
return writeLines(list, path, charset, false);
}
/**
* 将列表写入文件覆盖模式
*
@ -2362,21 +2249,6 @@ public class FileUtil extends PathUtil {
return writeLines(list, path, charset, false);
}
/**
* 将列表写入文件覆盖模式
*
* @param <T> 集合元素类型
* @param list 列表
* @param file 文件
* @param charset 字符集
* @return 目标文件
* @throws IORuntimeException IO异常
* @since 4.2.0
*/
public static <T> File writeLines(final Collection<T> list, final File file, final String charset) throws IORuntimeException {
return writeLines(list, file, charset, false);
}
/**
* 将列表写入文件覆盖模式
*
@ -2420,35 +2292,6 @@ public class FileUtil extends PathUtil {
return appendLines(list, path, CharsetUtil.UTF_8);
}
/**
* 将列表写入文件追加模式
*
* @param <T> 集合元素类型
* @param list 列表
* @param path 绝对路径
* @param charset 字符集
* @return 目标文件
* @throws IORuntimeException IO异常
*/
public static <T> File appendLines(final Collection<T> list, final String path, final String charset) throws IORuntimeException {
return writeLines(list, path, charset, true);
}
/**
* 将列表写入文件追加模式
*
* @param <T> 集合元素类型
* @param list 列表
* @param file 文件
* @param charset 字符集
* @return 目标文件
* @throws IORuntimeException IO异常
* @since 3.1.2
*/
public static <T> File appendLines(final Collection<T> list, final File file, final String charset) throws IORuntimeException {
return writeLines(list, file, charset, true);
}
/**
* 将列表写入文件追加模式
*
@ -2483,21 +2326,6 @@ public class FileUtil extends PathUtil {
return writeLines(list, file, charset, true);
}
/**
* 将列表写入文件
*
* @param <T> 集合元素类型
* @param list 列表
* @param path 文件路径
* @param charset 字符集
* @param isAppend 是否追加
* @return 目标文件
* @throws IORuntimeException IO异常
*/
public static <T> File writeLines(final Collection<T> list, final String path, final String charset, final boolean isAppend) throws IORuntimeException {
return writeLines(list, file(path), charset, isAppend);
}
/**
* 将列表写入文件
*
@ -2513,21 +2341,6 @@ public class FileUtil extends PathUtil {
return writeLines(list, file(path), charset, isAppend);
}
/**
* 将列表写入文件
*
* @param <T> 集合元素类型
* @param list 列表
* @param file 文件
* @param charset 字符集
* @param isAppend 是否追加
* @return 目标文件
* @throws IORuntimeException IO异常
*/
public static <T> File writeLines(final Collection<T> list, final File file, final String charset, final boolean isAppend) throws IORuntimeException {
return FileWriter.of(file, CharsetUtil.charset(charset)).writeLines(list, isAppend);
}
/**
* 将列表写入文件
*

View File

@ -74,16 +74,6 @@ public class FileWriter extends FileWrapper {
checkFile();
}
/**
* 构造
*
* @param file 文件
* @param charset 编码使用 {@link CharsetUtil#charset(String)}
*/
public FileWriter(final File file, final String charset) {
this(file, CharsetUtil.charset(charset));
}
/**
* 构造
*

View File

@ -1285,8 +1285,12 @@ public class MapUtil extends MapGetUtil {
if (JdkUtil.IS_JDK8) {
V value = map.get(key);
if (null == value) {
//map.putIfAbsent(key, mappingFunction.apply(key));
value = map.computeIfAbsent(key, mappingFunction);
map.putIfAbsent(key, mappingFunction.apply(key));
value = map.get(key);
// 判空后调用依旧无法解决死循环问题
// Issue2349Test
//value = map.computeIfAbsent(key, mappingFunction);
}
return value;
} else {

View File

@ -12,6 +12,8 @@
package cn.hutool.core.map;
import cn.hutool.core.util.JdkUtil;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@ -43,7 +45,7 @@ public class SafeConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
*
* @param initialCapacity 预估初始大小
*/
public SafeConcurrentHashMap(int initialCapacity) {
public SafeConcurrentHashMap(final int initialCapacity) {
super(initialCapacity);
}
@ -52,7 +54,7 @@ public class SafeConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
*
* @param m 初始键值对
*/
public SafeConcurrentHashMap(Map<? extends K, ? extends V> m) {
public SafeConcurrentHashMap(final Map<? extends K, ? extends V> m) {
super(m);
}
@ -62,7 +64,7 @@ public class SafeConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
* @param initialCapacity 初始容量
* @param loadFactor 增长系数
*/
public SafeConcurrentHashMap(int initialCapacity, float loadFactor) {
public SafeConcurrentHashMap(final int initialCapacity, final float loadFactor) {
super(initialCapacity, loadFactor);
}
@ -73,14 +75,27 @@ public class SafeConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
* @param loadFactor 增长系数
* @param concurrencyLevel 并发级别即Segment的个数
*/
public SafeConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
public SafeConcurrentHashMap(final int initialCapacity,
final float loadFactor, final int concurrencyLevel) {
super(initialCapacity, loadFactor, concurrencyLevel);
}
// endregion == 构造 ==
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
return MapUtil.computeIfAbsent(this, key, mappingFunction);
public V computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) {
if (JdkUtil.IS_JDK8) {
V value = get(key);
if (null == value) {
putIfAbsent(key, mappingFunction.apply(key));
value = get(key);
// 判空后调用依旧无法解决死循环问题
// Issue2349Test
//value = map.computeIfAbsent(key, mappingFunction);
}
return value;
} else {
return super.computeIfAbsent(key, mappingFunction);
}
}
}

View File

@ -191,17 +191,6 @@ public class UrlQueryUtil {
return Convert.toMap(String.class, String.class, queryMap);
}
/**
* 将URL参数解析为Map也可以解析Post中的键值对参数
*
* @param paramsStr 参数字符串或者带参数的Path
* @param charset 字符集
* @return 参数Map
*/
public static Map<String, List<String>> decodeQuery(final String paramsStr, final String charset) {
return decodeQueryList(paramsStr, CharsetUtil.charset(charset));
}
/**
* 将URL参数解析为Map也可以解析Post中的键值对参数
*

View File

@ -164,17 +164,6 @@ public class StrUtil extends CharSequenceUtil implements StrPool {
return obj.toString();
}
/**
* 将byte数组转为字符串
*
* @param bytes byte数组
* @param charset 字符集
* @return 字符串
*/
public static String str(final byte[] bytes, final String charset) {
return str(bytes, CharsetUtil.charset(charset));
}
/**
* 解码字节码
*

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package cn.hutool.core.compress;
import cn.hutool.core.io.file.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import org.junit.jupiter.api.Test;
public class Issue3018Test {
@Test
void unzipTest() {
ZipUtil.unzip(FileUtil.getInputStream("d:/test/default.zip"),
FileUtil.file("d:/test/"),
CharsetUtil.UTF_8
);
}
@Test
void unzipFromFileTest() {
ZipUtil.unzip("d:/test/default.zip");
}
}

View File

@ -11,7 +11,7 @@ public class UrlQueryUtilTest {
@Test
public void decodeQueryTest() {
final String paramsStr = "uuuu=0&a=b&c=%3F%23%40!%24%25%5E%26%3Ddsssss555555";
final Map<String, List<String>> map = UrlQueryUtil.decodeQuery(paramsStr, CharsetUtil.NAME_UTF_8);
final Map<String, List<String>> map = UrlQueryUtil.decodeQueryList(paramsStr, CharsetUtil.UTF_8);
Assertions.assertEquals("0", map.get("uuuu").get(0));
Assertions.assertEquals("b", map.get("a").get(0));
Assertions.assertEquals("?#@!$%^&=dsssss555555", map.get("c").get(0));
@ -28,7 +28,7 @@ public class UrlQueryUtilTest {
@Test
public void toQueryTest() {
final String paramsStr = "uuuu=0&a=b&c=3Ddsssss555555";
final Map<String, List<String>> map = UrlQueryUtil.decodeQuery(paramsStr, CharsetUtil.NAME_UTF_8);
final Map<String, List<String>> map = UrlQueryUtil.decodeQueryList(paramsStr, CharsetUtil.UTF_8);
final String encodedParams = UrlQueryUtil.toQuery(map);
Assertions.assertEquals(paramsStr, encodedParams);
@ -91,41 +91,41 @@ public class UrlQueryUtilTest {
public void decodeParamTest() {
// 开头的被去除
String a = "?a=b&c=d&";
Map<String, List<String>> map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
Map<String, List<String>> map = UrlQueryUtil.decodeQueryList(a, CharsetUtil.UTF_8);
Assertions.assertEquals("b", map.get("a").get(0));
Assertions.assertEquals("d", map.get("c").get(0));
// =e被当作空为keye为value
a = "?a=b&c=d&=e";
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
map = UrlQueryUtil.decodeQueryList(a, CharsetUtil.UTF_8);
Assertions.assertEquals("b", map.get("a").get(0));
Assertions.assertEquals("d", map.get("c").get(0));
Assertions.assertEquals("e", map.get("").get(0));
// 多余的&去除
a = "?a=b&c=d&=e&&&&";
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
map = UrlQueryUtil.decodeQueryList(a, CharsetUtil.UTF_8);
Assertions.assertEquals("b", map.get("a").get(0));
Assertions.assertEquals("d", map.get("c").get(0));
Assertions.assertEquals("e", map.get("").get(0));
// 值为空
a = "?a=b&c=d&e=";
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
map = UrlQueryUtil.decodeQueryList(a, CharsetUtil.UTF_8);
Assertions.assertEquals("b", map.get("a").get(0));
Assertions.assertEquals("d", map.get("c").get(0));
Assertions.assertEquals("", map.get("e").get(0));
// &=被作为键和值都为空
a = "a=b&c=d&=";
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
map = UrlQueryUtil.decodeQueryList(a, CharsetUtil.UTF_8);
Assertions.assertEquals("b", map.get("a").get(0));
Assertions.assertEquals("d", map.get("c").get(0));
Assertions.assertEquals("", map.get("").get(0));
// &e&这类单独的字符串被当作key
a = "a=b&c=d&e&";
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
map = UrlQueryUtil.decodeQueryList(a, CharsetUtil.UTF_8);
Assertions.assertEquals("b", map.get("a").get(0));
Assertions.assertEquals("d", map.get("c").get(0));
Assertions.assertNull(map.get("e").get(0));
@ -133,7 +133,7 @@ public class UrlQueryUtilTest {
// 被编码的键和值被还原
a = "a=bbb&c=%E4%BD%A0%E5%A5%BD&%E5%93%88%E5%96%BD=";
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
map = UrlQueryUtil.decodeQueryList(a, CharsetUtil.UTF_8);
Assertions.assertEquals("bbb", map.get("a").get(0));
Assertions.assertEquals("你好", map.get("c").get(0));
Assertions.assertEquals("", map.get("哈喽").get(0));

View File

@ -43,7 +43,6 @@
<zxing.version>3.5.1</zxing.version>
<net.version>3.9.0</net.version>
<emoji-java.version>5.1.1</emoji-java.version>
<servlet-api.version>4.0.1</servlet-api.version>
<spring-boot.version>2.7.5</spring-boot.version>
<cglib.version>3.3.0</cglib.version>
</properties>
@ -64,21 +63,6 @@
<artifactId>hutool-setting</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<!-- 版本固定5.0.0可以支持jdk8 -->
<version>5.0.0</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<!-- 模板引擎 -->
<dependency>

View File

@ -50,6 +50,21 @@
<scope>provided</scope>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<!-- 版本固定5.0.0可以支持jdk8 -->
<version>5.0.0</version>
<optional>true</optional>
</dependency>
<!-- 第三方HTTP客户端库 -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>

View File

@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/
package cn.hutool.extra.servlet;
package cn.hutool.http.server.servlet;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
@ -33,6 +33,7 @@ import cn.hutool.core.array.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.meta.Method;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.Cookie;
@ -66,14 +67,6 @@ import java.util.Map;
*/
public class JakartaServletUtil {
public static final String METHOD_DELETE = "DELETE";
public static final String METHOD_HEAD = "HEAD";
public static final String METHOD_GET = "GET";
public static final String METHOD_OPTIONS = "OPTIONS";
public static final String METHOD_POST = "POST";
public static final String METHOD_PUT = "PUT";
public static final String METHOD_TRACE = "TRACE";
// --------------------------------------------------------- getParam start
/**
@ -373,18 +366,6 @@ public class JakartaServletUtil {
return null;
}
/**
* 获得请求header中的信息
*
* @param request 请求对象{@link HttpServletRequest}
* @param name 头信息的KEY
* @param charsetName 字符集
* @return header值
*/
public static String getHeader(final HttpServletRequest request, final String name, final String charsetName) {
return getHeader(request, name, CharsetUtil.charset(charsetName));
}
/**
* 获得请求header中的信息
*
@ -425,7 +406,7 @@ public class JakartaServletUtil {
* @return 是否为GET请求
*/
public static boolean isGetMethod(final HttpServletRequest request) {
return METHOD_GET.equalsIgnoreCase(request.getMethod());
return Method.GET.name().equalsIgnoreCase(request.getMethod());
}
/**
@ -435,7 +416,7 @@ public class JakartaServletUtil {
* @return 是否为POST请求
*/
public static boolean isPostMethod(final HttpServletRequest request) {
return METHOD_POST.equalsIgnoreCase(request.getMethod());
return Method.POST.name().equalsIgnoreCase(request.getMethod());
}
/**

View File

@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/
package cn.hutool.extra.servlet;
package cn.hutool.http.server.servlet;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
@ -32,6 +32,7 @@ import cn.hutool.core.text.StrUtil;
import cn.hutool.core.array.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.http.meta.Method;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
@ -65,14 +66,6 @@ import java.util.Map;
*/
public class ServletUtil {
public static final String METHOD_DELETE = "DELETE";
public static final String METHOD_HEAD = "HEAD";
public static final String METHOD_GET = "GET";
public static final String METHOD_OPTIONS = "OPTIONS";
public static final String METHOD_POST = "POST";
public static final String METHOD_PUT = "PUT";
public static final String METHOD_TRACE = "TRACE";
// --------------------------------------------------------- getParam start
/**
@ -424,7 +417,7 @@ public class ServletUtil {
* @return 是否为GET请求
*/
public static boolean isGetMethod(final HttpServletRequest request) {
return METHOD_GET.equalsIgnoreCase(request.getMethod());
return Method.GET.name().equalsIgnoreCase(request.getMethod());
}
/**
@ -434,7 +427,7 @@ public class ServletUtil {
* @return 是否为POST请求
*/
public static boolean isPostMethod(final HttpServletRequest request) {
return METHOD_POST.equalsIgnoreCase(request.getMethod());
return Method.POST.name().equalsIgnoreCase(request.getMethod());
}
/**

View File

@ -0,0 +1,19 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
/**
* Servlet封装包括Servlet参数获取文件上传Response写出等入口为ServletUtil
*
* @author looly
*
*/
package cn.hutool.http.server.servlet;

View File

@ -1,4 +1,16 @@
package cn.hutool.extra.servlet;
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package cn.hutool.http.server.servlet;
import cn.hutool.core.util.ByteUtil;
import org.junit.jupiter.api.Disabled;