검증게시판, 압축파일, 한글파일 첨부가능

This commit is contained in:
Macbook
2026-06-13 01:44:49 +09:00
parent beeebed8f4
commit 864d85484d
7 changed files with 297 additions and 73 deletions
@@ -25,10 +25,21 @@ import java.util.UUID;
@Slf4j
public class VerificationIssueImageService {
private static final Set<String> ALLOWED_TYPES = Set.of(
private static final Set<String> ALLOWED_IMAGE_TYPES = Set.of(
"image/jpeg", "image/png", "image/gif", "image/webp", "image/bmp"
);
private static final Set<String> ALLOWED_DOCUMENT_TYPES = Set.of(
"application/zip",
"application/x-zip-compressed",
"application/x-hwp",
"application/haansoft-hwp",
"application/vnd.hancom.hwp",
"application/hwp+zip",
"application/vnd.hancom.hwpx",
"application/x-hwpx"
);
private final GcVerificationIssueImageRepository imageRepository;
private final GcVerificationIssueRepository issueRepository;
private final Path uploadRoot;
@@ -100,7 +111,7 @@ public class VerificationIssueImageService {
}
if (saved.isEmpty()) {
throw new IllegalArgumentException("no valid image files");
throw new IllegalArgumentException("no valid attachment files");
}
return saved;
}
@@ -155,14 +166,19 @@ public class VerificationIssueImageService {
}
private void validateFile(long sizeBytes, String contentType) {
if (contentType == null) {
throw new IllegalArgumentException("unsupported image type");
if (contentType == null || !isAllowedContentType(contentType)) {
throw new IllegalArgumentException("unsupported file type");
}
if (sizeBytes > maxFileSizeBytes) {
throw new IllegalArgumentException("file too large");
}
}
private static boolean isAllowedContentType(String contentType) {
return ALLOWED_IMAGE_TYPES.contains(contentType)
|| ALLOWED_DOCUMENT_TYPES.contains(contentType);
}
private String resolveContentType(MultipartFile file, byte[] data) {
String type = file.getContentType();
if (type != null && !type.isBlank()) {
@@ -173,12 +189,15 @@ public class VerificationIssueImageService {
if ("image/x-png".equals(type)) {
return "image/png";
}
if (ALLOWED_TYPES.contains(type)) {
if (ALLOWED_IMAGE_TYPES.contains(type)) {
return type;
}
// application/octet-stream 등 — 매직 바이트로 판별
if (ALLOWED_DOCUMENT_TYPES.contains(type)) {
return type;
}
// application/octet-stream 등 — 매직 바이트·확장자로 판별
if ("application/octet-stream".equals(type) || type.startsWith("image/")) {
String sniffed = sniffImageType(data);
String sniffed = sniffFileType(data, file.getOriginalFilename());
if (sniffed != null) return sniffed;
}
}
@@ -191,9 +210,35 @@ public class VerificationIssueImageService {
if (lower.endsWith(".gif")) return "image/gif";
if (lower.endsWith(".webp")) return "image/webp";
if (lower.endsWith(".bmp")) return "image/bmp";
if (lower.endsWith(".zip")) return "application/zip";
if (lower.endsWith(".hwp")) return "application/x-hwp";
if (lower.endsWith(".hwpx")) return "application/vnd.hancom.hwpx";
}
return sniffImageType(data);
return sniffFileType(data, file.getOriginalFilename());
}
private String sniffFileType(byte[] header, String fileName) {
String image = sniffImageType(header);
if (image != null) return image;
if (header != null && header.length >= 4
&& header[0] == 0x50 && header[1] == 0x4B
&& (header[2] == 0x03 || header[2] == 0x05 || header[2] == 0x07)) {
if (fileName != null && fileName.toLowerCase().endsWith(".hwpx")) {
return "application/vnd.hancom.hwpx";
}
return "application/zip";
}
if (header != null && header.length >= 8
&& header[0] == (byte) 0xD0 && header[1] == (byte) 0xCF
&& header[2] == 0x11 && header[3] == (byte) 0xE0) {
if (fileName != null && fileName.toLowerCase().endsWith(".hwp")) {
return "application/x-hwp";
}
}
return null;
}
private String sniffImageType(byte[] header) {
@@ -230,7 +275,7 @@ public class VerificationIssueImageService {
}
private String sanitizeFileName(String name) {
if (name == null || name.isBlank()) return "image.png";
if (name == null || name.isBlank()) return "attachment.bin";
String base = Paths.get(name).getFileName().toString();
return base.replaceAll("[^a-zA-Z0-9._\\-가-힣]", "_");
}