diff --git a/src/main/java/com/alist/api/common/modules/file/FileController.java b/src/main/java/com/alist/api/common/modules/file/FileController.java index 70e7cce..bb6660e 100644 --- a/src/main/java/com/alist/api/common/modules/file/FileController.java +++ b/src/main/java/com/alist/api/common/modules/file/FileController.java @@ -14,14 +14,16 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ContentDisposition; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.*; import org.springframework.web.bind.annotation.*; +import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; @Slf4j @Tag( @@ -183,10 +185,10 @@ public class FileController { summary = "파일 보기" , description = "권한 확인 후 Nginx X-Accel-Redirect로 파일을 inline 조회합니다.") @GetMapping("/view/{fileUuid}") - public ResponseEntity fileView( + public ResponseEntity fileView( @PathVariable String fileUuid , HttpServletRequest request - ) { + ) throws IOException { FileDownloadDto fileDownloadDto = new FileDownloadDto(); fileDownloadDto.setFileUuid(fileUuid); fileDownloadDto.setEventType("VIEW"); @@ -200,33 +202,36 @@ public class FileController { return ResponseEntity.notFound().build(); } - if (fileService.isRedirectMode() || fileService.isLocalTestRequest(request)) { - return ResponseEntity.status(HttpStatus.FOUND) - .location(URI.create(fileService.buildPublicViewUrl(target.getSavePath()))) - .build(); + Path filePath = fileService.resolveStoredFilePath(target.getSavePath()); + + if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) { + return ResponseEntity.notFound().build(); } - HttpHeaders headers = new HttpHeaders(); - headers.add("X-Accel-Redirect", fileService.buildAccelRedirectPath(target.getSavePath())); - headers.add(HttpHeaders.CONTENT_TYPE, - target.getContentType() == null ? "application/octet-stream" : target.getContentType()); - headers.add(HttpHeaders.CONTENT_DISPOSITION, - ContentDisposition.inline() - .filename(target.getOriginName(), StandardCharsets.UTF_8) - .build() - .toString()); + Resource resource = new InputStreamResource(Files.newInputStream(filePath)); + String contentType = target.getContentType() == null + ? "application/octet-stream" + : target.getContentType(); - return new ResponseEntity<>(headers, HttpStatus.OK); + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .contentLength(Files.size(filePath)) + .header(HttpHeaders.CONTENT_DISPOSITION, + ContentDisposition.inline() + .filename(target.getOriginName(), StandardCharsets.UTF_8) + .build() + .toString()) + .body(resource); } @Operation( summary = "파일 다운로드" , description = "권한 확인 후 파일 다운로드 로그를 남기고 redirect 또는 Nginx X-Accel-Redirect로 파일 다운로드 합니다.") @GetMapping("/download/{fileUuid}") - public ResponseEntity fileDownload( + public ResponseEntity fileDownload( @PathVariable String fileUuid , HttpServletRequest request - ) { + ) throws IOException { FileDownloadDto fileDownloadDto = new FileDownloadDto(); fileDownloadDto.setFileUuid(fileUuid); fileDownloadDto.setEventType("DOWNLOAD"); @@ -240,22 +245,25 @@ public class FileController { return ResponseEntity.notFound().build(); } - if (fileService.isRedirectMode() || fileService.isLocalTestRequest(request)) { - return ResponseEntity.status(HttpStatus.FOUND) - .location(URI.create(fileService.buildPublicDownloadUrl(target.getSavePath()))) - .build(); + Path filePath = fileService.resolveStoredFilePath(target.getSavePath()); + + if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) { + return ResponseEntity.notFound().build(); } - HttpHeaders headers = new HttpHeaders(); - headers.add("X-Accel-Redirect", fileService.buildAccelRedirectPath(target.getSavePath())); - headers.add(HttpHeaders.CONTENT_TYPE, - target.getContentType() == null ? "application/octet-stream" : target.getContentType()); - headers.add(HttpHeaders.CONTENT_DISPOSITION, - ContentDisposition.attachment() - .filename(target.getOriginName(), StandardCharsets.UTF_8) - .build() - .toString()); + Resource resource = new InputStreamResource(Files.newInputStream(filePath)); + String contentType = target.getContentType() == null + ? "application/octet-stream" + : target.getContentType(); - return new ResponseEntity<>(headers, HttpStatus.OK); + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .contentLength(Files.size(filePath)) + .header(HttpHeaders.CONTENT_DISPOSITION, + ContentDisposition.attachment() + .filename(target.getOriginName(), StandardCharsets.UTF_8) + .build() + .toString()) + .body(resource); } } diff --git a/src/main/java/com/alist/api/common/modules/file/service/FileService.java b/src/main/java/com/alist/api/common/modules/file/service/FileService.java index 9eb5685..f9a6fa3 100644 --- a/src/main/java/com/alist/api/common/modules/file/service/FileService.java +++ b/src/main/java/com/alist/api/common/modules/file/service/FileService.java @@ -47,12 +47,6 @@ public class FileService { @Value("${file.upload.final-root}") private String uploadFinalRoot; - @Value("${file.download.redirect-prefix:/protected-files}") - private String fileDownloadRedirectPrefix; - - @Value("${file.download.mode:accel}") - private String fileDownloadMode; - private final FileMapper fileMapper; private final JwtTokenProvider jwtTokenProvider; private final StringRedisTemplate stringRedisTemplate; @@ -656,63 +650,20 @@ public class FileService { return fileDetailInfo; } - public String buildAccelRedirectPath(String savePath) { + public Path resolveStoredFilePath(String savePath) { if (savePath == null || savePath.isBlank()) { throw new IllegalArgumentException("savePath is empty"); } - String prefix = (fileDownloadRedirectPrefix == null || fileDownloadRedirectPrefix.isBlank()) - ? "/protected-files" - : fileDownloadRedirectPrefix.trim(); + Path root = Paths.get(uploadFinalRoot).normalize().toAbsolutePath(); + String relativePath = savePath.startsWith("/") ? savePath.substring(1) : savePath; - if (!prefix.startsWith("/")) { - prefix = "/" + prefix; + Path resolved = root.resolve(relativePath).normalize(); + + if (!resolved.startsWith(root)) { + throw new IllegalArgumentException("invalid savePath"); } - return prefix + savePath; - } - - public boolean isRedirectMode() { - return "redirect".equalsIgnoreCase(fileDownloadMode); - } - - public String buildPublicViewUrl(String savePath) { - return buildPublicTestUrl("/public-test-view", savePath); - } - - public String buildPublicDownloadUrl(String savePath) { - return buildPublicTestUrl("/public-test-download", savePath); - } - - private String buildPublicTestUrl(String prefix, String savePath) { - if (savePath == null || savePath.isBlank()) { - throw new IllegalArgumentException("savePath is empty"); - } - - String baseUrl = publicBaseUrl == null ? "" : publicBaseUrl.trim(); - if (baseUrl.endsWith("/")) { - baseUrl = baseUrl.substring(0, baseUrl.length() - 1); - } - - return baseUrl + prefix + savePath; - } - - public boolean isLocalTestRequest(HttpServletRequest request) { - return containsLocalhost(request.getHeader("Referer")) - || containsLocalhost(request.getHeader("Origin")) - || containsLocalhost(request.getHeader("Host")) - || containsLocalhost(request.getHeader("X-Forwarded-Host")); - } - - public boolean containsLocalhost(String value) { - if (value == null || value.isBlank()) { - return false; - } - - String lower = value.toLowerCase(); - return lower.contains("localhost") - || lower.contains("127.0.0.1") - || lower.contains("[::1]") - || lower.contains("::1"); + return resolved; } } diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 165b919..5026f68 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -52,9 +52,6 @@ file: interrupt-seconds: 30 auth-cache: ttl-seconds: 20 - download: - mode: redirect - redirect-prefix: /public-test-files springdoc: api-docs: diff --git a/src/main/resources/application-pjt.yaml b/src/main/resources/application-pjt.yaml index 568b8d5..0b1f4e7 100644 --- a/src/main/resources/application-pjt.yaml +++ b/src/main/resources/application-pjt.yaml @@ -54,9 +54,6 @@ file: interrupt-seconds: 30 auth-cache: ttl-seconds: 20 - download: - mode: accel - redirect-prefix: /protected-files springdoc: api-docs: