Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding RestoreRawRequestPath middlware to restore the raw request path value to current request #9402

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
- Microsoft.IdentityModel.JsonWebTokens
- Microsoft.IdentityModel.Logging
- Updated Grpc.AspNetCore package to 2.55.0 (https://github.com/Azure/azure-functions-host/pull/9373)
- Added RestoreRawRequestPath middleware to restore the raw un-decoded request path value to the current request (https://github.com/Azure/azure-functions-host/pull/9402)
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace Microsoft.Azure.WebJobs.Script.WebHost.Middleware
{
/// <summary>
/// Middleware to restore the raw request URL path received, in the current request URL.
/// App service front end decodes encoded strings in the request URL path.
/// This causes routing to fail if the route param value has %2F in it.
/// Refer below issues for more context:
/// https://github.com/dotnet/aspnetcore/issues/40532#issuecomment-1083562919
surgupta-msft marked this conversation as resolved.
Show resolved Hide resolved
/// https://github.com/Azure/azure-functions-host/issues/9290.
/// </summary>
internal class RestoreRawRequestPathMiddleware
{
private readonly RequestDelegate _next;
internal const string UnEncodedUrlPathHeaderName = "X-Waws-Unencoded-Url";

public RestoreRawRequestPathMiddleware(RequestDelegate next)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
}

public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.TryGetValue(UnEncodedUrlPathHeaderName, out var unencodedUrlValue) &&
unencodedUrlValue.Count > 0)
surgupta-msft marked this conversation as resolved.
Show resolved Hide resolved
{
context.Request.Path = new PathString(unencodedUrlValue.First());
}

await _next(context);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ public static IApplicationBuilder UseWebJobsScriptHost(this IApplicationBuilder

// Ensure the ClrOptimizationMiddleware is registered before all middleware
builder.UseMiddleware<ClrOptimizationMiddleware>();

// Update the request URL path(which was altered by App service FE) to raw request path
liliankasem marked this conversation as resolved.
Show resolved Hide resolved
// only if customer has opted in using the app setting.
builder.UseWhen(
_ => string.Equals(
environment.GetEnvironmentVariable(EnvironmentSettingNames.RestoreRawRequestPathEnabled), "1",
StringComparison.Ordinal), config =>
{
config.UseMiddleware<RestoreRawRequestPathMiddleware>();
// We need to re-add routing middleware after any updates to request path.
config.UseRouting();
config.UseEndpoints(endpoints => endpoints.MapControllers());
});

builder.UseMiddleware<HttpRequestBodySizeMiddleware>();
builder.UseMiddleware<SystemTraceMiddleware>();
builder.UseMiddleware<HostnameFixupMiddleware>();
Expand Down
3 changes: 3 additions & 0 deletions src/WebJobs.Script/Environment/EnvironmentSettingNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,8 @@ public static class EnvironmentSettingNames
public const string AppKind = "APP_KIND";

public const string DrainOnApplicationStopping = "FUNCTIONS_ENABLE_DRAIN_ON_APP_STOPPING";

// Restore the raw request path from wire to the current request.
public const string RestoreRawRequestPathEnabled = "WEBSITE_RESTORE_RAW_REQUEST_PATH";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Script.WebHost.Middleware;
using Moq;
using Xunit;

public class RestoreRawRequestPathMiddlewareTests
{
[Fact]
public async Task Invoke_WithUnencodedHeaderValue_SetsRequestPath()
{
// Arrange
const string unEncodedHeaderValue = "/cloud%2Fdevdiv";
const string requestPathValue = "/cloud/devdiv";
var context = CreateHttpContextWithHeader(requestPathValue, unEncodedHeaderValue);
var mockNext = new Mock<RequestDelegate>();
var middleware = new RestoreRawRequestPathMiddleware(mockNext.Object);
var originalPath = context.Request.Path;

// Act
await middleware.Invoke(context);

// Assert
Assert.NotEqual(new PathString(originalPath), context.Request.Path);
Assert.Equal(new PathString(unEncodedHeaderValue), context.Request.Path);
mockNext.Verify(next => next(context), Times.Once());
}

[Fact]
public async Task Invoke_WithoutUnencodedUrlHeader_DoesNotChangeRequestPath()
{
// Arrange
const string requestPathValue = "/cloud/devdiv";
var context = CreateHttpContextWithHeader(requestPathValue);
var mockNext = new Mock<RequestDelegate>();
var middleware = new RestoreRawRequestPathMiddleware(mockNext.Object);
var originalPath = context.Request.Path;

// Act
await middleware.Invoke(context);

// Assert
Assert.Equal(originalPath, context.Request.Path);
mockNext.Verify(next => next(context), Times.Once());
}

private static HttpContext CreateHttpContextWithHeader(string requestPathValue, string unEncodedHeaderValue = null)
{
var context = new DefaultHttpContext
{
Request =
{
Path = requestPathValue
}
};

if (unEncodedHeaderValue is not null)
{
context.Request.Headers[RestoreRawRequestPathMiddleware.UnEncodedUrlPathHeaderName] = unEncodedHeaderValue;
}

return context;
}
}
Loading