From dd1f3e2f60810af9380e3ab7ca2702c2a4d8b3b1 Mon Sep 17 00:00:00 2001 From: George Andguladze Date: Sat, 22 Aug 2026 23:54:43 +0400 Subject: [PATCH] Fix rewrite map lookups nested in a pattern RewriteMapSegment evaluated its key pattern with the shared RewriteContext StringBuilder, so a rewrite map used inside another pattern (for example {id-map:{C:1}} in an action URL) corrupted the builder the surrounding pattern was still using. The key evaluation appended to and then cleared that shared builder, so the map lookup missed and the rest of the URL was lost. Give the key evaluation its own StringBuilder and restore the shared one afterward, matching what ToLowerSegment, UrlDecodeSegment, and UrlEncodeSegment already do. Fixes #24618 --- .../src/PatternSegments/RewriteMapSegment.cs | 6 +++ .../test/IISUrlRewrite/MiddleWareTests.cs | 45 +++++++++++++++++++ 2 files changed, 51 insertions(+) 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() {