Checkpoint before follow-up message

This commit is contained in:
Cursor Agent
2025-07-09 07:54:10 +00:00
parent fc0a9ddaf1
commit c853410f2b
8 changed files with 186 additions and 4 deletions

View File

@@ -8,6 +8,7 @@ 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;
@@ -43,7 +44,7 @@ public class FileController {
@PostMapping("/upload")
@Operation(summary = "上传文件", description = "模式一:后端上传文件")
public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception {
public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception {
MultipartFile file = uploadReqVO.getFile();
byte[] content = IoUtil.readBytes(file.getInputStream());
return success(fileService.createFile(content, file.getOriginalFilename(),
@@ -58,7 +59,7 @@ public class FileController {
})
public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(
@RequestParam("name") String name,
@RequestParam(value = "directory", required = false) String directory) {
@ValidDirectoryParam @RequestParam(value = "directory", required = false) String directory) {
return success(fileService.getFilePresignedUrl(name, directory));
}

View File

@@ -1,5 +1,6 @@
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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@@ -14,6 +15,7 @@ public class FileUploadReqVO {
private MultipartFile file;
@Schema(description = "文件目录", example = "XXX/YYY")
@ValidDirectory(message = "目录路径无效,包含非法字符")
private String directory;
}

View File

@@ -0,0 +1,65 @@
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;
}
}

View File

@@ -0,0 +1,65 @@
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;
}
}

View File

@@ -0,0 +1,23 @@
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 {};
}

View File

@@ -0,0 +1,23 @@
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 {};
}

View File

@@ -1,10 +1,11 @@
package cn.iocoder.yudao.module.infra.controller.app.file;
THIS SHOULD BE A LINTER ERRORpackage cn.iocoder.yudao.module.infra.controller.app.file;
import cn.hutool.core.io.IoUtil;
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;
@@ -33,7 +34,7 @@ public class AppFileController {
@PostMapping("/upload")
@Operation(summary = "上传文件")
@PermitAll
public CommonResult<String> uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception {
public CommonResult<String> uploadFile(@Valid AppFileUploadReqVO uploadReqVO) throws Exception {
MultipartFile file = uploadReqVO.getFile();
byte[] content = IoUtil.readBytes(file.getInputStream());
return success(fileService.createFile(content, file.getOriginalFilename(),

View File

@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.infra.controller.app.file.vo;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.validator.ValidDirectory;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@@ -14,6 +15,7 @@ public class AppFileUploadReqVO {
private MultipartFile file;
@Schema(description = "文件目录", example = "XXX/YYY")
@ValidDirectory(message = "目录路径无效,包含非法字符")
private String directory;
}