diff --git a/README.md b/README.md
index c0e0d33..157f60c 100644
--- a/README.md
+++ b/README.md
@@ -22,16 +22,36 @@
## **Конфигурация и запуск**
### **1. Конфигурация параметров для сервиса**
-Для корректного запуска сервиса добавьте следующие параметры в файл `application.properties`:
-```properties
-file.upload.directory=/tmp/uploaded_files # Путь для хранения временных файлов
-spring.servlet.multipart.max-file-size=10MB # Максимальный размер загружаемого файла
-spring.servlet.multipart.max-request-size=10MB # Максимальный размер запроса
-server.port=8080 # Порт, на котором работает сервис
+Для корректного запуска сервиса добавьте следующие параметры в файл `application.yaml`:
+```yaml
+file:
+ upload:
+ directory: '/tmp/uploaded_files' # Путь для хранения временных файлов
+spring:
+ servlet:
+ multipart:
+ max-file-size: 10MB # Максимальный размер загружаемого файла
+ max-request-size: 10MB # Максимальный размер запроса
+server:
+ port: 8080 # Порт, на котором работает сервис
+```
+Если KESL использует русскую локализацию, то следует в файл `application.yaml` добавить следующий параметр:
+```yaml
+password:
+ protected:
+ result:
+ name: 'Объекты, защищенные паролем'
+```
+Для английской локализации:
+```yaml
+password:
+ protected:
+ result:
+ name: 'Password-protected objects'
```
### **2. Запуск JAR-файла с конфигурационным файлом**
```bash
-java -jar app.jar --spring.config.location=file:/path/to/your/application.properties
+java -jar app.jar --spring.config.location=file:/path/to/your/application.yaml -Dfile.encoding=UTF8
```
Ссылка на av-service задаётся в перекладчике (сервис ervu-lkrp-av) в параметре AV_REST_ADDRESS. Пример - AV_REST_ADDRESS=http://10.10.10.10:8080/av-wrapper/scan-file
\ No newline at end of file
diff --git a/av-service.service b/av-service.service
index 100bb24..79be821 100644
--- a/av-service.service
+++ b/av-service.service
@@ -8,7 +8,7 @@ Type=simple
Restart=on-failure
User=root
WorkingDirectory=/opt/av-service
-ExecStart=/usr/bin/java -jar av-service.jar
+ExecStart=/usr/bin/java -jar av-service.jar -Dfile.encoding=UTF8
[Install]
WantedBy=multi-user.target
diff --git a/pom.xml b/pom.xml
index 07262c6..376be50 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,6 +22,12 @@
spring-boot-starter-web
+
+ org.zeroturnaround
+ zt-exec
+ 1.12
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/src/main/java/ru/micord/av/service/controller/FileScanController.java b/src/main/java/ru/micord/av/service/controller/FileScanController.java
index dd7d0be..b0ec15f 100644
--- a/src/main/java/ru/micord/av/service/controller/FileScanController.java
+++ b/src/main/java/ru/micord/av/service/controller/FileScanController.java
@@ -9,7 +9,6 @@ import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
-import ru.micord.av.service.model.ScanResult;
import ru.micord.av.service.service.FileScanService;
/**
@@ -32,11 +31,10 @@ public class FileScanController {
return ResponseEntity.badRequest().build();
}
try {
- ScanResult result = fileScanService.scanFile(file);
- LOGGER.info("Scan result for file {}: status - {}, verdicts - {}",
+ int result = fileScanService.scanFile(file);
+ LOGGER.info("Scan result for file {}: exitCode - {}",
file.getOriginalFilename(),
- result.status(),
- String.join(", ", result.verdicts()));
+ result);
return ResponseEntity.ok(result);
}
catch (Exception e) {
diff --git a/src/main/java/ru/micord/av/service/model/ScanResult.java b/src/main/java/ru/micord/av/service/model/ScanResult.java
deleted file mode 100644
index 2f9196f..0000000
--- a/src/main/java/ru/micord/av/service/model/ScanResult.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package ru.micord.av.service.model;
-
-import java.util.Map;
-
-/**
- * @author Adel Kalimullin
- */
-public record ScanResult(String completed, String created, Integer progress,
- Map scan_result, String status, String[] verdicts) {
- public record Scan(String started, String stopped, Threat[] threats, String verdict) {
- public static final String VERDICT_CLEAN = "clean";
- public static final String VERDICT_INFECTED = "infected";
-
- public record Threat(String name, String object) {
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/ru/micord/av/service/service/FileScanService.java b/src/main/java/ru/micord/av/service/service/FileScanService.java
index 33f4771..250d769 100644
--- a/src/main/java/ru/micord/av/service/service/FileScanService.java
+++ b/src/main/java/ru/micord/av/service/service/FileScanService.java
@@ -2,23 +2,20 @@ package ru.micord.av.service.service;
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
+import org.zeroturnaround.exec.ProcessExecutor;
+import org.zeroturnaround.exec.ProcessResult;
+import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;
import ru.micord.av.service.exception.AvException;
-import ru.micord.av.service.model.ScanResult;
/**
* @author Adel Kalimullin
@@ -30,21 +27,16 @@ public class FileScanService {
private static final Logger LOGGER = LoggerFactory.getLogger(FileScanService.class);
@Value("${file.upload.directory}")
private String uploadDirectory;
+ @Value("${password.protected.result.name:Объекты, защищенные паролем}")
+ private String passwordProtectedResultName;
- public ScanResult scanFile(MultipartFile file) {
+ public int scanFile(MultipartFile file) {
File tempFile = null;
- LocalDateTime startTime;
- LocalDateTime stopTime;
try {
tempFile = saveFile(file);
- startTime = LocalDateTime.now();
- String rawResult = runKeslScan(tempFile);
- stopTime = LocalDateTime.now();
- return parseKeslResponse(rawResult, startTime, stopTime, tempFile,
- file.getOriginalFilename()
- );
+ return runKeslScan(tempFile, file.getOriginalFilename());
}
- catch (Exception e) {
+ catch (IOException | InterruptedException | TimeoutException e) {
throw new AvException("Error scanning file: " + file.getOriginalFilename(), e);
}
finally {
@@ -61,72 +53,49 @@ public class FileScanService {
private File saveFile(MultipartFile file) throws IOException {
Path uploadPath = Paths.get(uploadDirectory);
- if (!Files.exists(uploadPath)) {
- Files.createDirectories(uploadPath);
- }
- Path tempFile = Files.createTempFile(uploadPath, "kesl-upload-", ".tmp");
- file.transferTo(tempFile.toFile());
- return tempFile.toFile();
+ Files.createDirectories(uploadPath);
+ Path tempFilePath = Files.createTempFile(uploadPath, "kesl-upload-", ".tmp");
+ File tempFile = tempFilePath.toFile();
+ file.transferTo(tempFile);
+ return tempFile;
}
- private String runKeslScan(File file) throws IOException, InterruptedException {
- ProcessBuilder processBuilder = new ProcessBuilder(KESL_CONTROL, KESL_SCAN,
- file.getAbsolutePath()
- );
- processBuilder.redirectErrorStream(true);
- Process process = processBuilder.start();
- try (InputStream inputStream = process.getInputStream()) {
- String result = new String(inputStream.readAllBytes());
- int exitCode = process.waitFor();
- if (exitCode != 0 && exitCode != 72) {
- throw new AvException("KESL error, exit code: " + exitCode);
- }
- return result;
+ private int runKeslScan(File file, String originalFileName)
+ throws IOException, InterruptedException, TimeoutException {
+ ProcessResult processResult = new ProcessExecutor()
+ .command(KESL_CONTROL, KESL_SCAN, file.getAbsolutePath())
+ .redirectOutput(Slf4jStream.of(getClass()).asDebug())
+ .readOutput(true)
+ .execute();
+ String processOutput = processResult.outputUTF8();
+ int exitCode = processResult.getExitValue();
+
+ if (exitCode != 0 && exitCode != 72) {
+ throw new AvException(
+ "KESL error scanning file: " + originalFileName + ", exit code: " + exitCode);
}
+ checkScanResult(processOutput, originalFileName);
+
+ return exitCode;
}
- private ScanResult parseKeslResponse(String rawResult, LocalDateTime startTime,
- LocalDateTime stopTime, File file, String originalFilename) {
- Map scanResults = new HashMap<>();
- List verdicts = new ArrayList<>();
- List threats = new ArrayList<>();
- String verdict = null;
+ private void checkScanResult(String result, String originalFileName) {
+ if (!result.contains(passwordProtectedResultName)) {
+ throw new AvException(String.format(
+ "File scan result doesn't contains \"%s\", "
+ + "please check property \"password.protected.result.name\" is correct",
+ passwordProtectedResultName
+ ));
+ }
- for (String line : rawResult.split("\n")) {
- if (line.startsWith("Infected objects")) {
- verdict = extractInteger(line) > 0
- ? ScanResult.Scan.VERDICT_INFECTED
- : ScanResult.Scan.VERDICT_CLEAN;
- if (ScanResult.Scan.VERDICT_INFECTED.equals(verdict))
- threats.add(new ScanResult.Scan.Threat("", file.getAbsolutePath()));
- }
- if (line.startsWith("Scan errors") && extractInteger(line) > 0) {
- verdict = "error";
+ for (String line : result.split("\n")) {
+ String[] lineParts = line.split(":");
+ if (lineParts.length > 1) {
+ if (lineParts[0].startsWith(passwordProtectedResultName)
+ && Integer.parseInt(lineParts[1].trim()) > 0) {
+ throw new AvException("Detected password-protected file: " + originalFileName);
+ }
}
}
- if (verdict != null) {
- scanResults.put(originalFilename,
- new ScanResult.Scan(startTime.toString(), stopTime.toString(),
- threats.toArray(new ScanResult.Scan.Threat[0]), verdict
- )
- );
- verdicts.add(verdict);
- }
- return new ScanResult(startTime.toString(), stopTime.toString(), 100, scanResults, "completed",
- verdicts.toArray(new String[0])
- );
- }
-
- private int extractInteger(String line) {
- String[] parts = line.split(":");
- if (parts.length > 1) {
- try {
- return Integer.parseInt(parts[1].trim());
- }
- catch (NumberFormatException e) {
- throw new RuntimeException(e);
- }
- }
- return 0;
}
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
deleted file mode 100644
index ee7a3e8..0000000
--- a/src/main/resources/application.properties
+++ /dev/null
@@ -1 +0,0 @@
-file.upload.directory = /tmp/uploaded_files
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
new file mode 100644
index 0000000..be42268
--- /dev/null
+++ b/src/main/resources/application.yaml
@@ -0,0 +1,8 @@
+file:
+ upload:
+ directory: '/tmp/uploaded_files'
+
+password:
+ protected:
+ result:
+ name: 'Объекты, защищенные паролем'
\ No newline at end of file