This commit is contained in:
Looly
2022-10-26 12:34:05 +08:00
parent c79cdc1f6f
commit 36e560842d
13 changed files with 463 additions and 169 deletions

View File

@@ -0,0 +1,239 @@
package cn.hutool.core.io;
import java.util.Objects;
/**
* 循环缓冲区
*
* @author apache commons io
*/
public class CircularByteBuffer {
private final byte[] buffer;
private int startOffset;
private int endOffset;
private int currentNumberOfBytes;
/**
* 默认缓冲大小的构造({@link IoUtil#DEFAULT_BUFFER_SIZE})
*/
public CircularByteBuffer() {
this(IoUtil.DEFAULT_BUFFER_SIZE);
}
/**
* 构造
*
* @param pSize 缓冲大小
*/
public CircularByteBuffer(final int pSize) {
buffer = new byte[pSize];
startOffset = 0;
endOffset = 0;
currentNumberOfBytes = 0;
}
/**
* 从buffer中读取下一个byte同时移除这个bytes。
*
* @return The byte
* @throws IllegalStateException buffer为空抛出使用{@link #hasBytes()},或 {@link #getCurrentNumberOfBytes()}判断
*/
public byte read() {
if (currentNumberOfBytes <= 0) {
throw new IllegalStateException("No bytes available.");
}
final byte b = buffer[startOffset];
--currentNumberOfBytes;
if (++startOffset == buffer.length) {
startOffset = 0;
}
return b;
}
/**
* Returns the given number of bytes from the buffer by storing them in
* the given byte array at the given offset.
* 从buffer中获取指定长度的bytes从给定的targetBuffer的targetOffset位置写出
*
* @param targetBuffer 目标bytes
* @param targetOffset 目标数组开始位置
* @param length 读取长度
* @throws NullPointerException 提供的数组为{@code null}
* @throws IllegalArgumentException {@code targetOffset}或{@code length} 为负数或{@code targetBuffer}太小
* @throws IllegalStateException buffer中的byte不足使用{@link #getCurrentNumberOfBytes()}判断。
*/
public void read(final byte[] targetBuffer, final int targetOffset, final int length) {
Objects.requireNonNull(targetBuffer);
if (targetOffset < 0 || targetOffset >= targetBuffer.length) {
throw new IllegalArgumentException("Invalid offset: " + targetOffset);
}
if (length < 0 || length > buffer.length) {
throw new IllegalArgumentException("Invalid length: " + length);
}
if (targetOffset + length > targetBuffer.length) {
throw new IllegalArgumentException("The supplied byte array contains only "
+ targetBuffer.length + " bytes, but offset, and length would require "
+ (targetOffset + length - 1));
}
if (currentNumberOfBytes < length) {
throw new IllegalStateException("Currently, there are only " + currentNumberOfBytes
+ "in the buffer, not " + length);
}
int offset = targetOffset;
for (int i = 0; i < length; i++) {
targetBuffer[offset++] = buffer[startOffset];
--currentNumberOfBytes;
if (++startOffset == buffer.length) {
startOffset = 0;
}
}
}
/**
* 增加byte到buffer中
*
* @param value The byte
* @throws IllegalStateException buffer已满. 用{@link #hasSpace()}或{@link #getSpace()}判断。
*/
public void add(final byte value) {
if (currentNumberOfBytes >= buffer.length) {
throw new IllegalStateException("No space available");
}
buffer[endOffset] = value;
++currentNumberOfBytes;
if (++endOffset == buffer.length) {
endOffset = 0;
}
}
/**
* Returns, whether the next bytes in the buffer are exactly those, given by
* {@code sourceBuffer}, {@code offset}, and {@code length}. No bytes are being
* removed from the buffer. If the result is true, then the following invocations
* of {@link #read()} are guaranteed to return exactly those bytes.
*
* @param sourceBuffer the buffer to compare against
* @param offset start offset
* @param length length to compare
* @return True, if the next invocations of {@link #read()} will return the
* bytes at offsets {@code pOffset}+0, {@code pOffset}+1, ...,
* {@code pOffset}+{@code pLength}-1 of byte array {@code pBuffer}.
* @throws IllegalArgumentException Either of {@code pOffset}, or {@code pLength} is negative.
* @throws NullPointerException The byte array {@code pBuffer} is null.
*/
public boolean peek(final byte[] sourceBuffer, final int offset, final int length) {
Objects.requireNonNull(sourceBuffer, "Buffer");
if (offset < 0 || offset >= sourceBuffer.length) {
throw new IllegalArgumentException("Invalid offset: " + offset);
}
if (length < 0 || length > buffer.length) {
throw new IllegalArgumentException("Invalid length: " + length);
}
if (length < currentNumberOfBytes) {
return false;
}
int localOffset = startOffset;
for (int i = 0; i < length; i++) {
if (buffer[localOffset] != sourceBuffer[i + offset]) {
return false;
}
if (++localOffset == buffer.length) {
localOffset = 0;
}
}
return true;
}
/**
* Adds the given bytes to the buffer. This is the same as invoking {@link #add(byte)}
* for the bytes at offsets {@code offset+0}, {@code offset+1}, ...,
* {@code offset+length-1} of byte array {@code targetBuffer}.
*
* @param targetBuffer the buffer to copy
* @param offset start offset
* @param length length to copy
* @throws IllegalStateException The buffer doesn't have sufficient space. Use
* {@link #getSpace()} to prevent this exception.
* @throws IllegalArgumentException Either of {@code pOffset}, or {@code pLength} is negative.
* @throws NullPointerException The byte array {@code pBuffer} is null.
*/
public void add(final byte[] targetBuffer, final int offset, final int length) {
Objects.requireNonNull(targetBuffer, "Buffer");
if (offset < 0 || offset >= targetBuffer.length) {
throw new IllegalArgumentException("Invalid offset: " + offset);
}
if (length < 0) {
throw new IllegalArgumentException("Invalid length: " + length);
}
if (currentNumberOfBytes + length > buffer.length) {
throw new IllegalStateException("No space available");
}
for (int i = 0; i < length; i++) {
buffer[endOffset] = targetBuffer[offset + i];
if (++endOffset == buffer.length) {
endOffset = 0;
}
}
currentNumberOfBytes += length;
}
/**
* Returns, whether there is currently room for a single byte in the buffer.
* Same as {@link #hasSpace(int) hasSpace(1)}.
*
* @return true if there is space for a byte
* @see #hasSpace(int)
* @see #getSpace()
*/
public boolean hasSpace() {
return currentNumberOfBytes < buffer.length;
}
/**
* Returns, whether there is currently room for the given number of bytes in the buffer.
*
* @param count the byte count
* @return true if there is space for the given number of bytes
* @see #hasSpace()
* @see #getSpace()
*/
public boolean hasSpace(final int count) {
return currentNumberOfBytes + count <= buffer.length;
}
/**
* Returns, whether the buffer is currently holding, at least, a single byte.
*
* @return true if the buffer is not empty
*/
public boolean hasBytes() {
return currentNumberOfBytes > 0;
}
/**
* Returns the number of bytes, that can currently be added to the buffer.
*
* @return the number of bytes that can be added
*/
public int getSpace() {
return buffer.length - currentNumberOfBytes;
}
/**
* Returns the number of bytes, that are currently present in the buffer.
*
* @return the number of bytes
*/
public int getCurrentNumberOfBytes() {
return currentNumberOfBytes;
}
/**
* Removes all bytes from the buffer.
*/
public void clear() {
startOffset = 0;
endOffset = 0;
currentNumberOfBytes = 0;
}
}

View File

@@ -1,6 +1,6 @@
package cn.hutool.core.io;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.compress.ZipUtil;
import cn.hutool.core.io.file.FileCopier;
import cn.hutool.core.io.file.FileMode;
import cn.hutool.core.io.file.FileNameUtil;
@@ -14,15 +14,15 @@ import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.func.SerConsumer;
import cn.hutool.core.net.url.URLUtil;
import cn.hutool.core.reflect.ClassUtil;
import cn.hutool.core.regex.ReUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.reflect.ClassUtil;
import cn.hutool.core.regex.ReUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.net.url.URLUtil;
import cn.hutool.core.compress.ZipUtil;
import cn.hutool.core.util.SystemUtil;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -52,7 +52,6 @@ import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -410,16 +409,6 @@ public class FileUtil extends PathUtil {
return new File(URLUtil.toURI(url));
}
/**
* 获取临时文件路径(绝对路径)
*
* @return 临时文件路径
* @since 4.0.6
*/
public static String getTmpDirPath() {
return System.getProperty("java.io.tmpdir");
}
/**
* 获取临时文件目录
*
@@ -427,17 +416,7 @@ public class FileUtil extends PathUtil {
* @since 4.0.6
*/
public static File getTmpDir() {
return file(getTmpDirPath());
}
/**
* 获取用户路径(绝对路径)
*
* @return 用户路径
* @since 4.0.6
*/
public static String getUserHomePath() {
return System.getProperty("user.home");
return file(SystemUtil.getTmpDirPath());
}
/**
@@ -447,7 +426,7 @@ public class FileUtil extends PathUtil {
* @since 4.0.6
*/
public static File getUserHomeDir() {
return file(getUserHomePath());
return file(SystemUtil.getUserHomePath());
}
/**
@@ -1232,7 +1211,7 @@ public class FileUtil extends PathUtil {
if (path == null) {
normalPath = StrUtil.EMPTY;
} else {
normalPath = normalize(path);
normalPath = FileNameUtil.normalize(path);
if (isAbsolutePath(normalPath)) {
// 给定的路径已经是绝对路径了
return normalPath;
@@ -1243,7 +1222,7 @@ public class FileUtil extends PathUtil {
final URL url = ResourceUtil.getResourceUrl(normalPath, baseClass);
if (null != url) {
// 对于jar中文件包含file:前缀需要去掉此类前缀在此做标准化since 3.0.8 解决中文或空格路径被编码的问题
return FileUtil.normalize(URLUtil.getDecodedPath(url));
return FileNameUtil.normalize(URLUtil.getDecodedPath(url));
}
// 如果资源不存在,则返回一个拼接的资源绝对路径
@@ -1255,7 +1234,7 @@ public class FileUtil extends PathUtil {
}
// 资源不存在的情况下使用标准化路径有问题,使用原始路径拼接后标准化路径
return normalize(classPath.concat(Objects.requireNonNull(path)));
return FileNameUtil.normalize(classPath.concat(Objects.requireNonNull(path)));
}
/**
@@ -1290,7 +1269,7 @@ public class FileUtil extends PathUtil {
/**
* 给定路径已经是绝对路径<br>
* 此方法并没有针对路径做标准化,建议先执行{@link #normalize(String)}方法标准化路径后判断<br>
* 此方法并没有针对路径做标准化,建议先执行{@link FileNameUtil#normalize(String)}方法标准化路径后判断<br>
* 绝对路径判断条件是:
* <ul>
* <li>以/开头的路径</li>
@@ -1573,84 +1552,7 @@ public class FileUtil extends PathUtil {
* @return 修复后的路径
*/
public static String normalize(final String path) {
if (path == null) {
return null;
}
// 兼容Spring风格的ClassPath路径去除前缀不区分大小写
String pathToUse = StrUtil.removePrefixIgnoreCase(path, URLUtil.CLASSPATH_URL_PREFIX);
// 去除file:前缀
pathToUse = StrUtil.removePrefixIgnoreCase(pathToUse, URLUtil.FILE_URL_PREFIX);
// 识别home目录形式并转换为绝对路径
if (StrUtil.startWith(pathToUse, '~')) {
pathToUse = getUserHomePath() + pathToUse.substring(1);
}
// 统一使用斜杠
pathToUse = pathToUse.replaceAll("[/\\\\]+", StrUtil.SLASH);
// 去除开头空白符,末尾空白符合法,不去除
pathToUse = StrUtil.trimStart(pathToUse);
//兼容Windows下的共享目录路径原始路径如果以\\开头,则保留这种路径)
if (path.startsWith("\\\\")) {
pathToUse = "\\" + pathToUse;
}
String prefix = StrUtil.EMPTY;
final int prefixIndex = pathToUse.indexOf(StrUtil.COLON);
if (prefixIndex > -1) {
// 可能Windows风格路径
prefix = pathToUse.substring(0, prefixIndex + 1);
if (StrUtil.startWith(prefix, CharUtil.SLASH)) {
// 去除类似于/C:这类路径开头的斜杠
prefix = prefix.substring(1);
}
if (false == prefix.contains(StrUtil.SLASH)) {
pathToUse = pathToUse.substring(prefixIndex + 1);
} else {
// 如果前缀中包含/,说明非Windows风格path
prefix = StrUtil.EMPTY;
}
}
if (pathToUse.startsWith(StrUtil.SLASH)) {
prefix += StrUtil.SLASH;
pathToUse = pathToUse.substring(1);
}
final List<String> pathList = StrUtil.split(pathToUse, CharUtil.SLASH);
final List<String> pathElements = new LinkedList<>();
int tops = 0;
String element;
for (int i = pathList.size() - 1; i >= 0; i--) {
element = pathList.get(i);
// 只处理非.的目录,即只处理非当前目录
if (false == StrUtil.DOT.equals(element)) {
if (StrUtil.DOUBLE_DOT.equals(element)) {
tops++;
} else {
if (tops > 0) {
// 有上级目录标记时按照个数依次跳过
tops--;
} else {
// Normal path element found.
pathElements.add(0, element);
}
}
}
}
// issue#1703@Github
if (tops > 0 && StrUtil.isEmpty(prefix)) {
// 只有相对路径补充开头的..,绝对路径直接忽略之
while (tops-- > 0) {
//遍历完节点发现还有上级标注(即开头有一个或多个..),补充之
// Normal path element found.
pathElements.add(0, StrUtil.DOUBLE_DOT);
}
}
return prefix + CollUtil.join(pathElements, StrUtil.SLASH);
return FileNameUtil.normalize(path);
}
/**
@@ -1693,8 +1595,8 @@ public class FileUtil extends PathUtil {
public static String subPath(String dirPath, String filePath) {
if (StrUtil.isNotEmpty(dirPath) && StrUtil.isNotEmpty(filePath)) {
dirPath = StrUtil.removeSuffix(normalize(dirPath), "/");
filePath = normalize(filePath);
dirPath = StrUtil.removeSuffix(FileNameUtil.normalize(dirPath), "/");
filePath = FileNameUtil.normalize(filePath);
final String result = StrUtil.removePrefixIgnoreCase(filePath, dirPath);
return StrUtil.removePrefix(result, "/");

View File

@@ -738,6 +738,19 @@ public class IoUtil extends NioUtil {
return new ByteArrayInputStream(out.toByteArray());
}
/**
* {@link FastByteArrayOutputStream}转为{@link ByteArrayInputStream}
*
* @param out {@link FastByteArrayOutputStream}
* @return 字节流
*/
public static ByteArrayInputStream toStream(final FastByteArrayOutputStream out) {
if (out == null) {
return null;
}
return new ByteArrayInputStream(out.toByteArray());
}
/**
* 转换为{@link BufferedInputStream}
*

View File

@@ -1,10 +1,15 @@
package cn.hutool.core.io.file;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.net.url.URLUtil;
import cn.hutool.core.regex.ReUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.SystemUtil;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
/**
@@ -286,5 +291,119 @@ public class FileNameUtil {
public static boolean isType(final String fileName, final String... extNames) {
return StrUtil.equalsAnyIgnoreCase(extName(fileName), extNames);
}
/**
* 修复路径<br>
* 如果原路径尾部有分隔符,则保留为标准分隔符(/),否则不保留
* <ol>
* <li>1. 统一用 /</li>
* <li>2. 多个 / 转换为一个 /</li>
* <li>3. 去除左边空格</li>
* <li>4. .. 和 . 转换为绝对路径,当..多于已有路径时,直接返回根路径</li>
* </ol>
* <p>
* 栗子:
*
* <pre>
* "/foo//" =》 "/foo/"
* "/foo/./" =》 "/foo/"
* "/foo/../bar" =》 "/bar"
* "/foo/../bar/" =》 "/bar/"
* "/foo/../bar/../baz" =》 "/baz"
* "/../" =》 "/"
* "foo/bar/.." =》 "foo"
* "foo/../bar" =》 "bar"
* "foo/../../bar" =》 "bar"
* "//server/foo/../bar" =》 "/server/bar"
* "//server/../bar" =》 "/bar"
* "C:\\foo\\..\\bar" =》 "C:/bar"
* "C:\\..\\bar" =》 "C:/bar"
* "~/foo/../bar/" =》 "~/bar/"
* "~/../bar" =》 普通用户运行是'bar的home目录'ROOT用户运行是'/bar'
* </pre>
*
* @param path 原路径
* @return 修复后的路径
*/
public static String normalize(final String path) {
if (path == null) {
return null;
}
// 兼容Spring风格的ClassPath路径去除前缀不区分大小写
String pathToUse = StrUtil.removePrefixIgnoreCase(path, URLUtil.CLASSPATH_URL_PREFIX);
// 去除file:前缀
pathToUse = StrUtil.removePrefixIgnoreCase(pathToUse, URLUtil.FILE_URL_PREFIX);
// 识别home目录形式并转换为绝对路径
if (StrUtil.startWith(pathToUse, '~')) {
pathToUse = SystemUtil.getUserHomePath() + pathToUse.substring(1);
}
// 统一使用斜杠
pathToUse = pathToUse.replaceAll("[/\\\\]+", StrUtil.SLASH);
// 去除开头空白符,末尾空白符合法,不去除
pathToUse = StrUtil.trimStart(pathToUse);
//兼容Windows下的共享目录路径原始路径如果以\\开头,则保留这种路径)
if (path.startsWith("\\\\")) {
pathToUse = "\\" + pathToUse;
}
String prefix = StrUtil.EMPTY;
final int prefixIndex = pathToUse.indexOf(StrUtil.COLON);
if (prefixIndex > -1) {
// 可能Windows风格路径
prefix = pathToUse.substring(0, prefixIndex + 1);
if (StrUtil.startWith(prefix, CharUtil.SLASH)) {
// 去除类似于/C:这类路径开头的斜杠
prefix = prefix.substring(1);
}
if (false == prefix.contains(StrUtil.SLASH)) {
pathToUse = pathToUse.substring(prefixIndex + 1);
} else {
// 如果前缀中包含/,说明非Windows风格path
prefix = StrUtil.EMPTY;
}
}
if (pathToUse.startsWith(StrUtil.SLASH)) {
prefix += StrUtil.SLASH;
pathToUse = pathToUse.substring(1);
}
final List<String> pathList = StrUtil.split(pathToUse, CharUtil.SLASH);
final List<String> pathElements = new LinkedList<>();
int tops = 0;
String element;
for (int i = pathList.size() - 1; i >= 0; i--) {
element = pathList.get(i);
// 只处理非.的目录,即只处理非当前目录
if (false == StrUtil.DOT.equals(element)) {
if (StrUtil.DOUBLE_DOT.equals(element)) {
tops++;
} else {
if (tops > 0) {
// 有上级目录标记时按照个数依次跳过
tops--;
} else {
// Normal path element found.
pathElements.add(0, element);
}
}
}
}
// issue#1703@Github
if (tops > 0 && StrUtil.isEmpty(prefix)) {
// 只有相对路径补充开头的..,绝对路径直接忽略之
while (tops-- > 0) {
//遍历完节点发现还有上级标注(即开头有一个或多个..),补充之
// Normal path element found.
pathElements.add(0, StrUtil.DOUBLE_DOT);
}
}
return prefix + CollUtil.join(pathElements, StrUtil.SLASH);
}
// -------------------------------------------------------------------------------------------- name end
}

View File

@@ -147,4 +147,23 @@ public class SystemUtil {
public static String[] getJavaClassPaths() {
return get("java.class.path").split(get("path.separator"));
}
/**
* 获取用户路径(绝对路径)
*
* @return 用户路径
*/
public static String getUserHomePath() {
return get("user.home");
}
/**
* 获取临时文件路径(绝对路径)
*
* @return 临时文件路径
* @since 4.0.6
*/
public static String getTmpDirPath() {
return get("java.io.tmpdir");
}
}

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.file.LineSeparator;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.SystemUtil;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@@ -141,42 +142,15 @@ public class FileUtilTest {
FileUtil.convertLineSeparator(FileUtil.file("d:/aaa.txt"), CharsetUtil.UTF_8, LineSeparator.WINDOWS);
}
@Test
public void normalizeTest() {
Assert.assertEquals("/foo/", FileUtil.normalize("/foo//"));
Assert.assertEquals("/foo/", FileUtil.normalize("/foo/./"));
Assert.assertEquals("/bar", FileUtil.normalize("/foo/../bar"));
Assert.assertEquals("/bar/", FileUtil.normalize("/foo/../bar/"));
Assert.assertEquals("/baz", FileUtil.normalize("/foo/../bar/../baz"));
Assert.assertEquals("/", FileUtil.normalize("/../"));
Assert.assertEquals("foo", FileUtil.normalize("foo/bar/.."));
Assert.assertEquals("../bar", FileUtil.normalize("foo/../../bar"));
Assert.assertEquals("bar", FileUtil.normalize("foo/../bar"));
Assert.assertEquals("/server/bar", FileUtil.normalize("//server/foo/../bar"));
Assert.assertEquals("/bar", FileUtil.normalize("//server/../bar"));
Assert.assertEquals("C:/bar", FileUtil.normalize("C:\\foo\\..\\bar"));
//
Assert.assertEquals("C:/bar", FileUtil.normalize("C:\\..\\bar"));
Assert.assertEquals("../../bar", FileUtil.normalize("../../bar"));
Assert.assertEquals("C:/bar", FileUtil.normalize("/C:/bar"));
Assert.assertEquals("C:", FileUtil.normalize("C:"));
Assert.assertEquals("\\/192.168.1.1/Share/", FileUtil.normalize("\\\\192.168.1.1\\Share\\"));
}
@Test
public void normalizeBlankTest() {
Assert.assertEquals("C:/aaa ", FileUtil.normalize("C:\\aaa "));
}
@Test
public void normalizeHomePathTest() {
final String home = FileUtil.getUserHomePath().replace('\\', '/');
final String home = SystemUtil.getUserHomePath().replace('\\', '/');
Assert.assertEquals(home + "/bar/", FileUtil.normalize("~/foo/../bar/"));
}
@Test
public void normalizeHomePathTest2() {
final String home = FileUtil.getUserHomePath().replace('\\', '/');
final String home = SystemUtil.getUserHomePath().replace('\\', '/');
// 多个~应该只替换开头的
Assert.assertEquals(home + "/~bar/", FileUtil.normalize("~/foo/../~bar/"));
}

View File

@@ -18,4 +18,31 @@ public class FileNameUtilTest {
final String s = FileNameUtil.mainName("abc.tar.gz");
Assert.assertEquals("abc", s);
}
@Test
public void normalizeTest() {
Assert.assertEquals("/foo/", FileNameUtil.normalize("/foo//"));
Assert.assertEquals("/foo/", FileNameUtil.normalize("/foo/./"));
Assert.assertEquals("/bar", FileNameUtil.normalize("/foo/../bar"));
Assert.assertEquals("/bar/", FileNameUtil.normalize("/foo/../bar/"));
Assert.assertEquals("/baz", FileNameUtil.normalize("/foo/../bar/../baz"));
Assert.assertEquals("/", FileNameUtil.normalize("/../"));
Assert.assertEquals("foo", FileNameUtil.normalize("foo/bar/.."));
Assert.assertEquals("../bar", FileNameUtil.normalize("foo/../../bar"));
Assert.assertEquals("bar", FileNameUtil.normalize("foo/../bar"));
Assert.assertEquals("/server/bar", FileNameUtil.normalize("//server/foo/../bar"));
Assert.assertEquals("/bar", FileNameUtil.normalize("//server/../bar"));
Assert.assertEquals("C:/bar", FileNameUtil.normalize("C:\\foo\\..\\bar"));
//
Assert.assertEquals("C:/bar", FileNameUtil.normalize("C:\\..\\bar"));
Assert.assertEquals("../../bar", FileNameUtil.normalize("../../bar"));
Assert.assertEquals("C:/bar", FileNameUtil.normalize("/C:/bar"));
Assert.assertEquals("C:", FileNameUtil.normalize("C:"));
Assert.assertEquals("\\/192.168.1.1/Share/", FileNameUtil.normalize("\\\\192.168.1.1\\Share\\"));
}
@Test
public void normalizeBlankTest() {
Assert.assertEquals("C:/aaa ", FileNameUtil.normalize("C:\\aaa "));
}
}

View File

@@ -1,7 +1,9 @@
package cn.hutool.http.client.body;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.IoUtil;
import java.io.InputStream;
import java.io.OutputStream;
/**
@@ -29,4 +31,15 @@ public interface RequestBody {
IoUtil.close(out);
}
}
/**
* 获取body资源流
*
* @return {@link InputStream}
*/
default InputStream getStream() {
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
writeClose(out);
return IoUtil.toStream(out);
}
}

View File

@@ -2,6 +2,7 @@ package cn.hutool.http.client.body;
import cn.hutool.core.io.resource.Resource;
import java.io.InputStream;
import java.io.OutputStream;
/**
@@ -46,4 +47,9 @@ public class ResourceBody implements RequestBody {
public void write(final OutputStream out) {
resource.writeTo(out);
}
@Override
public InputStream getStream() {
return resource.getStream();
}
}

View File

@@ -1,12 +1,9 @@
package cn.hutool.http.client.engine.httpclient4;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.http.client.body.BytesBody;
import cn.hutool.http.client.body.RequestBody;
import cn.hutool.http.client.body.ResourceBody;
import org.apache.http.entity.AbstractHttpEntity;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
@@ -17,7 +14,7 @@ import java.nio.charset.Charset;
* @author looly
* @since 6.0.0
*/
public class RequestBodyEntity extends AbstractHttpEntity {
public class HttpClient4BodyEntity extends AbstractHttpEntity {
private final RequestBody body;
@@ -29,7 +26,7 @@ public class RequestBodyEntity extends AbstractHttpEntity {
* @param chunked 是否块模式传输
* @param body {@link RequestBody}
*/
public RequestBodyEntity(final String contentType, final Charset charset, final boolean chunked, final RequestBody body) {
public HttpClient4BodyEntity(final String contentType, final Charset charset, final boolean chunked, final RequestBody body) {
super();
setContentType(contentType);
setContentEncoding(null == charset ? null : charset.name());
@@ -46,13 +43,7 @@ public class RequestBodyEntity extends AbstractHttpEntity {
@Override
public InputStream getContent() {
if (body instanceof ResourceBody) {
return ((ResourceBody) body).getResource().getStream();
} else {
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
body.writeClose(out);
return new ByteArrayInputStream(out.toByteArray());
}
return body.getStream();
}
@Override

View File

@@ -88,7 +88,7 @@ public class HttpClient4Engine implements ClientEngine {
// 填充自定义消息体
final RequestBody body = message.body();
request.setEntity(new RequestBodyEntity(
request.setEntity(new HttpClient4BodyEntity(
// 用户自定义的内容类型
message.header(cn.hutool.http.meta.Header.CONTENT_TYPE),
// 用户自定义编码

View File

@@ -1,12 +1,9 @@
package cn.hutool.http.client.engine.httpclient5;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.http.client.body.BytesBody;
import cn.hutool.http.client.body.RequestBody;
import cn.hutool.http.client.body.ResourceBody;
import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -18,7 +15,7 @@ import java.nio.charset.Charset;
* @author looly
* @since 6.0.0
*/
public class RequestBodyEntity extends AbstractHttpEntity {
public class HttpClient5BodyEntity extends AbstractHttpEntity {
private final RequestBody body;
@@ -30,7 +27,7 @@ public class RequestBodyEntity extends AbstractHttpEntity {
* @param chunked 是否块模式传输
* @param body {@link RequestBody}
*/
public RequestBodyEntity(final String contentType, final Charset charset, final boolean chunked, final RequestBody body) {
public HttpClient5BodyEntity(final String contentType, final Charset charset, final boolean chunked, final RequestBody body) {
super(contentType, null == charset ? null : charset.name(), chunked);
this.body = body;
}
@@ -44,13 +41,7 @@ public class RequestBodyEntity extends AbstractHttpEntity {
@Override
public InputStream getContent() {
if (body instanceof ResourceBody) {
return ((ResourceBody) body).getResource().getStream();
} else {
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
body.writeClose(out);
return new ByteArrayInputStream(out.toByteArray());
}
return body.getStream();
}
@Override

View File

@@ -84,7 +84,7 @@ public class HttpClient5Engine implements ClientEngine {
// 填充自定义消息体
final RequestBody body = message.body();
request.setEntity(new RequestBodyEntity(
request.setEntity(new HttpClient5BodyEntity(
// 用户自定义的内容类型
message.header(cn.hutool.http.meta.Header.CONTENT_TYPE),
// 用户自定义编码