From d1876914d2b25ee3248f9db556bd02be4d982175 Mon Sep 17 00:00:00 2001 From: PIGCLOUD Date: Sun, 2 Aug 2026 22:00:15 +0800 Subject: [PATCH] =?UTF-8?q?fix(upms):=20=E6=9C=AC=E5=9C=B0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=AD=98=E5=82=A8=E5=A2=9E=E5=8A=A0=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E7=A9=BF=E8=B6=8A=E9=98=B2=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上传/读取/删除前校验路径位于存储根目录内,统一错误文案,并修复无扩展名时多余的点号。 --- .../admin/api/constant/UpmsErrorCodes.java | 5 ++ .../resources/i18n/messages_en.properties | 4 ++ .../resources/i18n/messages_zh_CN.properties | 4 ++ .../service/impl/SysFileServiceImpl.java | 66 ++++++++++++++++++- 4 files changed, 77 insertions(+), 2 deletions(-) diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/UpmsErrorCodes.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/UpmsErrorCodes.java index 029666fc8..2631f893a 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/UpmsErrorCodes.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/UpmsErrorCodes.java @@ -74,6 +74,11 @@ public interface UpmsErrorCodes { String SYS_DICT_ITEM_SORT_SCOPE_INVALID = "sys.dict.item.sort.scopeInvalid"; + // === 文件相关 === + String SYS_FILE_PATH_INVALID = "sys.file.path.invalid"; + + String SYS_FILE_UPLOAD_FAILED = "sys.file.upload.failed"; + // === 参数相关 === String SYS_PARAM_CONFIG_ERROR = "sys.param.config.error"; diff --git a/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_en.properties b/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_en.properties index 7b6c7de5c..af5edea52 100644 --- a/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_en.properties +++ b/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_en.properties @@ -42,6 +42,10 @@ sys.dict.notFound=Dictionary does not exist sys.dict.item.sort.duplicate=Dictionary item ID list cannot contain duplicates sys.dict.item.sort.scopeInvalid=Dictionary item sort data does not belong to the current dictionary +# File related +sys.file.path.invalid=Invalid file path +sys.file.upload.failed=File upload failed + # Parameter related sys.param.config.error=System parameter configuration error: {0} sys.param.delete.system=Built-in system parameters cannot be deleted diff --git a/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_zh_CN.properties b/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_zh_CN.properties index 0aa994e22..95ac10dd0 100644 --- a/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_zh_CN.properties +++ b/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_zh_CN.properties @@ -42,6 +42,10 @@ sys.dict.notFound=\u5B57\u5178\u4E0D\u5B58\u5728 sys.dict.item.sort.duplicate=\u5B57\u5178\u9879ID\u5217\u8868\u4E0D\u80FD\u91CD\u590D sys.dict.item.sort.scopeInvalid=\u5B57\u5178\u9879\u6392\u5E8F\u6570\u636E\u4E0D\u5C5E\u4E8E\u5F53\u524D\u5B57\u5178 +# 文件相关 +sys.file.path.invalid=文件路径不合法 +sys.file.upload.failed=文件上传失败 + # 参数相关 sys.param.config.error={0} \u7CFB\u7EDF\u53C2\u6570\u914D\u7F6E\u9519\u8BEF sys.param.delete.system=\u7CFB\u7EDF\u5185\u7F6E\u53C2\u6570\u4E0D\u80FD\u5220\u9664 diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysFileServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysFileServiceImpl.java index 5c45d96d1..9042da0f3 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysFileServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysFileServiceImpl.java @@ -30,6 +30,7 @@ import cn.hutool.crypto.SecureUtil; import cn.hutool.http.HttpUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.pig4cloud.pigx.admin.api.constant.UpmsErrorCodes; import com.pig4cloud.pigx.admin.api.dto.SysFileGroupDTO; import com.pig4cloud.pigx.admin.api.entity.SysFile; import com.pig4cloud.pigx.admin.api.entity.SysFileGroup; @@ -37,11 +38,13 @@ import com.pig4cloud.pigx.admin.mapper.SysFileGroupMapper; import com.pig4cloud.pigx.admin.mapper.SysFileMapper; import com.pig4cloud.pigx.admin.service.SysFileService; import com.pig4cloud.pigx.common.core.constant.CommonConstants; +import com.pig4cloud.pigx.common.core.util.MsgUtils; import com.pig4cloud.pigx.common.core.util.R; import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; import com.pig4cloud.pigx.common.file.core.FileObject; import com.pig4cloud.pigx.common.file.core.FileProperties; import com.pig4cloud.pigx.common.file.core.FileTemplate; +import com.pig4cloud.pigx.common.file.core.FileType; import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; import lombok.SneakyThrows; @@ -51,6 +54,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.io.File; import java.io.InputStream; import java.nio.charset.Charset; import java.util.*; @@ -86,8 +90,17 @@ public class SysFileServiceImpl extends ServiceImpl impl public R uploadFile(MultipartFile file, String fileName, String dir, Long groupId, String type) { // 如果客户端没有传入文件名,则使用UUID生成一个唯一的文件名 if (StrUtil.isBlank(fileName)) { - fileName = IdUtil.simpleUUID() + StrUtil.DOT + FileUtil.extName(file.getOriginalFilename()); + String extension = FileUtil.extName(file.getOriginalFilename()); + fileName = IdUtil.simpleUUID() + (StrUtil.isBlank(extension) ? StrUtil.EMPTY : StrUtil.DOT + extension); } + + try { + validateStoragePath(properties.getBucketName(), dir, fileName); + } catch (IllegalArgumentException ex) { + log.warn("拒绝包含非法路径的文件上传请求"); + return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_FILE_PATH_INVALID)); + } + Map resultMap = new HashMap<>(4); resultMap.put("bucketName", properties.getBucketName()); resultMap.put("fileName", fileName); @@ -100,7 +113,7 @@ public class SysFileServiceImpl extends ServiceImpl impl fileLog(file, dir, fileName, groupId, type); } catch (Exception e) { log.error("上传失败", e); - return R.failed(e.getLocalizedMessage()); + return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_FILE_UPLOAD_FAILED)); } return R.ok(resultMap); } @@ -121,6 +134,14 @@ public class SysFileServiceImpl extends ServiceImpl impl fileName = urlFileName; } + try { + validateStoragePath(properties.getBucketName(), null, fileName); + } catch (IllegalArgumentException ex) { + log.warn("拒绝包含非法路径的文件读取请求"); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + SysFile sysFile = baseMapper.selectOne(Wrappers.lambdaQuery().eq(SysFile::getFileName, fileName), false); if (Objects.isNull(sysFile)) { @@ -128,6 +149,14 @@ public class SysFileServiceImpl extends ServiceImpl impl return; } + try { + validateStoragePath(sysFile.getBucketName(), sysFile.getDir(), sysFile.getFileName()); + } catch (IllegalArgumentException ex) { + log.warn("拒绝读取包含非法存储路径的文件记录"); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + try (FileObject fileObject = fileTemplate.getObject(sysFile.getBucketName(), sysFile.getDir(), fileName)) { response.setContentType("application/octet-stream; charset=UTF-8"); // 文件唯一hash @@ -154,6 +183,14 @@ public class SysFileServiceImpl extends ServiceImpl impl if (Objects.isNull(file)) { return Boolean.FALSE; } + + try { + validateStoragePath(properties.getBucketName(), null, file.getFileName()); + } catch (IllegalArgumentException ex) { + log.warn("拒绝删除包含非法存储路径的文件记录"); + return Boolean.FALSE; + } + fileTemplate.removeObject(properties.getBucketName(), file.getFileName()); return this.removeById(id); } @@ -258,4 +295,29 @@ public class SysFileServiceImpl extends ServiceImpl impl this.save(sysFile); } + /** + * 校验文件存储路径,确保文件始终位于配置的存储桶目录内。 + * + * @param bucketName 存储桶名称 + * @param dir 相对目录 + * @param fileName 文件名 + */ + private void validateStoragePath(String bucketName, String dir, String fileName) { + if (properties.getType() != FileType.LOCAL) { + return; + } + + String basePath = properties.getLocal().getBasePath(); + if (StrUtil.isBlank(basePath)) { + throw new IllegalArgumentException(MsgUtils.getMessage(UpmsErrorCodes.SYS_FILE_PATH_INVALID)); + } + + File storageRoot = new File(basePath); + File bucketRoot = new File(storageRoot, bucketName); + File targetFile = StrUtil.isBlank(dir) ? new File(bucketRoot, fileName) + : new File(new File(bucketRoot, dir), fileName); + FileUtil.checkSlip(storageRoot, bucketRoot); + FileUtil.checkSlip(bucketRoot, targetFile); + } + }