using System; using System.IO; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Hangfire; using MalwareMultiScan.Backends.Backends.Implementations; using MalwareMultiScan.Shared.Data.Enums; using MalwareMultiScan.Shared.Data.Responses; using MalwareMultiScan.Shared.Interfaces; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace MalwareMultiScan.Worker.Jobs { public class ScanJob { private readonly IScanBackend _backend; private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; private readonly int _scanTimeout; public ScanJob(IConfiguration configuration, ILogger logger, IHttpClientFactory httpClientFactory) { _logger = logger; _httpClientFactory = httpClientFactory; _scanTimeout = configuration.GetValue("ScanTimeout"); _backend = configuration.GetValue("BackendType") switch { BackendType.Defender => new WindowsDefenderScanBackend(logger), BackendType.Clamav => new ClamavScanBackend(logger), BackendType.DrWeb => new DrWebScanBackend(logger), BackendType.Kes => new KesScanBackend(logger), _ => throw new NotImplementedException() }; } private async Task PostResult(ResultResponse response, Uri callbackUrl, CancellationToken cancellationToken) { var serializedResponse = JsonSerializer.Serialize( response, typeof(ResultResponse), new JsonSerializerOptions { WriteIndented = true }); _logger.LogInformation( $"Sending following payload to {callbackUrl}: {serializedResponse}"); using var httpClient = _httpClientFactory.CreateClient(); var callbackResponse = await httpClient.PostAsync(callbackUrl, new StringContent(serializedResponse, Encoding.UTF8, "application/json"), cancellationToken); _logger.LogInformation($"Callback URL {callbackUrl} returned a status {callbackResponse.StatusCode}"); } private async Task Scan(Func> scanMethod, Uri callbackUrl) { var cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.CancelAfter(_scanTimeout * 1000); var cancellationToken = cancellationTokenSource.Token; var response = new ResultResponse { Backend = _backend.Id }; try { response.Success = true; response.Threats = await scanMethod(cancellationToken); response.DatabaseLastUpdate = _backend.DatabaseLastUpdate; } catch (Exception exception) { response.Success = false; _logger.LogError(exception, "Scanning failed with exception"); } await PostResult(response, callbackUrl, cancellationToken); } [AutomaticRetry(Attempts = 0, OnAttemptsExceeded = AttemptsExceededAction.Delete)] public async Task ScanUrl(Uri url, Uri callbackUrl) { await Scan(async t => await _backend.ScanAsync(url, t), callbackUrl); } [AutomaticRetry(Attempts = 0, OnAttemptsExceeded = AttemptsExceededAction.Delete)] public async Task ScanFile(string file, Uri callbackUrl) { try { await Scan(async t => await _backend.ScanAsync(file, t), callbackUrl); } finally { File.Delete(file); } } } }