fix: 兼容客户端文档 MIME 差异

- 接受 WPS及系统 MIME 别名并统一保存服务端标准类型

- 保留扩展名、大小和文件头校验以拒绝伪装文件
This commit is contained in:
2026-07-30 15:06:08 +08:00
parent ff5f90121b
commit 864cea6135
2 changed files with 166 additions and 19 deletions

View File

@@ -56,7 +56,7 @@ public class AgentDocumentService {
private static final byte[] OLE_SIGNATURE = {
(byte) 0xd0, (byte) 0xcf, 0x11, (byte) 0xe0, (byte) 0xa1, (byte) 0xb1, 0x1a, (byte) 0xe1
};
private static final Map<String, Set<String>> MIME_TYPES = mimeTypes();
private static final Map<String, String> CANONICAL_MIME_TYPES = canonicalMimeTypes();
private final AgentDocumentAttachmentMapper attachmentMapper;
private final AgentDocumentSnapshotMapper snapshotMapper;
@@ -701,8 +701,8 @@ public class AgentDocumentService {
throw new ResponseStatusException(HttpStatus.PAYLOAD_TOO_LARGE,
"该类型文档不能超过 " + limit / (1024 * 1024) + " MiB");
}
String mimeType = normalizeMime(file.getContentType(), extension);
validateSignature(file, extension);
String mimeType = normalizeMime(file.getContentType(), extension);
return new DocumentFile(originalName, extension, mimeType, file.getSize());
}
@@ -768,15 +768,27 @@ public class AgentDocumentService {
return properties.getLimits().getOfficeMaxBytes().toBytes();
}
/**
* 将客户端声明的 MIME 规范为服务端可信类型。
*
* @param declared 客户端声明 MIME
* @param extension 已校验扩展名
* @return 服务端规范 MIME
*/
private String normalizeMime(String declared, String extension) {
String normalized = declared == null ? "" : declared.split(";", 2)[0].trim().toLowerCase(Locale.ROOT);
Set<String> allowed = MIME_TYPES.get(extension);
if (StringUtils.hasText(normalized) && !"application/octet-stream".equals(normalized)
&& (allowed == null || !allowed.contains(normalized))) {
throw new ResponseStatusException(HttpStatus.UNSUPPORTED_MEDIA_TYPE,
"文件 MIME 类型与扩展名不一致");
String canonical = CANONICAL_MIME_TYPES.get(extension);
if (!StringUtils.hasText(canonical)) {
throw new ResponseStatusException(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "不支持该文档类型");
}
return allowed.iterator().next();
// 浏览器 MIME 由系统注册表或桌面 MIME 数据库决定,只用于诊断,最终类型由扩展名和文件内容校验确定。
if (StringUtils.hasText(normalized)
&& !"application/octet-stream".equals(normalized)
&& !canonical.equals(normalized)) {
LOG.info("Normalize Agent document MIME alias: extension={}, declaredMime={}, canonicalMime={}",
extension, normalized, canonical);
}
return canonical;
}
private String safeOriginalName(String value) {
@@ -904,17 +916,22 @@ public class AgentDocumentService {
return new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
}
private static Map<String, Set<String>> mimeTypes() {
Map<String, Set<String>> values = new LinkedHashMap<>();
values.put("pdf", Set.of("application/pdf"));
values.put("doc", Set.of("application/msword"));
values.put("docx", Set.of("application/vnd.openxmlformats-officedocument.wordprocessingml.document"));
values.put("ppt", Set.of("application/vnd.ms-powerpoint"));
values.put("pptx", Set.of("application/vnd.openxmlformats-officedocument.presentationml.presentation"));
values.put("xls", Set.of("application/vnd.ms-excel"));
values.put("xlsx", Set.of("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
values.put("txt", Set.of("text/plain"));
values.put("md", Set.of("text/markdown", "text/plain"));
/**
* 创建受支持扩展名到服务端规范 MIME 的映射。
*
* @return 不可变规范 MIME 映射
*/
private static Map<String, String> canonicalMimeTypes() {
Map<String, String> values = new LinkedHashMap<>();
values.put("pdf", "application/pdf");
values.put("doc", "application/msword");
values.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
values.put("ppt", "application/vnd.ms-powerpoint");
values.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
values.put("xls", "application/vnd.ms-excel");
values.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
values.put("txt", "text/plain");
values.put("md", "text/markdown");
return Map.copyOf(values);
}