File: RequestBodyLimitTests.cs
Web Access
Project: src\src\Servers\HttpSys\test\FunctionalTests\Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj (Microsoft.AspNetCore.Server.HttpSys.FunctionalTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.InternalTesting;
using Xunit;
 
namespace Microsoft.AspNetCore.Server.HttpSys;
 
public class RequestBodyLimitTests : LoggedTest
{
    [ConditionalFact]
    public async Task ContentLengthEqualsLimit_ReadSync_Success()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, httpContext =>
        {
            httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Equal(11, httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            int read = httpContext.Request.Body.Read(input, 0, input.Length);
            httpContext.Response.ContentLength = read;
            httpContext.Response.Body.Write(input, 0, read);
            return Task.FromResult(0);
        }, options => options.MaxRequestBodySize = 11, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World");
            Assert.Equal("Hello World", response);
        }
    }
 
    [ConditionalFact]
    public async Task ContentLengthEqualsLimit_ReadAsync_Success()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, async httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.True(httpContext.Request.CanHaveBody());
            Assert.Equal(11, httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
            httpContext.Response.ContentLength = read;
            await httpContext.Response.Body.WriteAsync(input, 0, read);
        }, options => options.MaxRequestBodySize = 11, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World");
            Assert.Equal("Hello World", response);
        }
    }
 
    [ConditionalFact]
    public async Task ContentLengthEqualsLimit_ReadBeginEnd_Success()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Equal(11, httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            int read = httpContext.Request.Body.EndRead(httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
            httpContext.Response.ContentLength = read;
            httpContext.Response.Body.EndWrite(httpContext.Response.Body.BeginWrite(input, 0, read, null, null));
            return Task.FromResult(0);
        }, options => options.MaxRequestBodySize = 11, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World");
            Assert.Equal("Hello World", response);
        }
    }
 
    [ConditionalFact]
    public async Task ChunkedEqualsLimit_ReadSync_Success()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, httpContext =>
        {
            httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Null(httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            int read = httpContext.Request.Body.Read(input, 0, input.Length);
            httpContext.Response.ContentLength = read;
            httpContext.Response.Body.Write(input, 0, read);
            return Task.FromResult(0);
        }, options => options.MaxRequestBodySize = 11, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World", chunked: true);
            Assert.Equal("Hello World", response);
        }
    }
 
    [ConditionalFact]
    public async Task ChunkedEqualsLimit_ReadAsync_Success()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, async httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.True(httpContext.Request.CanHaveBody());
            Assert.Null(httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
            httpContext.Response.ContentLength = read;
            await httpContext.Response.Body.WriteAsync(input, 0, read);
        }, options => options.MaxRequestBodySize = 11, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World", chunked: true);
            Assert.Equal("Hello World", response);
        }
    }
 
    [ConditionalFact]
    public async Task ChunkedEqualsLimit_ReadBeginEnd_Success()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Null(httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            int read = httpContext.Request.Body.EndRead(httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
            httpContext.Response.ContentLength = read;
            httpContext.Response.Body.EndWrite(httpContext.Response.Body.BeginWrite(input, 0, read, null, null));
            return Task.FromResult(0);
        }, options => options.MaxRequestBodySize = 11, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World", chunked: true);
            Assert.Equal("Hello World", response);
        }
    }
 
    [ConditionalFact]
    public async Task ContentLengthExceedsLimit_ReadSync_ThrowsImmediately()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, httpContext =>
        {
            httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Equal(11, httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            var ex = Assert.Throws<BadHttpRequestException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
            Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            ex = Assert.Throws<BadHttpRequestException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
            Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            return Task.FromResult(0);
        }, options => options.MaxRequestBodySize = 10, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World");
            Assert.Equal(string.Empty, response);
        }
    }
 
    [ConditionalFact]
    public async Task ContentLengthExceedsLimit_ReadAsync_ThrowsImmediately()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Equal(11, httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            var ex = Assert.Throws<BadHttpRequestException>(() => { var t = httpContext.Request.Body.ReadAsync(input, 0, input.Length); });
            Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            ex = Assert.Throws<BadHttpRequestException>(() => { var t = httpContext.Request.Body.ReadAsync(input, 0, input.Length); });
            Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            return Task.FromResult(0);
        }, options => options.MaxRequestBodySize = 10, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World");
            Assert.Equal(string.Empty, response);
        }
    }
 
    [ConditionalFact]
    public async Task ContentLengthExceedsLimit_ReadBeginEnd_ThrowsImmediately()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Equal(11, httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            var ex = Assert.Throws<BadHttpRequestException>(() => httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
            Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            ex = Assert.Throws<BadHttpRequestException>(() => httpContext.Request.Body.BeginRead(input, 0, input.Length, null, null));
            Assert.Equal("The request's Content-Length 11 is larger than the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            return Task.FromResult(0);
        }, options => options.MaxRequestBodySize = 10, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World");
            Assert.Equal(string.Empty, response);
        }
    }
 
    [ConditionalFact]
    public async Task ChunkedExceedsLimit_ReadSync_ThrowsAtLimit()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, httpContext =>
        {
            httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Null(httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            var ex = Assert.Throws<BadHttpRequestException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
            Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            ex = Assert.Throws<BadHttpRequestException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
            Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            return Task.FromResult(0);
        }, options => options.MaxRequestBodySize = 10, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World", chunked: true);
            Assert.Equal(string.Empty, response);
        }
    }
 
    [ConditionalFact]
    public async Task ChunkedExceedsLimit_ReadAsync_ThrowsAtLimit()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, async httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Null(httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => httpContext.Request.Body.ReadAsync(input, 0, input.Length));
            Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => httpContext.Request.Body.ReadAsync(input, 0, input.Length));
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
        }, options => options.MaxRequestBodySize = 10, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World", chunked: true);
            Assert.Equal(string.Empty, response);
        }
    }
 
    [ConditionalFact]
    public async Task ChunkedExceedsLimit_ReadBeginEnd_ThrowsAtLimit()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Null(httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            var body = httpContext.Request.Body;
            var ex = Assert.Throws<BadHttpRequestException>(() => body.EndRead(body.BeginRead(input, 0, input.Length, null, null)));
            Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            ex = Assert.Throws<BadHttpRequestException>(() => body.EndRead(body.BeginRead(input, 0, input.Length, null, null)));
            Assert.Equal("The total number of bytes read 11 has exceeded the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            return Task.FromResult(0);
        }, options => options.MaxRequestBodySize = 10, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World", chunked: true);
            Assert.Equal(string.Empty, response);
        }
    }
 
    [ConditionalFact]
    public async Task Chunked_ReadSyncPartialBodyUnderLimit_ThrowsAfterLimit()
    {
        var content = new StaggardContent();
        string address;
        using (Utilities.CreateHttpServer(out address, httpContext =>
        {
            httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Null(httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            int read = httpContext.Request.Body.Read(input, 0, input.Length);
            Assert.Equal(10, read);
            content.Block.Release();
            var ex = Assert.Throws<BadHttpRequestException>(() => httpContext.Request.Body.Read(input, 0, input.Length));
            Assert.Equal("The total number of bytes read 20 has exceeded the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
            return Task.FromResult(0);
        }, options => options.MaxRequestBodySize = 10, LoggerFactory))
        {
            string response = await SendRequestAsync(address, content, chunked: true);
            Assert.Equal(string.Empty, response);
        }
    }
 
    [ConditionalFact]
    public async Task Chunked_ReadAsyncPartialBodyUnderLimit_ThrowsAfterLimit()
    {
        var content = new StaggardContent();
        string address;
        using (Utilities.CreateHttpServer(out address, async httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Null(httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
            Assert.Equal(10, read);
            content.Block.Release();
            var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => httpContext.Request.Body.ReadAsync(input, 0, input.Length));
            Assert.Equal("The total number of bytes read 20 has exceeded the request body size limit 10.", ex.Message);
            Assert.Equal(StatusCodes.Status413PayloadTooLarge, ex.StatusCode);
        }, options => options.MaxRequestBodySize = 10, LoggerFactory))
        {
            string response = await SendRequestAsync(address, content, chunked: true);
            Assert.Equal(string.Empty, response);
        }
    }
 
    [ConditionalFact]
    public async Task AdjustLimitPerRequest_ContentLength_ReadAsync_Success()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, async httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Equal(11, feature.MaxRequestBodySize);
            feature.MaxRequestBodySize = 12;
            Assert.Equal(12, httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
            Assert.True(feature.IsReadOnly);
            httpContext.Response.ContentLength = read;
            await httpContext.Response.Body.WriteAsync(input, 0, read);
        }, options => options.MaxRequestBodySize = 11, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World!");
            Assert.Equal("Hello World!", response);
        }
    }
 
    [ConditionalFact]
    public async Task AdjustLimitPerRequest_Chunked_ReadAsync_Success()
    {
        string address;
        using (Utilities.CreateHttpServer(out address, async httpContext =>
        {
            var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
            Assert.NotNull(feature);
            Assert.False(feature.IsReadOnly);
            Assert.Equal(11, feature.MaxRequestBodySize);
            feature.MaxRequestBodySize = 12;
            Assert.Null(httpContext.Request.ContentLength);
            byte[] input = new byte[100];
            int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
            Assert.True(feature.IsReadOnly);
            httpContext.Response.ContentLength = read;
            await httpContext.Response.Body.WriteAsync(input, 0, read);
        }, options => options.MaxRequestBodySize = 11, LoggerFactory))
        {
            var response = await SendRequestAsync(address, "Hello World!", chunked: true);
            Assert.Equal("Hello World!", response);
        }
    }
 
    private Task<string> SendRequestAsync(string uri, string upload, bool chunked = false)
    {
        return SendRequestAsync(uri, new StringContent(upload), chunked);
    }
 
    private async Task<string> SendRequestAsync(string uri, HttpContent content, bool chunked = false)
    {
        using (HttpClient client = new HttpClient())
        {
            client.DefaultRequestHeaders.TransferEncodingChunked = chunked;
            HttpResponseMessage response = await client.PostAsync(uri, content);
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
    }
 
    private class StaggardContent : HttpContent
    {
        public StaggardContent()
        {
            Block = new SemaphoreSlim(0, 1);
        }
 
        public SemaphoreSlim Block { get; private set; }
 
        protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
        {
            await stream.WriteAsync(new byte[10], 0, 10);
            await stream.FlushAsync();
            Assert.True(await Block.WaitAsync(TimeSpan.FromSeconds(10)));
            await stream.WriteAsync(new byte[10], 0, 10);
        }
 
        protected override bool TryComputeLength(out long length)
        {
            length = 10;
            return true;
        }
    }
}