diff --git a/src/Middleware/Rewrite/src/PatternSegments/RewriteMapSegment.cs b/src/Middleware/Rewrite/src/PatternSegments/RewriteMapSegment.cs index b98be41adbdb..1fb9df0e1efe 100644 --- a/src/Middleware/Rewrite/src/PatternSegments/RewriteMapSegment.cs +++ b/src/Middleware/Rewrite/src/PatternSegments/RewriteMapSegment.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text; using Microsoft.AspNetCore.Rewrite.IISUrlRewrite; namespace Microsoft.AspNetCore.Rewrite.PatternSegments; @@ -18,7 +19,12 @@ public RewriteMapSegment(IISRewriteMap rewriteMap, Pattern pattern) public override string? Evaluate(RewriteContext context, BackReferenceCollection? ruleBackReferences, BackReferenceCollection? conditionBackReferences) { + // PERF as we share the string builder across the context, we need to make a new one here to + // evaluate the rewrite map key, which may itself contain nested pattern segments. + var tempBuilder = context.Builder; + context.Builder = new StringBuilder(64); var key = _pattern.Evaluate(context, ruleBackReferences, conditionBackReferences).ToLowerInvariant(); + context.Builder = tempBuilder; return _rewriteMap[key]; } } diff --git a/src/Middleware/Rewrite/test/IISUrlRewrite/MiddleWareTests.cs b/src/Middleware/Rewrite/test/IISUrlRewrite/MiddleWareTests.cs index 6c83b25b860c..ad572ab854a9 100644 --- a/src/Middleware/Rewrite/test/IISUrlRewrite/MiddleWareTests.cs +++ b/src/Middleware/Rewrite/test/IISUrlRewrite/MiddleWareTests.cs @@ -150,6 +150,51 @@ public async Task Invoke_RewriteBasedOnQueryStringParameters() Assert.Equal("/newpage.aspx?param1=123¶m2=321", response); } + [Fact] + // Regression test for https://github.com/dotnet/aspnetcore/issues/24618. A rewrite map lookup + // nested inside an action URL (for example {id-map:{C:1}}) must evaluate its key with its own + // string builder. Otherwise the key evaluation appends to and clears the shared RewriteContext + // builder that the surrounding pattern is still using, so the map lookup misses and the rest of + // the action URL is lost. + public async Task Invoke_RewriteMapNestedInActionUrlEvaluatesKey() + { + var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" + + + + + + + + + + + + + + + ")); + using var host = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseTestServer() + .Configure(app => + { + app.UseRewriter(options); + app.Run(context => context.Response.WriteAsync(context.Request.Path + context.Request.QueryString)); + }); + }).Build(); + + await host.StartAsync(); + + var server = host.GetTestServer(); + + var response = await server.CreateClient().GetStringAsync("page.asp?id=1234-1234"); + + Assert.Equal("/newpage.aspx?id=abcd-abcd", response); + } + [Fact] public async Task Invoke_RedirectToLowerCase() {