mirror of
https://github.com/RoastSlav/quickdrop.git
synced 2026-01-03 21:19:51 -06:00
Added a client side check for the file size and now deletes the temp decrypted file when downloading
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
package org.rostislav.quickdrop.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.rostislav.quickdrop.model.FileEntity;
|
||||
import org.rostislav.quickdrop.service.FileService;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -12,8 +13,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import static org.rostislav.quickdrop.util.FileUtils.populateModelAttributes;
|
||||
|
||||
@@ -66,7 +66,7 @@ public class FileViewController {
|
||||
}
|
||||
|
||||
@GetMapping("/download/{id}")
|
||||
public ResponseEntity<Resource> downloadFile(@PathVariable Long id, HttpServletRequest request) {
|
||||
public ResponseEntity<StreamingResponseBody> downloadFile(@PathVariable Long id, HttpServletRequest request) {
|
||||
FileEntity fileEntity = fileService.getFile(id);
|
||||
|
||||
if (fileEntity.passwordHash != null) {
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
package org.rostislav.quickdrop.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.rostislav.quickdrop.model.FileEntity;
|
||||
import org.rostislav.quickdrop.model.FileUploadRequest;
|
||||
import org.rostislav.quickdrop.repository.FileRepository;
|
||||
@@ -14,16 +25,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
|
||||
import static org.rostislav.quickdrop.util.DataValidator.validateObjects;
|
||||
import static org.rostislav.quickdrop.util.FileEncryptionUtils.decryptFile;
|
||||
@@ -42,6 +44,41 @@ public class FileService {
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
private static StreamingResponseBody getStreamingResponseBody(Path outputFile, FileEntity fileEntity) {
|
||||
return outputStream -> {
|
||||
try (FileInputStream inputStream = new FileInputStream(outputFile.toFile())) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
outputStream.flush();
|
||||
} finally {
|
||||
if (fileEntity.passwordHash != null) {
|
||||
try {
|
||||
Files.delete(outputFile);
|
||||
logger.info("Decrypted file deleted: {}", outputFile);
|
||||
} catch (
|
||||
Exception e) {
|
||||
logger.error("Error deleting decrypted file: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean saveUnencryptedFile(MultipartFile file, Path path) {
|
||||
try {
|
||||
Files.createFile(path);
|
||||
Files.write(path, file.getBytes());
|
||||
logger.info("File saved: {}", path);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error saving file: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public FileEntity saveFile(MultipartFile file, FileUploadRequest fileUploadRequest) {
|
||||
if (!validateObjects(file, fileUploadRequest)) {
|
||||
return null;
|
||||
@@ -73,20 +110,12 @@ public class FileService {
|
||||
fileEntity.passwordHash = passwordEncoder.encode(fileUploadRequest.password);
|
||||
}
|
||||
|
||||
logger.info("FileEntity saved: {}", fileEntity);
|
||||
logger.info("FileEntity inserted into database: {}", fileEntity);
|
||||
return fileRepository.save(fileEntity);
|
||||
}
|
||||
|
||||
private boolean saveUnencryptedFile(MultipartFile file, Path path) {
|
||||
try {
|
||||
Files.createFile(path);
|
||||
Files.write(path, file.getBytes());
|
||||
logger.info("File saved: {}", path);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error saving file: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
public List<FileEntity> getFiles() {
|
||||
return fileRepository.findAll();
|
||||
}
|
||||
|
||||
public boolean saveEncryptedFile(Path savePath, MultipartFile file, FileUploadRequest fileUploadRequest) {
|
||||
@@ -102,7 +131,9 @@ public class FileService {
|
||||
|
||||
try {
|
||||
Path encryptedFile = Files.createFile(savePath);
|
||||
logger.info("Encrypting file: {}", encryptedFile);
|
||||
encryptFile(tempFile.toFile(), encryptedFile.toFile(), fileUploadRequest.password);
|
||||
logger.info("Encrypted file saved: {}", encryptedFile);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error encrypting file: {}", e.getMessage());
|
||||
return false;
|
||||
@@ -110,6 +141,7 @@ public class FileService {
|
||||
|
||||
try {
|
||||
Files.delete(tempFile);
|
||||
logger.info("Temp file deleted: {}", tempFile);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error deleting temp file: {}", e.getMessage());
|
||||
return false;
|
||||
@@ -118,13 +150,10 @@ public class FileService {
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<FileEntity> getFiles() {
|
||||
return fileRepository.findAll();
|
||||
}
|
||||
|
||||
public ResponseEntity<Resource> downloadFile(Long id, String password) {
|
||||
public ResponseEntity<StreamingResponseBody> downloadFile(Long id, String password) {
|
||||
FileEntity fileEntity = fileRepository.findById(id).orElse(null);
|
||||
if (fileEntity == null) {
|
||||
logger.info("File not found: {}", id);
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
@@ -133,7 +162,9 @@ public class FileService {
|
||||
if (fileEntity.passwordHash != null) {
|
||||
try {
|
||||
outputFile = File.createTempFile("Decrypted", "tmp").toPath();
|
||||
logger.info("Decrypting file: {}", pathOfFile);
|
||||
decryptFile(pathOfFile.toFile(), outputFile.toFile(), password);
|
||||
logger.info("File decrypted: {}", outputFile);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error decrypting file: {}", e.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
@@ -142,13 +173,16 @@ public class FileService {
|
||||
outputFile = pathOfFile;
|
||||
}
|
||||
|
||||
StreamingResponseBody responseBody = getStreamingResponseBody(outputFile, fileEntity);
|
||||
|
||||
try {
|
||||
Resource resource = new UrlResource(outputFile.toUri());
|
||||
logger.info("Sending file: {}", fileEntity);
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileEntity.name, StandardCharsets.UTF_8) + "\"")
|
||||
.header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
|
||||
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resource.contentLength()))
|
||||
.body(resource);
|
||||
.body(responseBody);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error reading file: {}", e.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
|
||||
@@ -8,8 +8,8 @@ spring.jpa.hibernate.ddl-auto=update
|
||||
spring.thymeleaf.prefix=classpath:/templates/
|
||||
spring.thymeleaf.suffix=.html
|
||||
spring.thymeleaf.cache=false
|
||||
spring.servlet.multipart.max-file-size=1024MB
|
||||
spring.servlet.multipart.max-request-size=1024MB
|
||||
spring.servlet.multipart.max-file-size=1025MB
|
||||
spring.servlet.multipart.max-request-size=1025MB
|
||||
server.tomcat.connection-timeout=60000
|
||||
file.save.path=files
|
||||
file.max.age=30
|
||||
|
||||
@@ -67,4 +67,17 @@ function isPasswordProtected() {
|
||||
const passwordField = document.getElementById("password");
|
||||
|
||||
return passwordField && passwordField.value.trim() !== "";
|
||||
}
|
||||
|
||||
function validateFileSize() {
|
||||
const file = document.getElementById('file').files[0];
|
||||
const maxSize = 1024 * 1024 * 1024; // 1GB
|
||||
const fileSizeAlert = document.getElementById('fileSizeAlert');
|
||||
|
||||
if (file.size > maxSize) {
|
||||
fileSizeAlert.style.display = 'block';
|
||||
document.getElementById('file').value = '';
|
||||
} else {
|
||||
fileSizeAlert.style.display = 'none';
|
||||
}
|
||||
}
|
||||
@@ -67,9 +67,23 @@
|
||||
name="file"
|
||||
required
|
||||
type="file"
|
||||
onchange="validateFileSize()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- File Size Alert -->
|
||||
<div class="alert alert-danger"
|
||||
id="fileSizeAlert"
|
||||
role="alert"
|
||||
style="display: none;">
|
||||
File
|
||||
size
|
||||
exceeds
|
||||
the
|
||||
1GB
|
||||
limit.
|
||||
</div>
|
||||
|
||||
<!-- Description Input -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="description">Description:</label>
|
||||
|
||||
Reference in New Issue
Block a user