using System.Buffers.Binary; using System.Net.Sockets; using System.Text; namespace Api.SeaHavenIndustries.Helper { public enum DocumentScanResult { Passed, Infected, Unavailable } public interface IDocumentScanner { Task ScanAsync(string path, CancellationToken cancellationToken); } public class ClamAvDocumentScanner : IDocumentScanner { private readonly IConfiguration _configuration; public ClamAvDocumentScanner(IConfiguration configuration) { _configuration = configuration; } public async Task ScanAsync(string path, CancellationToken cancellationToken) { var host = _configuration["ClamAV:Host"]; var port = _configuration.GetValue("ClamAV:Port") ?? 3310; if (string.IsNullOrWhiteSpace(host) || !File.Exists(path)) return DocumentScanResult.Unavailable; try { using var client = new TcpClient(); await client.ConnectAsync(host, port, cancellationToken); await using var network = client.GetStream(); await network.WriteAsync(Encoding.ASCII.GetBytes("zINSTREAM\0"), cancellationToken); await using var file = File.OpenRead(path); var buffer = new byte[64 * 1024]; while (true) { var count = await file.ReadAsync(buffer, cancellationToken); if (count == 0) break; var length = new byte[4]; BinaryPrimitives.WriteInt32BigEndian(length, count); await network.WriteAsync(length, cancellationToken); await network.WriteAsync(buffer.AsMemory(0, count), cancellationToken); } await network.WriteAsync(new byte[4], cancellationToken); await network.FlushAsync(cancellationToken); var responseBuffer = new byte[1024]; var responseLength = await network.ReadAsync(responseBuffer, cancellationToken); var response = Encoding.ASCII.GetString(responseBuffer, 0, responseLength); if (response.Contains("FOUND", StringComparison.OrdinalIgnoreCase)) return DocumentScanResult.Infected; return response.Contains("OK", StringComparison.OrdinalIgnoreCase) ? DocumentScanResult.Passed : DocumentScanResult.Unavailable; } catch (Exception) when (!cancellationToken.IsCancellationRequested) { return DocumentScanResult.Unavailable; } } } }