using System.Buffers.Binary; using System.Net.Sockets; using System.Text; using Big.Application; namespace Big.Infrastructure; /// /// Scans a document with clamd over its INSTREAM protocol (S-29, ADR-0036): zINSTREAM\0, the /// document as one big-endian length-prefixed chunk, a zero-length terminator, then one reply — /// stream: OK or stream: <signature> FOUND. Anything else (an ERROR reply, a refused /// connection, a timeout) is , so the caller fails closed. /// public sealed class ClamdDocumentScanner(ClamAvOptions options) : IDocumentScanner { public async Task ScanAsync(byte[] content, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(content); using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct); timeout.CancelAfter(options.Timeout); string reply; try { using var client = new TcpClient(); await client.ConnectAsync(options.Host, options.Port, timeout.Token); var stream = client.GetStream(); var length = new byte[4]; BinaryPrimitives.WriteInt32BigEndian(length, content.Length); await stream.WriteAsync("zINSTREAM\0"u8.ToArray(), timeout.Token); await stream.WriteAsync(length, timeout.Token); await stream.WriteAsync(content, timeout.Token); await stream.WriteAsync(new byte[4], timeout.Token); using var reader = new StreamReader(stream, Encoding.ASCII); reply = (await reader.ReadToEndAsync(timeout.Token)).TrimEnd('\0', '\n'); } catch (Exception e) when (e is SocketException or IOException || (e is OperationCanceledException && !ct.IsCancellationRequested)) { return ScanVerdict.Unavailable; } if (reply == "stream: OK") return ScanVerdict.Clean; return reply.EndsWith(" FOUND", StringComparison.Ordinal) ? ScanVerdict.Infected : ScanVerdict.Unavailable; } }