import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.net.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@RestController
@Validated
public class SecureFileProcessingController {
@Value("${app.upload.directory:/app/uploads}")
private String uploadDirectory;
@Value("${app.output.directory:/app/output}")
private String outputDirectory;
@Value("${app.backup.directory:/app/backups}")
private String backupDirectory;
private final SecureFileProcessor fileProcessor;
private final SecurityValidator validator;
private final AuditLogger auditLogger;
public SecureFileProcessingController() {
this.fileProcessor = new SecureFileProcessor();
this.validator = new SecurityValidator();
this.auditLogger = new AuditLogger();
}
// SECURE: File backup using Java NIO instead of external commands
@PostMapping("/api/backup")
public ResponseEntity<BackupResponse> backupFile(
@Valid @RequestBody SecureBackupRequest request,
HttpServletRequest httpRequest) {
String clientIP = getClientIP(httpRequest);
auditLogger.logRequest("backup", request.getFilename(), clientIP);
try {
// Comprehensive input validation
validator.validateFilename(request.getFilename());
Path sourcePath = resolveSecurePath(uploadDirectory, request.getFilename());
// Security checks
if (!Files.exists(sourcePath)) {
auditLogger.logSecurityEvent("file_not_found", request.getFilename(), clientIP);
return ResponseEntity.notFound().build();
}
if (!Files.isRegularFile(sourcePath)) {
return ResponseEntity.badRequest()
.body(new BackupResponse(false, "Path is not a regular file", null));
}
// Check file size
long fileSize = Files.size(sourcePath);
if (fileSize > 100 * 1024 * 1024) { // 100MB limit
return ResponseEntity.badRequest()
.body(new BackupResponse(false, "File too large", null));
}
// SECURE: Use Java NIO for file operations
String backupFilename = generateSecureBackupName(request.getFilename());
Path backupPath = Paths.get(backupDirectory, backupFilename);
// Ensure backup directory exists
Files.createDirectories(backupPath.getParent());
// Perform secure copy
Files.copy(sourcePath, backupPath,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES);
// Verify backup integrity
if (!verifyFileIntegrity(sourcePath, backupPath)) {
Files.deleteIfExists(backupPath);
return ResponseEntity.status(500)
.body(new BackupResponse(false, "Backup integrity check failed", null));
}
auditLogger.logSuccess("backup", request.getFilename(), clientIP);
BackupResponse response = new BackupResponse(
true, "Backup completed successfully", backupFilename
);
return ResponseEntity.ok(response);
} catch (SecurityException e) {
auditLogger.logSecurityEvent("backup_security_violation",
request.getFilename(), clientIP);
return ResponseEntity.status(403)
.body(new BackupResponse(false, "Security validation failed", null));
} catch (Exception e) {
auditLogger.logError("backup", e.getMessage(), clientIP);
return ResponseEntity.status(500)
.body(new BackupResponse(false, "Backup operation failed", null));
}
}
// SECURE: File analysis using Java libraries
@GetMapping("/api/analyze/{filename}")
public ResponseEntity<FileAnalysisResponse> analyzeFile(
@PathVariable @Pattern(regexp = "^[a-zA-Z0-9._-]+$",
message = "Invalid filename format") String filename,
HttpServletRequest httpRequest) {
String clientIP = getClientIP(httpRequest);
auditLogger.logRequest("analyze", filename, clientIP);
try {
validator.validateFilename(filename);
Path filePath = resolveSecurePath(uploadDirectory, filename);
if (!Files.exists(filePath)) {
return ResponseEntity.notFound().build();
}
// SECURE: Use Java libraries for file analysis
FileAnalysisResult analysis = analyzeFileSecurely(filePath);
auditLogger.logSuccess("analyze", filename, clientIP);
FileAnalysisResponse response = new FileAnalysisResponse(
true, "Analysis completed", analysis
);
return ResponseEntity.ok(response);
} catch (SecurityException e) {
auditLogger.logSecurityEvent("analyze_security_violation", filename, clientIP);
return ResponseEntity.status(403)
.body(new FileAnalysisResponse(false, "Security validation failed", null));
} catch (Exception e) {
auditLogger.logError("analyze", e.getMessage(), clientIP);
return ResponseEntity.status(500)
.body(new FileAnalysisResponse(false, "Analysis failed", null));
}
}
// SECURE: Image conversion using Java ImageIO
@PostMapping("/api/convert")
public ResponseEntity<ConvertResponse> convertImage(
@Valid @RequestBody SecureConvertRequest request,
HttpServletRequest httpRequest) {
String clientIP = getClientIP(httpRequest);
auditLogger.logRequest("convert", request.getInputFile(), clientIP);
try {
// Comprehensive validation
validator.validateFilename(request.getInputFile());
validator.validateImageFormat(request.getOutputFormat());
validator.validateQuality(request.getQuality());
Path inputPath = resolveSecurePath(uploadDirectory, request.getInputFile());
if (!Files.exists(inputPath)) {
return ResponseEntity.notFound().build();
}
// SECURE: Use Java ImageIO for image processing
String outputFilename = convertImageSecurely(
inputPath, request.getOutputFormat(), request.getQuality()
);
auditLogger.logSuccess("convert", request.getInputFile(), clientIP);
ConvertResponse response = new ConvertResponse(
true, "Conversion completed successfully", outputFilename
);
return ResponseEntity.ok(response);
} catch (SecurityException e) {
auditLogger.logSecurityEvent("convert_security_violation",
request.getInputFile(), clientIP);
return ResponseEntity.status(403)
.body(new ConvertResponse(false, "Security validation failed", null));
} catch (Exception e) {
auditLogger.logError("convert", e.getMessage(), clientIP);
return ResponseEntity.status(500)
.body(new ConvertResponse(false, "Conversion failed", null));
}
}
// SECURE: Network connectivity test using Java sockets
@PostMapping("/api/network/test")
public ResponseEntity<NetworkTestResponse> testNetwork(
@Valid @RequestBody SecureNetworkTestRequest request,
HttpServletRequest httpRequest) {
String clientIP = getClientIP(httpRequest);
auditLogger.logRequest("network_test", request.getHost(), clientIP);
try {
// Validate inputs
validator.validateHostname(request.getHost());
validator.validatePort(request.getPort());
validator.validateProtocol(request.getProtocol());
// SECURE: Use Java sockets instead of external commands
NetworkTestResult testResult = testNetworkConnectivitySecurely(
request.getHost(), request.getPort(), request.getProtocol()
);
auditLogger.logSuccess("network_test",
request.getHost() + ":" + request.getPort(), clientIP);
NetworkTestResponse response = new NetworkTestResponse(
true, "Network test completed", testResult
);
return ResponseEntity.ok(response);
} catch (SecurityException e) {
auditLogger.logSecurityEvent("network_test_security_violation",
request.getHost(), clientIP);
return ResponseEntity.status(403)
.body(new NetworkTestResponse(false, "Security validation failed", null));
} catch (Exception e) {
auditLogger.logError("network_test", e.getMessage(), clientIP);
return ResponseEntity.status(500)
.body(new NetworkTestResponse(false, "Network test failed", null));
}
}
// Helper methods for secure operations
private Path resolveSecurePath(String baseDirectory, String filename) throws SecurityException {
try {
Path basePath = Paths.get(baseDirectory).toAbsolutePath().normalize();
Path filePath = basePath.resolve(filename).normalize();
// Ensure resolved path is within base directory
if (!filePath.startsWith(basePath)) {
throw new SecurityException("Path traversal detected: " + filename);
}
return filePath;
} catch (Exception e) {
throw new SecurityException("Invalid file path: " + filename, e);
}
}
private String generateSecureBackupName(String originalFilename) {
String baseName = originalFilename.replaceAll("\\.[^.]+$", "");
String extension = originalFilename.substring(originalFilename.lastIndexOf('.'));
String timestamp = String.valueOf(System.currentTimeMillis());
return baseName + "_backup_" + timestamp + extension;
}
private boolean verifyFileIntegrity(Path source, Path backup) throws IOException {
// Compare file sizes
if (Files.size(source) != Files.size(backup)) {
return false;
}
// Compare checksums (simplified - in production, use proper hashing)
try {
byte[] sourceBytes = Files.readAllBytes(source);
byte[] backupBytes = Files.readAllBytes(backup);
return Arrays.equals(sourceBytes, backupBytes);
} catch (Exception e) {
return false;
}
}
private FileAnalysisResult analyzeFileSecurely(Path filePath) throws IOException {
BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
FileAnalysisResult result = new FileAnalysisResult();
result.setFilename(filePath.getFileName().toString());
result.setSize(attrs.size());
result.setCreationTime(attrs.creationTime().toString());
result.setLastModified(attrs.lastModifiedTime().toString());
result.setMimeType(Files.probeContentType(filePath));
result.setReadable(Files.isReadable(filePath));
result.setWritable(Files.isWritable(filePath));
return result;
}
private String convertImageSecurely(Path inputPath, String outputFormat, int quality)
throws IOException {
BufferedImage image = ImageIO.read(inputPath.toFile());
if (image == null) {
throw new IllegalArgumentException("Invalid or unsupported image format");
}
String inputFilename = inputPath.getFileName().toString();
String outputFilename = inputFilename.replaceAll("\\.[^.]+$", "." + outputFormat.toLowerCase());
Path outputPath = Paths.get(outputDirectory, outputFilename);
// Ensure output directory exists
Files.createDirectories(outputPath.getParent());
// Convert image
boolean success = ImageIO.write(image, outputFormat.toLowerCase(), outputPath.toFile());
if (!success) {
throw new RuntimeException("Image conversion failed");
}
return outputFilename;
}
private NetworkTestResult testNetworkConnectivitySecurely(String hostname, int port, String protocol) {
NetworkTestResult result = new NetworkTestResult();
result.setHost(hostname);
result.setPort(port);
result.setProtocol(protocol);
long startTime = System.currentTimeMillis();
try {
if ("ping".equals(protocol)) {
// Use InetAddress for reachability test
InetAddress address = InetAddress.getByName(hostname);
boolean reachable = address.isReachable(5000); // 5 second timeout
result.setSuccess(reachable);
result.setMessage(reachable ? "Host is reachable" : "Host is not reachable");
} else {
// Use Socket for port connectivity test
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(hostname, port), 5000);
result.setSuccess(true);
result.setMessage("Port is open and accepting connections");
} catch (IOException e) {
result.setSuccess(false);
result.setMessage("Port is closed or unreachable: " + e.getMessage());
}
}
} catch (Exception e) {
result.setSuccess(false);
result.setMessage("Network test failed: " + e.getMessage());
}
long endTime = System.currentTimeMillis();
result.setResponseTime(endTime - startTime);
return result;
}
private String getClientIP(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
return request.getRemoteAddr();
}
}
// Secure request DTOs with validation annotations
class SecureBackupRequest {
@NotBlank(message = "Filename is required")
@Size(max = 255, message = "Filename too long")
@Pattern(regexp = "^[a-zA-Z0-9._-]+$", message = "Invalid filename format")
private String filename;
// Getter and setter
public String getFilename() { return filename; }
public void setFilename(String filename) { this.filename = filename; }
}
class SecureConvertRequest {
@NotBlank(message = "Input file is required")
@Size(max = 255, message = "Filename too long")
@Pattern(regexp = "^[a-zA-Z0-9._-]+\\.(jpg|jpeg|png|gif)$",
message = "Invalid image file format")
private String inputFile;
@NotBlank(message = "Output format is required")
@Pattern(regexp = "^(jpg|jpeg|png|gif|bmp|webp)$",
message = "Unsupported output format")
private String outputFormat;
@Min(value = 1, message = "Quality must be at least 1")
@Max(value = 100, message = "Quality cannot exceed 100")
private int quality = 85;
// Getters and setters
public String getInputFile() { return inputFile; }
public void setInputFile(String inputFile) { this.inputFile = inputFile; }
public String getOutputFormat() { return outputFormat; }
public void setOutputFormat(String outputFormat) { this.outputFormat = outputFormat; }
public int getQuality() { return quality; }
public void setQuality(int quality) { this.quality = quality; }
}
class SecureNetworkTestRequest {
@NotBlank(message = "Host is required")
@Size(max = 253, message = "Hostname too long")
@Pattern(regexp = "^[a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?)*$",
message = "Invalid hostname format")
private String host;
@Min(value = 1, message = "Port must be at least 1")
@Max(value = 65535, message = "Port cannot exceed 65535")
private int port;
@NotBlank(message = "Protocol is required")
@Pattern(regexp = "^(ping|tcp)$", message = "Unsupported protocol")
private String protocol;
// Getters and setters
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public String getProtocol() { return protocol; }
public void setProtocol(String protocol) { this.protocol = protocol; }
}
// Response DTOs
class BackupResponse {
private boolean success;
private String message;
private String backupFilename;
public BackupResponse(boolean success, String message, String backupFilename) {
this.success = success;
this.message = message;
this.backupFilename = backupFilename;
}
// Getters
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public String getBackupFilename() { return backupFilename; }
}
class FileAnalysisResponse {
private boolean success;
private String message;
private FileAnalysisResult result;
public FileAnalysisResponse(boolean success, String message, FileAnalysisResult result) {
this.success = success;
this.message = message;
this.result = result;
}
// Getters
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public FileAnalysisResult getResult() { return result; }
}
// Additional supporting classes would be defined here...