using System.Text; namespace w4c_workflows.Services.Nodes.Executors; /// Raw response body plus a "too large" signal, before content interpretation. internal sealed record BodyRead(string? Body, byte[]? Bytes, bool TooLarge); /// /// Shared HTTP response-body reader that enforces the per-run response-size quota. /// A declared Content-Length over the cap is rejected before reading; otherwise /// the stream is copied in chunks and rejected as soon as the cap is crossed, so /// an oversized (or unbounded) body never lands in memory. Both the decoded text /// and the raw bytes are returned, because responseFormat: file needs the /// exact bytes rather than a lossy string. /// internal static class HttpBodyReader { public static async Task ReadAsync( HttpResponseMessage response, long maxBytes, CancellationToken ct) { if (maxBytes <= 0) { var bytes = await response.Content.ReadAsByteArrayAsync(ct); return new BodyRead(ResponseEncoding(response).GetString(bytes), bytes, false); } if (response.Content.Headers.ContentLength is long declared && declared > maxBytes) return new BodyRead(null, null, true); await using var stream = await response.Content.ReadAsStreamAsync(ct); using var buffer = new MemoryStream(); var chunk = new byte[81_920]; int read; while ((read = await stream.ReadAsync(chunk, ct)) > 0) { if (buffer.Length + read > maxBytes) return new BodyRead(null, null, true); buffer.Write(chunk, 0, read); } var raw = buffer.ToArray(); return new BodyRead(ResponseEncoding(response).GetString(raw), raw, false); } /// Content-Type charset when the server declares one, else UTF-8. private static Encoding ResponseEncoding(HttpResponseMessage response) { var charset = response.Content.Headers.ContentType?.CharSet; if (!string.IsNullOrWhiteSpace(charset)) { try { return Encoding.GetEncoding(charset.Trim('"')); } catch (ArgumentException) { // Unknown charset: fall back to UTF-8 rather than failing the node. } } return Encoding.UTF8; } }