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;
}
}