Refactor file path validation using @AssertTrue method in upload VOss
Co-authored-by: zhijiantianya <zhijiantianya@gmail.com>
This commit is contained in:
BIN
test_assert_true.class
Normal file
BIN
test_assert_true.class
Normal file
Binary file not shown.
@@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator.ValidDirectoryParam;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
|
||||
import cn.iocoder.yudao.module.infra.service.file.FileService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -59,7 +58,7 @@ public class FileController {
|
||||
})
|
||||
public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(
|
||||
@RequestParam("name") String name,
|
||||
@ValidDirectoryParam @RequestParam(value = "directory", required = false) String directory) {
|
||||
@RequestParam(value = "directory", required = false) String directory) {
|
||||
return success(fileService.getFilePresignedUrl(name, directory));
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,8 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator.ValidDirectory;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.AssertTrue;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -15,7 +16,36 @@ public class FileUploadReqVO {
|
||||
private MultipartFile file;
|
||||
|
||||
@Schema(description = "文件目录", example = "XXX/YYY")
|
||||
@ValidDirectory(message = "目录路径无效,包含非法字符")
|
||||
private String directory;
|
||||
|
||||
@AssertTrue(message = "目录路径无效,包含非法字符")
|
||||
public boolean isDirectoryValid() {
|
||||
if (StrUtil.isEmpty(directory)) {
|
||||
return true; // 空值认为是有效的
|
||||
}
|
||||
|
||||
// 统一使用正斜杠
|
||||
String normalizedPath = directory.replace('\\', '/');
|
||||
|
||||
// 检查绝对路径
|
||||
if (normalizedPath.startsWith("/") || normalizedPath.matches("^[A-Za-z]:.*")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查路径遍历攻击
|
||||
String[] dangerousPatterns = {
|
||||
"..", "..\\", "../", "..%2f", "..%5c", "..%2F", "..%5C",
|
||||
"%2e%2e", "%2E%2E", "%2e%2e%2f", "%2E%2E%2F",
|
||||
"....//", "....\\\\", "....%2f", "....%5c"
|
||||
};
|
||||
|
||||
String lowerPath = normalizedPath.toLowerCase();
|
||||
for (String pattern : dangerousPatterns) {
|
||||
if (lowerPath.contains(pattern)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -1,65 +0,0 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
/**
|
||||
* 目录路径参数验证器实现
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class DirectoryParamValidator implements ConstraintValidator<ValidDirectoryParam, String> {
|
||||
|
||||
@Override
|
||||
public void initialize(ValidDirectoryParam constraintAnnotation) {
|
||||
// 初始化方法,如果需要可以在这里进行一些初始化操作
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String directory, ConstraintValidatorContext context) {
|
||||
// 如果为空,则认为是有效的(可选字段)
|
||||
if (StrUtil.isEmpty(directory)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否包含路径遍历攻击
|
||||
return !containsPathTraversal(directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路径是否包含路径遍历攻击
|
||||
*
|
||||
* @param path 路径
|
||||
* @return 是否包含路径遍历攻击
|
||||
*/
|
||||
private boolean containsPathTraversal(String path) {
|
||||
if (StrUtil.isEmpty(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 统一使用正斜杠
|
||||
String normalizedPath = path.replace('\\', '/');
|
||||
|
||||
// 检查常见的路径遍历模式
|
||||
String[] dangerousPatterns = {
|
||||
"..", "..\\", "../", "..%2f", "..%5c", "..%2F", "..%5C",
|
||||
"%2e%2e", "%2E%2E", "%2e%2e%2f", "%2E%2E%2F",
|
||||
"....//", "....\\\\", "....%2f", "....%5c"
|
||||
};
|
||||
|
||||
String lowerPath = normalizedPath.toLowerCase();
|
||||
for (String pattern : dangerousPatterns) {
|
||||
if (lowerPath.contains(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查绝对路径
|
||||
if (normalizedPath.startsWith("/") || normalizedPath.matches("^[A-Za-z]:.*")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
/**
|
||||
* 目录路径验证器实现
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class DirectoryValidator implements ConstraintValidator<ValidDirectory, String> {
|
||||
|
||||
@Override
|
||||
public void initialize(ValidDirectory constraintAnnotation) {
|
||||
// 初始化方法,如果需要可以在这里进行一些初始化操作
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String directory, ConstraintValidatorContext context) {
|
||||
// 如果为空,则认为是有效的(可选字段)
|
||||
if (StrUtil.isEmpty(directory)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否包含路径遍历攻击
|
||||
return !containsPathTraversal(directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路径是否包含路径遍历攻击
|
||||
*
|
||||
* @param path 路径
|
||||
* @return 是否包含路径遍历攻击
|
||||
*/
|
||||
private boolean containsPathTraversal(String path) {
|
||||
if (StrUtil.isEmpty(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 统一使用正斜杠
|
||||
String normalizedPath = path.replace('\\', '/');
|
||||
|
||||
// 检查常见的路径遍历模式
|
||||
String[] dangerousPatterns = {
|
||||
"..", "..\\", "../", "..%2f", "..%5c", "..%2F", "..%5C",
|
||||
"%2e%2e", "%2E%2E", "%2e%2e%2f", "%2E%2E%2F",
|
||||
"....//", "....\\\\", "....%2f", "....%5c"
|
||||
};
|
||||
|
||||
String lowerPath = normalizedPath.toLowerCase();
|
||||
for (String pattern : dangerousPatterns) {
|
||||
if (lowerPath.contains(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查绝对路径
|
||||
if (normalizedPath.startsWith("/") || normalizedPath.matches("^[A-Za-z]:.*")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator;
|
||||
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 目录路径验证注解
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Constraint(validatedBy = DirectoryValidator.class)
|
||||
public @interface ValidDirectory {
|
||||
|
||||
String message() default "目录路径无效,包含非法字符";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator;
|
||||
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 目录路径参数验证注解
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Constraint(validatedBy = DirectoryParamValidator.class)
|
||||
public @interface ValidDirectoryParam {
|
||||
|
||||
String message() default "目录路径无效,包含非法字符";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
|
||||
import cn.iocoder.yudao.module.infra.controller.app.file.vo.AppFileUploadReqVO;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator.ValidDirectoryParam;
|
||||
import cn.iocoder.yudao.module.infra.service.file.FileService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -49,7 +48,7 @@ public class AppFileController {
|
||||
})
|
||||
public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(
|
||||
@RequestParam("name") String name,
|
||||
@ValidDirectoryParam @RequestParam(value = "directory", required = false) String directory) {
|
||||
@RequestParam(value = "directory", required = false) String directory) {
|
||||
return success(fileService.getFilePresignedUrl(name, directory));
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,8 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.app.file.vo;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator.ValidDirectory;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.AssertTrue;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -15,7 +16,36 @@ public class AppFileUploadReqVO {
|
||||
private MultipartFile file;
|
||||
|
||||
@Schema(description = "文件目录", example = "XXX/YYY")
|
||||
@ValidDirectory(message = "目录路径无效,包含非法字符")
|
||||
private String directory;
|
||||
|
||||
@AssertTrue(message = "目录路径无效,包含非法字符")
|
||||
public boolean isDirectoryValid() {
|
||||
if (StrUtil.isEmpty(directory)) {
|
||||
return true; // 空值认为是有效的
|
||||
}
|
||||
|
||||
// 统一使用正斜杠
|
||||
String normalizedPath = directory.replace('\\', '/');
|
||||
|
||||
// 检查绝对路径
|
||||
if (normalizedPath.startsWith("/") || normalizedPath.matches("^[A-Za-z]:.*")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查路径遍历攻击
|
||||
String[] dangerousPatterns = {
|
||||
"..", "..\\", "../", "..%2f", "..%5c", "..%2F", "..%5C",
|
||||
"%2e%2e", "%2E%2E", "%2e%2e%2f", "%2E%2E%2F",
|
||||
"....//", "....\\\\", "....%2f", "....%5c"
|
||||
};
|
||||
|
||||
String lowerPath = normalizedPath.toLowerCase();
|
||||
for (String pattern : dangerousPatterns) {
|
||||
if (lowerPath.contains(pattern)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -1,104 +0,0 @@
|
||||
package cn.iocoder.yudao.module.infra.framework.file.core.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 路径验证工具类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class PathValidationUtils {
|
||||
|
||||
/**
|
||||
* 验证并清理目录路径,防止路径遍历攻击
|
||||
*
|
||||
* @param directory 原始目录路径
|
||||
* @return 清理后的目录路径
|
||||
*/
|
||||
public static String validateAndCleanDirectory(String directory) {
|
||||
if (StrUtil.isEmpty(directory)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. 检查是否包含路径遍历攻击的字符序列
|
||||
String normalizedPath = directory.replace('\\', '/'); // 统一使用正斜杠
|
||||
if (containsPathTraversal(normalizedPath)) {
|
||||
throw ServiceExceptionUtil.exception(FILE_PATH_INVALID);
|
||||
}
|
||||
|
||||
// 2. 清理路径,移除多余的斜杠和点
|
||||
String cleanedPath = cleanPath(normalizedPath);
|
||||
|
||||
// 3. 确保路径不以斜杠开头或结尾
|
||||
if (cleanedPath.startsWith("/")) {
|
||||
cleanedPath = cleanedPath.substring(1);
|
||||
}
|
||||
if (cleanedPath.endsWith("/")) {
|
||||
cleanedPath = cleanedPath.substring(0, cleanedPath.length() - 1);
|
||||
}
|
||||
|
||||
return cleanedPath.isEmpty() ? null : cleanedPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路径是否包含路径遍历攻击
|
||||
*
|
||||
* @param path 路径
|
||||
* @return 是否包含路径遍历攻击
|
||||
*/
|
||||
private static boolean containsPathTraversal(String path) {
|
||||
if (StrUtil.isEmpty(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查常见的路径遍历模式
|
||||
String[] dangerousPatterns = {
|
||||
"..", "..\\", "../", "..%2f", "..%5c", "..%2F", "..%5C",
|
||||
"%2e%2e", "%2E%2E", "%2e%2e%2f", "%2E%2E%2F",
|
||||
"....//", "....\\\\", "....%2f", "....%5c"
|
||||
};
|
||||
|
||||
String lowerPath = path.toLowerCase();
|
||||
for (String pattern : dangerousPatterns) {
|
||||
if (lowerPath.contains(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查绝对路径
|
||||
if (path.startsWith("/") || path.matches("^[A-Za-z]:.*")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理路径,移除多余的斜杠和点
|
||||
*
|
||||
* @param path 原始路径
|
||||
* @return 清理后的路径
|
||||
*/
|
||||
private static String cleanPath(String path) {
|
||||
if (StrUtil.isEmpty(path)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
// 移除多余的斜杠
|
||||
path = path.replaceAll("/+", "/");
|
||||
|
||||
// 移除开头的斜杠
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
// 移除结尾的斜杠
|
||||
if (path.endsWith("/")) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
@@ -15,7 +15,6 @@ import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
|
||||
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
|
||||
import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO;
|
||||
import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils;
|
||||
import cn.iocoder.yudao.module.infra.framework.file.core.utils.PathValidationUtils;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -95,10 +94,7 @@ public class FileServiceImpl implements FileService {
|
||||
|
||||
@VisibleForTesting
|
||||
String generateUploadPath(String name, String directory) {
|
||||
// 1. 验证并清理目录路径,防止路径遍历攻击
|
||||
directory = PathValidationUtils.validateAndCleanDirectory(directory);
|
||||
|
||||
// 2. 生成前缀、后缀
|
||||
// 1. 生成前缀、后缀
|
||||
String prefix = null;
|
||||
if (PATH_PREFIX_DATE_ENABLE) {
|
||||
prefix = LocalDateTimeUtil.format(LocalDateTimeUtil.now(), PURE_DATE_PATTERN);
|
||||
@@ -108,7 +104,7 @@ public class FileServiceImpl implements FileService {
|
||||
suffix = String.valueOf(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
// 3.1 先拼接 suffix 后缀
|
||||
// 2.1 先拼接 suffix 后缀
|
||||
if (StrUtil.isNotEmpty(suffix)) {
|
||||
String ext = FileUtil.extName(name);
|
||||
if (StrUtil.isNotEmpty(ext)) {
|
||||
@@ -117,11 +113,11 @@ public class FileServiceImpl implements FileService {
|
||||
name = name + StrUtil.C_UNDERLINE + suffix;
|
||||
}
|
||||
}
|
||||
// 3.2 再拼接 prefix 前缀
|
||||
// 2.2 再拼接 prefix 前缀
|
||||
if (StrUtil.isNotEmpty(prefix)) {
|
||||
name = prefix + StrUtil.SLASH + name;
|
||||
}
|
||||
// 3.3 最后拼接 directory 目录
|
||||
// 2.3 最后拼接 directory 目录
|
||||
if (StrUtil.isNotEmpty(directory)) {
|
||||
name = directory + StrUtil.SLASH + name;
|
||||
}
|
||||
@@ -131,13 +127,10 @@ public class FileServiceImpl implements FileService {
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public FilePresignedUrlRespVO getFilePresignedUrl(String name, String directory) {
|
||||
// 1. 验证并清理目录路径,防止路径遍历攻击
|
||||
directory = PathValidationUtils.validateAndCleanDirectory(directory);
|
||||
|
||||
// 2. 生成上传的 path,需要保证唯一
|
||||
// 1. 生成上传的 path,需要保证唯一
|
||||
String path = generateUploadPath(name, directory);
|
||||
|
||||
// 3. 获取文件预签名地址
|
||||
// 2. 获取文件预签名地址
|
||||
FileClient fileClient = fileConfigService.getMasterFileClient();
|
||||
FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path);
|
||||
return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class,
|
||||
|
@@ -0,0 +1,92 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
|
||||
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
import jakarta.validation.ValidatorFactory;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* FileUploadReqVO 测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
class FileUploadReqVOTest {
|
||||
|
||||
private static Validator validator;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() {
|
||||
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||
validator = factory.getValidator();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidDirectory() {
|
||||
// 测试有效目录
|
||||
FileUploadReqVO vo = new FileUploadReqVO();
|
||||
vo.setFile(new MockMultipartFile("test.txt", "test.txt", "text/plain", "test".getBytes()));
|
||||
vo.setDirectory("uploads/2024/01");
|
||||
|
||||
var violations = validator.validate(vo);
|
||||
assertTrue(violations.isEmpty(), "有效目录应该通过验证");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullDirectory() {
|
||||
// 测试空目录
|
||||
FileUploadReqVO vo = new FileUploadReqVO();
|
||||
vo.setFile(new MockMultipartFile("test.txt", "test.txt", "text/plain", "test".getBytes()));
|
||||
vo.setDirectory(null);
|
||||
|
||||
var violations = validator.validate(vo);
|
||||
assertTrue(violations.isEmpty(), "空目录应该通过验证");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyDirectory() {
|
||||
// 测试空字符串目录
|
||||
FileUploadReqVO vo = new FileUploadReqVO();
|
||||
vo.setFile(new MockMultipartFile("test.txt", "test.txt", "text/plain", "test".getBytes()));
|
||||
vo.setDirectory("");
|
||||
|
||||
var violations = validator.validate(vo);
|
||||
assertTrue(violations.isEmpty(), "空字符串目录应该通过验证");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPathTraversalAttack() {
|
||||
// 测试路径遍历攻击
|
||||
FileUploadReqVO vo = new FileUploadReqVO();
|
||||
vo.setFile(new MockMultipartFile("test.txt", "test.txt", "text/plain", "test".getBytes()));
|
||||
vo.setDirectory("../../etc/passwd");
|
||||
|
||||
var violations = validator.validate(vo);
|
||||
assertFalse(violations.isEmpty(), "路径遍历攻击应该被拒绝");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAbsolutePath() {
|
||||
// 测试绝对路径
|
||||
FileUploadReqVO vo = new FileUploadReqVO();
|
||||
vo.setFile(new MockMultipartFile("test.txt", "test.txt", "text/plain", "test".getBytes()));
|
||||
vo.setDirectory("/etc/passwd");
|
||||
|
||||
var violations = validator.validate(vo);
|
||||
assertFalse(violations.isEmpty(), "绝对路径应该被拒绝");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWindowsAbsolutePath() {
|
||||
// 测试Windows绝对路径
|
||||
FileUploadReqVO vo = new FileUploadReqVO();
|
||||
vo.setFile(new MockMultipartFile("test.txt", "test.txt", "text/plain", "test".getBytes()));
|
||||
vo.setDirectory("C:\\windows\\system32");
|
||||
|
||||
var violations = validator.validate(vo);
|
||||
assertFalse(violations.isEmpty(), "Windows绝对路径应该被拒绝");
|
||||
}
|
||||
}
|
@@ -1,111 +0,0 @@
|
||||
package cn.iocoder.yudao.module.infra.framework.file.core.utils;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 路径验证工具类测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
class PathValidationUtilsTest {
|
||||
|
||||
@Test
|
||||
void testValidateAndCleanDirectory_NullInput() {
|
||||
String result = PathValidationUtils.validateAndCleanDirectory(null);
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateAndCleanDirectory_EmptyInput() {
|
||||
String result = PathValidationUtils.validateAndCleanDirectory("");
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateAndCleanDirectory_ValidDirectory() {
|
||||
String result = PathValidationUtils.validateAndCleanDirectory("test/directory");
|
||||
assertEquals("test/directory", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateAndCleanDirectory_WithTrailingSlash() {
|
||||
String result = PathValidationUtils.validateAndCleanDirectory("test/directory/");
|
||||
assertEquals("test/directory", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateAndCleanDirectory_WithLeadingSlash() {
|
||||
String result = PathValidationUtils.validateAndCleanDirectory("/test/directory");
|
||||
assertEquals("test/directory", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateAndCleanDirectory_WithMultipleSlashes() {
|
||||
String result = PathValidationUtils.validateAndCleanDirectory("test///directory");
|
||||
assertEquals("test/directory", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateAndCleanDirectory_PathTraversalAttack() {
|
||||
// 测试路径遍历攻击
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
PathValidationUtils.validateAndCleanDirectory("../../etc/passwd");
|
||||
});
|
||||
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
PathValidationUtils.validateAndCleanDirectory("..\\..\\windows\\system32");
|
||||
});
|
||||
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
PathValidationUtils.validateAndCleanDirectory("....//etc/passwd");
|
||||
});
|
||||
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
PathValidationUtils.validateAndCleanDirectory("....\\\\windows\\system32");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateAndCleanDirectory_UrlEncodedPathTraversal() {
|
||||
// 测试URL编码的路径遍历攻击
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
PathValidationUtils.validateAndCleanDirectory("..%2f..%2fetc%2fpasswd");
|
||||
});
|
||||
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
PathValidationUtils.validateAndCleanDirectory("..%5c..%5cwindows%5csystem32");
|
||||
});
|
||||
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
PathValidationUtils.validateAndCleanDirectory("%2e%2e%2f%2e%2e%2fetc%2fpasswd");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateAndCleanDirectory_AbsolutePath() {
|
||||
// 测试绝对路径
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
PathValidationUtils.validateAndCleanDirectory("/etc/passwd");
|
||||
});
|
||||
|
||||
assertThrows(ServiceException.class, () -> {
|
||||
PathValidationUtils.validateAndCleanDirectory("C:\\windows\\system32");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateAndCleanDirectory_ValidComplexPath() {
|
||||
String result = PathValidationUtils.validateAndCleanDirectory("uploads/2024/01/images");
|
||||
assertEquals("uploads/2024/01/images", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateAndCleanDirectory_WithDots() {
|
||||
// 测试包含点的路径(但不是路径遍历)
|
||||
String result = PathValidationUtils.validateAndCleanDirectory("my.file.txt");
|
||||
assertEquals("my.file.txt", result);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user