From 9d282d613240a31c77c938bef902038ae80563e4 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Tue, 16 Jan 2024 13:34:13 +0100 Subject: [PATCH] Added Better Errors for MultiPart requests --- .../AspNetCore/src/AspNetCore/ErrorHelper.cs | 22 ++++++++++++------- .../src/AspNetCore/HttpMultipartMiddleware.cs | 15 ++++++++++--- .../AspNetCoreResources.Designer.cs | 6 +++++ .../Properties/AspNetCoreResources.resx | 3 +++ ...reTests.Fail_Without_Preflight_Header.snap | 13 ++++++++--- .../Core/src/Abstractions/ErrorCodes.cs | 5 +++++ 6 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ErrorHelper.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/ErrorHelper.cs index 66fac473744..e292148c4df 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/ErrorHelper.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/ErrorHelper.cs @@ -1,4 +1,4 @@ -using HotChocolate.AspNetCore.Properties; +using static HotChocolate.AspNetCore.Properties.AspNetCoreResources; namespace HotChocolate.AspNetCore; @@ -9,32 +9,32 @@ internal static class ErrorHelper { public static IError InvalidRequest() => ErrorBuilder.New() - .SetMessage(AspNetCoreResources.ErrorHelper_InvalidRequest) + .SetMessage(ErrorHelper_InvalidRequest) .SetCode(ErrorCodes.Server.RequestInvalid) .Build(); public static IError RequestHasNoElements() => ErrorBuilder.New() - .SetMessage(AspNetCoreResources.ErrorHelper_RequestHasNoElements) + .SetMessage(ErrorHelper_RequestHasNoElements) .SetCode(ErrorCodes.Server.RequestInvalid) .Build(); public static IError NoSupportedAcceptMediaType() => ErrorBuilder.New() - .SetMessage(AspNetCoreResources.ErrorHelper_NoSupportedAcceptMediaType) + .SetMessage(ErrorHelper_NoSupportedAcceptMediaType) .SetCode(ErrorCodes.Server.NoSupportedAcceptMediaType) .Build(); public static IQueryResult TypeNameIsEmpty() => QueryResultBuilder.CreateError( new Error( - AspNetCoreResources.ErrorHelper_TypeNameIsEmpty, + ErrorHelper_TypeNameIsEmpty, code: ErrorCodes.Server.TypeParameterIsEmpty)); public static IQueryResult InvalidTypeName(string typeName) => QueryResultBuilder.CreateError( new Error( - AspNetCoreResources.ErrorHelper_InvalidTypeName, + ErrorHelper_InvalidTypeName, code: ErrorCodes.Server.InvalidTypeName, extensions: new Dictionary { @@ -44,7 +44,7 @@ public static IQueryResult InvalidTypeName(string typeName) public static IQueryResult TypeNotFound(string typeName) => QueryResultBuilder.CreateError( new Error( - string.Format(AspNetCoreResources.ErrorHelper_TypeNotFound, typeName), + string.Format(ErrorHelper_TypeNotFound, typeName), code: ErrorCodes.Server.TypeDoesNotExist, extensions: new Dictionary { @@ -54,10 +54,16 @@ public static IQueryResult TypeNotFound(string typeName) public static IQueryResult InvalidAcceptMediaType(string headerValue) => QueryResultBuilder.CreateError( new Error( - string.Format(AspNetCoreResources.ErrorHelper_InvalidAcceptMediaType, headerValue), + string.Format(ErrorHelper_InvalidAcceptMediaType, headerValue), code: ErrorCodes.Server.InvalidAcceptHeaderValue, extensions: new Dictionary { { nameof(headerValue), headerValue } })); + + public static IQueryResult MultiPartRequestPreflightRequired() + => QueryResultBuilder.CreateError( + new Error( + ErrorHelper_MultiPartRequestPreflightRequired, + code: ErrorCodes.Server.MultiPartPreflightRequired)); } diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpMultipartMiddleware.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpMultipartMiddleware.cs index 98414dfaeb1..20d044b2dab 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/HttpMultipartMiddleware.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/HttpMultipartMiddleware.cs @@ -6,6 +6,8 @@ using HotChocolate.AspNetCore.Serialization; using HotChocolate.Language; using HotChocolate.Utilities; +using static System.Net.HttpStatusCode; +using static HotChocolate.AspNetCore.ErrorHelper; using static HotChocolate.AspNetCore.Properties.AspNetCoreResources; using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate; @@ -16,6 +18,7 @@ public sealed class HttpMultipartMiddleware : HttpPostMiddlewareBase private const string _operations = "operations"; private const string _map = "map"; private readonly FormOptions _formOptions; + private readonly IQueryResult _multipartRequestError = MultiPartRequestPreflightRequired(); public HttpMultipartMiddleware( HttpRequestDelegate next, @@ -40,10 +43,16 @@ public override async Task InvokeAsync(HttpContext context) { if (HttpMethods.IsPost(context.Request.Method) && GetOptions(context).EnableMultipartRequests && - ParseContentType(context) == RequestContentType.Form && - (context.Request.Headers.ContainsKey(HttpHeaderKeys.Preflight) || - !GetOptions(context).EnforceMultipartRequestsPreflightHeader)) + ParseContentType(context) == RequestContentType.Form) { + if (!context.Request.Headers.ContainsKey(HttpHeaderKeys.Preflight) && + GetOptions(context).EnforceMultipartRequestsPreflightHeader) + { + var headerResult = HeaderUtilities.GetAcceptHeader(context.Request); + await WriteResultAsync(context, _multipartRequestError, headerResult.AcceptMediaTypes, BadRequest); + return; + } + if (!IsDefaultSchema) { context.Items[WellKnownContextData.SchemaName] = SchemaName; diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.Designer.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.Designer.cs index 4bb7ca59886..7a324567854 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.Designer.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.Designer.cs @@ -284,5 +284,11 @@ internal static string ErrorHelper_TypeNameIsEmpty { return ResourceManager.GetString("ErrorHelper_TypeNameIsEmpty", resourceCulture); } } + + internal static string ErrorHelper_MultiPartRequestPreflightRequired { + get { + return ResourceManager.GetString("ErrorHelper_MultiPartRequestPreflightRequired", resourceCulture); + } + } } } diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.resx b/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.resx index bce235bb668..dcc1e936264 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.resx +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.resx @@ -138,4 +138,7 @@ The specified types argument is empty. + + Multi-part requests must include a GraphQL preflight header. + diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpMultipartMiddlewareTests.Fail_Without_Preflight_Header.snap b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpMultipartMiddlewareTests.Fail_Without_Preflight_Header.snap index 4c83bfdd6eb..afe0f41ef57 100644 --- a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpMultipartMiddlewareTests.Fail_Without_Preflight_Header.snap +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/__snapshots__/HttpMultipartMiddlewareTests.Fail_Without_Preflight_Header.snap @@ -1,7 +1,14 @@ { - "ContentType": null, - "StatusCode": "NotFound", + "ContentType": "application/graphql-response+json; charset=utf-8", + "StatusCode": "BadRequest", "Data": null, - "Errors": null, + "Errors": [ + { + "message": "Multi-part requests must include a GraphQL preflight header.", + "extensions": { + "code": "HC0077" + } + } + ], "Extensions": null } \ No newline at end of file diff --git a/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs b/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs index d8e046b1f4c..aa71de9ad74 100644 --- a/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs +++ b/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs @@ -192,6 +192,11 @@ public static class Server /// The request did not specify any supported accept media type. /// public const string InvalidAcceptHeaderValue = "HC0064"; + + /// + /// Multi-part requests must include a GraphQL preflight header. + /// + public const string MultiPartPreflightRequired = "HC0077"; } public static class Schema