검증게시판, 압축파일, 한글파일 첨부가능
This commit is contained in:
+4
-3
@@ -47,9 +47,10 @@ public class VerificationIssueImageController {
|
||||
private static String toUserMessage(String code) {
|
||||
if (code == null) return "업로드할 수 없습니다.";
|
||||
return switch (code) {
|
||||
case "unsupported image type" -> "지원하지 않는 이미지 형식입니다. (jpeg, png, gif, webp, bmp)";
|
||||
case "file too large" -> "이미지 크기는 파일당 5MB 이하여야 합니다.";
|
||||
case "no files", "no valid image files" -> "업로드할 이미지가 없습니다.";
|
||||
case "unsupported file type", "unsupported image type" ->
|
||||
"지원하지 않는 파일 형식입니다. (jpeg, png, gif, webp, bmp, zip, hwp, hwpx)";
|
||||
case "file too large" -> "첨부 파일 크기는 파일당 5MB 이하여야 합니다.";
|
||||
case "no files", "no valid image files", "no valid attachment files" -> "업로드할 파일이 없습니다.";
|
||||
default -> code.startsWith("max images exceeded")
|
||||
? "이슈당 첨부 이미지는 최대 30장까지입니다."
|
||||
: code.startsWith("issue not found") ? "검증 이슈를 찾을 수 없습니다." : code;
|
||||
|
||||
+1
-1
@@ -25,6 +25,6 @@ public class VerificationUploadExceptionHandler {
|
||||
public ResponseEntity<Map<String, String>> handleMultipart(MultipartException e) {
|
||||
log.warn("[VerificationUpload] multipart error: {}", e.getMessage());
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("message", "이미지 파일 형식을 확인할 수 없습니다."));
|
||||
.body(Map.of("message", "첨부 파일 형식을 확인할 수 없습니다."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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._\\-가-힣]", "_");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user