Skip to content

Commit

Permalink
Merge pull request #1191 from cabinetoffice/DP-1024-uploading-25md-fi…
Browse files Browse the repository at this point in the history
…le-error

DP-1024 - Uploading a file over 25mb results in error
  • Loading branch information
dbgoaco authored Jan 30, 2025
2 parents 8b07dd1 + ac26d2b commit 6779fe3
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 7 deletions.
88 changes: 88 additions & 0 deletions Frontend/CO.CDP.OrganisationApp.Tests/FileUploadMiddlewareTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using CO.CDP.OrganisationApp.Pages.Forms;
using Microsoft.AspNetCore.Http;
using Moq;
using System.Net;

namespace CO.CDP.OrganisationApp.Tests;

public class FileUploadMiddlewareTests
{
public class FileUploadMiddlewareTest : FileUploadMiddleware
{
public FileUploadMiddlewareTest(RequestDelegate next) : base(next)
{
}

protected override async Task WriteAsync(HttpContext context)
{
var writer = new StreamWriter(context.Response.Body);

await writer.WriteAsync($"The uploaded file is too large. Maximum allowed size is {FormElementFileUploadModel.AllowedMaxFileSizeMB} MB.");
writer.Flush();
}
}

[Fact]
public async Task InvokeAsync_ShouldWriteResponse_WhenStatusCodeIs400AndContentTypeIsMultipartFormData()
{
var responseBody = new MemoryStream();
var httpContextMock = new Mock<HttpContext>();
var middleware = SetUp(HttpStatusCode.BadRequest, "multipart/form-data;", httpContextMock, responseBody);

await middleware.InvokeAsync(httpContextMock.Object);

responseBody.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(responseBody);
var responseBodyText = await reader.ReadToEndAsync();
Assert.Contains("The uploaded file is too large. Maximum allowed size is", responseBodyText);
}

[Fact]
public async Task InvokeAsync_ShouldNotWriteResponse_WhenStatusCodeIsNot400()
{
var responseBody = new MemoryStream();
var httpContextMock = new Mock<HttpContext>();
var middleware = SetUp(HttpStatusCode.OK, "multipart/form-data;", httpContextMock, responseBody);

await middleware.InvokeAsync(httpContextMock.Object);

responseBody.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(responseBody);
var responseBodyText = await reader.ReadToEndAsync();
Assert.Contains(string.Empty, responseBodyText);
}

[Fact]
public async Task InvokeAsync_ShouldNotWriteResponse_WhenContentTypeIsNotMultipartFormData()
{
var responseBody = new MemoryStream();
var httpContextMock = new Mock<HttpContext>();
var middleware = SetUp(HttpStatusCode.BadRequest, "application/json", httpContextMock, responseBody);

await middleware.InvokeAsync(httpContextMock.Object);

responseBody.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(responseBody);
var responseBodyText = await reader.ReadToEndAsync();
Assert.Contains(string.Empty, responseBodyText);
}

private FileUploadMiddleware SetUp(HttpStatusCode statusCode,
string contentType,
Mock<HttpContext> httpContextMock,
MemoryStream responseBody)
{
var requestMock = new Mock<HttpRequest>();
var responseMock = new Mock<HttpResponse>();
var requestDelegateMock = new Mock<RequestDelegate>();

responseMock.Setup(r => r.StatusCode).Returns((int)statusCode);
requestMock.Setup(h => h.ContentType).Returns(contentType);

httpContextMock.Setup(h => h.Request).Returns(requestMock.Object);
httpContextMock.Setup(h => h.Response).Returns(responseMock.Object);
responseMock.Setup(res => res.Body).Returns(responseBody);

return new FileUploadMiddlewareTest(requestDelegateMock.Object);
}
}
25 changes: 25 additions & 0 deletions Frontend/CO.CDP.OrganisationApp/FileUploadMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using CO.CDP.OrganisationApp.Pages.Forms;
using System.Net;

namespace CO.CDP.OrganisationApp;

public class FileUploadMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context)
{
await next(context);

string contentType = context.Request.ContentType ?? string.Empty;
bool isMultipartFormData = contentType.Contains("multipart/form-data;");

if ((context.Response.StatusCode == (int)HttpStatusCode.BadRequest) && (isMultipartFormData))
{
await WriteAsync(context);
}
}

protected virtual async Task WriteAsync(HttpContext context)
{
await context.Response.WriteAsync($"The uploaded file is too large. Maximum allowed size is {FormElementFileUploadModel.AllowedMaxFileSizeMB} MB.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace CO.CDP.OrganisationApp.Pages.Forms;

public class FormElementFileUploadModel : FormElementModel, IValidatableObject
{
private const int AllowedMaxFileSizeMB = 25;
public const int AllowedMaxFileSizeMB = 25;
private readonly string[] AllowedExtensions = [".jpg", ".jpeg", ".png", ".pdf", ".txt", ".xls", ".xlsx", ".csv", ".docx", ".doc"];

[BindProperty]
Expand Down
21 changes: 15 additions & 6 deletions Frontend/CO.CDP.OrganisationApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
using Amazon.SQS;
using CO.CDP.AwsServices;
using CO.CDP.AwsServices.Sqs;
using CO.CDP.Configuration.ForwardedHeaders;
using CO.CDP.DataSharing.WebApiClient;
using CO.CDP.EntityVerificationClient;
using CO.CDP.Forms.WebApiClient;
using CO.CDP.Localization;
using CO.CDP.MQ;
using CO.CDP.Organisation.WebApiClient;
using CO.CDP.OrganisationApp;
using CO.CDP.OrganisationApp.Authentication;
using CO.CDP.OrganisationApp.Authorization;
using CO.CDP.OrganisationApp.Pages;
using CO.CDP.OrganisationApp.Pages.Forms;
using CO.CDP.OrganisationApp.Pages.Forms.ChoiceProviderStrategies;
using CO.CDP.OrganisationApp.ThirdPartyApiClients.CompaniesHouse;
using CO.CDP.OrganisationApp.ThirdPartyApiClients.CharityCommission;
using CO.CDP.OrganisationApp.ThirdPartyApiClients.CompaniesHouse;
using CO.CDP.OrganisationApp.WebApiClients;
using CO.CDP.Person.WebApiClient;
using CO.CDP.Tenant.WebApiClient;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Options;
using Microsoft.FeatureManagement;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Globalization;
using static IdentityModel.OidcConstants;
using static System.Net.Mime.MediaTypeNames;
using ISession = CO.CDP.OrganisationApp.ISession;
using Microsoft.FeatureManagement;
using Amazon.SQS;
using CO.CDP.AwsServices.Sqs;
using CO.CDP.MQ;
using Microsoft.Extensions.Options;

const string FormsHttpClientName = "FormsHttpClient";
const string TenantHttpClientName = "TenantHttpClient";
Expand Down Expand Up @@ -61,6 +63,11 @@
};
});

builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = (FormElementFileUploadModel.AllowedMaxFileSizeMB * 2) * 1024 * 1024;
});

builder.Services.AddFeatureManagement(builder.Configuration.GetSection("Features"));

var mvcBuilder = builder.Services.AddRazorPages()
Expand Down Expand Up @@ -308,6 +315,8 @@
app.UseAuthentication();
app.UseSession();
app.UseMiddleware<AuthenticatedSessionAwareMiddleware>();
app.UseMiddleware<FileUploadMiddleware>();

app.UseAuthorization();
app.MapRazorPages();

Expand Down

0 comments on commit 6779fe3

Please sign in to comment.