3

I try to set the content type of a multipart/form-data GET request that miss boundary. I've tried this code:

public class BoundaryMiddleware
{
    private readonly RequestDelegate _next;

    public BoundaryMiddleware(RequestDelegate next)
    {
        this._next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        if (context.Request.Path.Value.Contains("api/webhook"))
        {  
            if (context.Request.ContentType == "multipart/form-data")
            { 
                context.Request.ContentType = $"multipart/form-data; boundary=\"{Guid.NewGuid()}\""; 
            }
        }

        await _next.Invoke(context);
    }
}

And the startup file:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env,
        ILoggerFactory loggerFactory,
        IConfiguration configuration)
    {
        loggerFactory.AddDebug();
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            loggerFactory.AddDebug();
        }

        app.UseMiddleware<BoundaryMiddleware>();

        app.UseMvc();
    }
}

However, this code throw the following Exception:

 System.IO.IOException: Unexpected end of Stream, the content may have already been read by another component. 
     at Microsoft.AspNetCore.WebUtilities.MultipartReaderStream.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
     at Microsoft.AspNetCore.WebUtilities.StreamHelperExtensions.DrainAsync(Stream stream, ArrayPool`1 bytePool, Nullable`1 limit, CancellationToken cancellationToken)
     at Microsoft.AspNetCore.WebUtilities.MultipartReader.ReadNextSectionAsync(CancellationToken cancellationToken)
     at Microsoft.AspNetCore.Http.Features.FormFeature.InnerReadFormAsync(CancellationToken cancellationToken)
     at Microsoft.AspNetCore.Mvc.ModelBinding.FormValueProviderFactory.AddValueProviderAsync(ValueProviderFactoryContext context)
     at Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider.CreateAsync(ActionContext actionContext, IList`1 factories)
     at Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider.CreateAsync(ControllerContext controllerContext)
     at Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()

Is there a way to modify this ? Thanks

Edit: this is the resquest passed to the web app:

GET /api/webhook HTTP/1.1
Host: localhost:5000
content-type: multipart/form-data
cache-control: no-cache

Edit 2: If I send the request as it, this exception is raised:

System.IO.InvalidDataException: Missing content-type boundary.
   at Microsoft.AspNetCore.Http.Features.FormFeature.GetBoundary(MediaTypeHeaderValue contentType, Int32 lengthLimit)
   at Microsoft.AspNetCore.Http.Features.FormFeature.InnerReadFormAsync(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.ModelBinding.FormValueProviderFactory.AddValueProviderAsync(ValueProviderFactoryContext context)
   at Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider.CreateAsync(ActionContext actionContext, IList`1 factories)
   at Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider.CreateAsync(ControllerContext controllerContext)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
frank_lbt
  • 317
  • 3
  • 17
  • Your question is difficult to answer without the call to the service, from the error it looks like the stream has been read already, you need to set the stream to the beginning before trying to read it. – Rav Jun 12 '19 at 09:27
  • why do you need to inject that Id? I mean you can do it in your controller action, let the routing do his thing – SilentTremor Jun 12 '19 at 09:39
  • 2
    You can’t just add a random boundary identifier. That identifier has to be used throughout the request body in order for this to work. – Why don’t you attempt to fix your client instead if it is producing an incorrect request? – poke Jun 12 '19 at 09:39
  • I'm not the owner of the code that call my api, and the request is a get request so the body does not contains anything. – frank_lbt Jun 12 '19 at 09:40
  • 2
    content multipart/form-data as a get? Am I missing something? – SilentTremor Jun 12 '19 at 09:43
  • I know this is strange, but the caller has implemented his api in this way. – frank_lbt Jun 12 '19 at 09:44
  • But the question is why do you even try to add multipart boundary on your content type header, they are not sending stuff to you, right? – SilentTremor Jun 12 '19 at 10:03
  • Yes , but without boundary it generate another exception: `System.IO.InvalidDataException: Missing content-type boundary.` – frank_lbt Jun 12 '19 at 10:12
  • You could try to remove the header instead maybe. Pretty sure it is meaningless on a GET request – ste-fu Jun 12 '19 at 10:16

2 Answers2

1

.Net HTTP headers are generally a bit funny and the framework will make various assumptions when attempting to convert the raw http request into an HttpRequest and then further processing takes place when invoking an action on a Controller.

Given that there is no body, it makes little sense to have a Content-Type header, or a boundary value. You might find it more effective to remove the header altogether.

public async Task Invoke(HttpContext context)
{
    if (context.Request.Path.Value.Contains("api/webhook"))
    {  
        if (context.Request.ContentType == "multipart/form-data")
            context.Request.Headers.Remove("Content-Type");            
    }

    await _next.Invoke(context);
}
ste-fu
  • 6,003
  • 3
  • 26
  • 44
0

For HTTP GET Content-Type HTTP header should't matter at all, Content-Type HTTP header is required to be set on requests with a body (PUT and POST usually).

Indeed get can be with a body, see here if your API need to support that good luck, yet is not impossible!

Otherwise, if your API consumers don't send a body, just do this:

[AllowAnonymous]
[HttpGet]
[Route("api/webhook")]
public IActionResult GetWebhook()
{
     // rock and roll 
     return Ok();
}

With the CURL test (use your local)

curl -X GET "https://localhost:44395/api/webhook" -H "content-type: multipart/form-data" --verbose

No need for that request interceptor.

SilentTremor
  • 4,279
  • 2
  • 14
  • 26