using System.Net; using System.Text; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using ZB.MOM.WW.ScadaBridge.AuditLog.Configuration; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services; using ZB.MOM.WW.ScadaBridge.InboundAPI; using ZB.MOM.WW.ScadaBridge.InboundAPI.Middleware; namespace ZB.MOM.WW.ScadaBridge.Host.Tests; public class InboundBodyLimitKestrelTests { [Theory] [InlineData(false, false, false)] [InlineData(false, true, false)] [InlineData(true, false, false)] [InlineData(true, true, false)] [InlineData(false, false, true)] public async Task EI10_OverLimitBody_AuditCannotDisableKestrelCap(bool http2, bool skipCapture, bool declared) { var builder = WebApplication.CreateBuilder(); builder.WebHost.ConfigureKestrel(o => o.Listen(IPAddress.Loopback, 0, l => l.Protocols = http2 ? HttpProtocols.Http2 : HttpProtocols.Http1)); builder.Services.Configure(o => o.MaxRequestBodyBytes = 128); builder.Services.Configure(o => o.PerTargetOverrides["echo"] = new PerTargetRedactionOverride { SkipBodyCapture = skipCapture }); builder.Services.AddSingleton(Substitute.For()); await using var app = builder.Build(); app.UseRouting(); app.UseAuditWriteMiddleware(); var dispatched = 0; app.MapPost("/api/{methodName}", async (HttpContext context) => { await context.Request.Body.CopyToAsync(Stream.Null); Interlocked.Increment(ref dispatched); return Results.Ok(); }).AddEndpointFilter(); await app.StartAsync(); var address = app.Services.GetRequiredService().Features.Get()!.Addresses.Single(); using var client = new HttpClient(); using var request = new HttpRequestMessage(HttpMethod.Post, address + "/api/echo") { Version = http2 ? HttpVersion.Version20 : HttpVersion.Version11, VersionPolicy = HttpVersionPolicy.RequestVersionExact }; var bytes = Encoding.UTF8.GetBytes("{\"value\":\"" + new string('x', 1000) + "\"}"); request.Content = declared ? new ByteArrayContent(bytes) : new UnknownLengthContent(bytes); request.Content.Headers.ContentType = new("application/json"); using var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.RequestEntityTooLarge, response.StatusCode); Assert.Equal(0, dispatched); } private sealed class UnknownLengthContent(byte[] bytes) : HttpContent { protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) => stream.WriteAsync(bytes).AsTask(); protected override bool TryComputeLength(out long length) { length = 0; return false; } } }