Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion server/PlanShare/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ created_at TEXT NOT NULL
builder.Services.AddSingleton(uploadBudget);
builder.Services.AddHostedService<CleanupService>();

// Request size limit (10 MB)
// Request size limit (10 MB). nginx on the server sets the same limit for /api/
// (client_max_body_size 10m); without it, nginx's 1 MB default refuses larger shares
// first. The web client's too-large message (PlanShareService) also names 10 MB.
// Change all three together.
builder.WebHost.ConfigureKestrel(o => o.Limits.MaxRequestBodySize = 10 * 1024 * 1024);

var app = builder.Build();
Expand Down
6 changes: 5 additions & 1 deletion src/PlanViewer.Web/Services/PlanShareService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ public async Task<PlanShareResult> ShareAsync(AnalysisResult result, string text

/* The server explains a refusal (store full, daily limit, bad request) as {"error": "..."} in
a message meant for the user, so show that text. A reply without it, such as an HTML error
page from the proxy in front of the server, gets the generic message with the status code. */
page from the proxy in front of the server, gets the generic message with the status code.
A 413 is the exception: nginx and Kestrel both refuse an upload over 10 MB without JSON (see
the limit in server/PlanShare/Program.cs), and "server returned 413" explains nothing. */
private static async Task<string> ShareFailureMessageAsync(HttpResponseMessage response)
{
try
Expand All @@ -101,6 +103,8 @@ private static async Task<string> ShareFailureMessageAsync(HttpResponseMessage r
{
// Not JSON, so there is no server text to show
}
if (response.StatusCode == HttpStatusCode.RequestEntityTooLarge)
return "This plan is too large to share. The limit is 10 MB.";
return $"Share failed: server returned {(int)response.StatusCode}";
}

Expand Down
15 changes: 15 additions & 0 deletions tests/PlanViewer.Core.Tests/PlanShareServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public async Task Share_ReturnsTheIdAndDeleteToken()
[InlineData(HttpStatusCode.InsufficientStorage, "Plan sharing is full right now. Please try again later.")]
[InlineData(HttpStatusCode.TooManyRequests, "Daily sharing limit reached for your network. Please try again tomorrow.")]
[InlineData(HttpStatusCode.BadRequest, "The request body must be a JSON object.")]
// Server text wins over the built-in 413 message below
[InlineData(HttpStatusCode.RequestEntityTooLarge, "Plans this large can't be shared.")]
public async Task Share_ShowsTheErrorTextTheServerSent(HttpStatusCode status, string error)
{
var handler = new StubHandler(() => Reply(status, $$"""{"error":"{{error}}"}"""));
Expand All @@ -86,6 +88,19 @@ public async Task Share_FallsBackToTheStatusCode_WhenTheReplyHasNoErrorText(Http
Assert.Equal($"Share failed: server returned {(int)status}", ex.Message);
}

// nginx refuses an oversized upload with its own HTML page, and Kestrel with an empty body
[Theory]
[InlineData("<html><head><title>413 Request Entity Too Large</title></head><body><center><h1>413 Request Entity Too Large</h1></center><hr><center>nginx</center></body></html>", "text/html")]
[InlineData("", "application/json")]
public async Task Share_ExplainsTheSizeLimit_WhenTheUploadIsTooLarge(string body, string contentType)
{
var handler = new StubHandler(() => Reply(HttpStatusCode.RequestEntityTooLarge, body, contentType));

var ex = await Assert.ThrowsAsync<PlanShareException>(() => Share(handler));

Assert.Equal("This plan is too large to share. The limit is 10 MB.", ex.Message);
}

[Fact]
public async Task Delete_SendsTheTokenInAHeader_NotInTheUrl()
{
Expand Down
Loading