/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Middleware/Rewrite/test/IISUrlRewrite/MiddleWareTests.cs
789 строк
28 KB
Roman Konecny
Collapse scheme-relative leading slashes in Rewrite middleware redirect/rewrite targets (#66961)
24 июн 2026, 12:20
Не верифицирован
24 июн 2026, 12:20
0563bff
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Net; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Rewrite.IISUrlRewrite; using Microsoft.AspNetCore.Rewrite.UrlActions; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Hosting; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite; public class MiddlewareTests { [Fact] public async Task Invoke_RedirectPathToPathAndQuery() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Rewrite to article.aspx""> <match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" /> <action type=""Redirect"" url =""article.aspx?id={R:1}&title={R:2}"" /> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Response.Headers.Location)); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetAsync("article/10/hey"); Assert.Equal("/article.aspx?id=10&title=hey", response.Headers.Location.OriginalString); } [Theory] [InlineData("legacy//example.com")] [InlineData("legacy///example.com")] [InlineData(@"legacy/\example.com")] public async Task Invoke_RedirectCollapsesSchemeRelativeBackReference(string requestPath) { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Collapse""> <match url=""^legacy/(.*)"" /> <action type=""Redirect"" url=""/{R:1}"" redirectType=""Found"" /> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Response.Headers.Location)); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetAsync(requestPath); Assert.Equal("/example.com", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_RewritePathToPathAndQuery() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Rewrite to article.aspx""> <match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" /> <action type=""Rewrite"" url =""article.aspx?id={R:1}&title={R:2}"" /> </rule> </rules> </rewrite>")); 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("/article/10/hey"); Assert.Equal("/article.aspx?id=10&title=hey", response); } [Fact] public async Task Invoke_RewriteBasedOnQueryStringParameters() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Query String Rewrite""> <match url=""page\.asp$"" /> <conditions> <add input=""{QUERY_STRING}"" pattern=""p1=(\d+)"" /> <add input=""##{C:1}##_{QUERY_STRING}"" pattern=""##([^#]+)##_.*p2=(\d+)"" /> </conditions> <action type=""Rewrite"" url=""newpage.aspx?param1={C:1}&param2={C:2}"" appendQueryString=""false""/> </rule> </rules> </rewrite>")); 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?p2=321&p1=123"); Assert.Equal("/newpage.aspx?param1=123¶m2=321", response); } [Fact] public async Task Invoke_RedirectToLowerCase() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Convert to lower case"" stopProcessing=""true""> <match url="".*[A-Z].*"" ignoreCase=""false"" /> <action type=""Redirect"" url=""{ToLower:{R:0}}"" redirectType=""Permanent"" /> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Response.Headers.Location)); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetAsync("HElLo"); Assert.Equal("/hello", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_RedirectRemoveTrailingSlash() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Remove trailing slash"" stopProcessing=""true""> <match url=""(.*)/$"" /> <conditions> <add input=""{REQUEST_FILENAME}"" matchType=""IsFile"" negate=""true"" /> <add input=""{REQUEST_FILENAME}"" matchType=""IsDirectory"" negate=""true"" /> </conditions> <action type=""Redirect"" redirectType=""Permanent"" url=""{R:1}"" /> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetAsync("hey/hello/"); Assert.Equal("/hey/hello", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_RedirectAddTrailingSlash() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Add trailing slash"" stopProcessing=""true""> <match url=""(.*[^/])$"" /> <conditions> <add input=""{REQUEST_FILENAME}"" matchType=""IsFile"" negate=""true"" /> <add input=""{REQUEST_FILENAME}"" matchType=""IsDirectory"" negate=""true"" /> </conditions> <action type=""Redirect"" redirectType=""Permanent"" url=""{R:1}/"" /> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetAsync("hey/hello"); Assert.Equal("/hey/hello/", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_RedirectToHttps() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Redirect to HTTPS"" stopProcessing=""true""> <match url=""(.*)"" /> <conditions> <add input=""{HTTPS}"" pattern=""^OFF$"" /> </conditions> <action type=""Redirect"" url=""https://{HTTP_HOST}/{R:1}"" redirectType=""Permanent"" /> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetAsync(new Uri("http://example.com")); Assert.Equal("https://example.com/", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_RewriteToHttps() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Rewrite to HTTPS"" stopProcessing=""true""> <match url=""(.*)"" /> <conditions> <add input=""{HTTPS}"" pattern=""^OFF$"" /> </conditions> <action type=""Rewrite"" url=""https://{HTTP_HOST}/{R:1}"" /> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Scheme + "://" + context.Request.Host + context.Request.Path + context.Request.QueryString)); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetStringAsync(new Uri("http://example.com")); Assert.Equal("https://example.com/", response); } [Fact] public async Task Invoke_ReverseProxyToAnotherSite() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Proxy""> <match url=""(.*)"" /> <action type=""Rewrite"" url=""http://internalserver/{R:1}"" /> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Scheme + "://" + context.Request.Host + context.Request.Path + context.Request.QueryString)); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetStringAsync(new Uri("http://example.com/")); Assert.Equal("http://internalserver/", response); } [Fact] public async Task Invoke_CaptureEmptyStringInRegexAssertRedirectLocationHasForwardSlash() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Test""> <match url=""(.*)"" /> <action type=""Redirect"" url=""{R:1}"" /> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Scheme + "://" + context.Request.Host + context.Request.Path + context.Request.QueryString)); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetAsync(new Uri("http://example.com/")); Assert.Equal("/", response.Headers.Location.OriginalString); } [Fact] public async Task Invoke_CaptureEmptyStringInRegexAssertRewriteLocationHasForwardSlash() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Test""> <match url=""(.*)"" /> <action type=""Rewrite"" url=""{R:1}"" /> </rule> </rules> </rewrite>")); 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(new Uri("http://example.com/")); Assert.Equal("/", response); } [Fact] public async Task Invoke_CaptureEmptyStringInRegexAssertLocationHeaderContainsPathBase() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Test""> <match url=""(.*)"" /> <action type=""Redirect"" url=""{R:1}"" /> </rule> </rules> </rewrite>")); 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(); server.BaseAddress = new Uri("http://localhost:5000/foo"); var response = await server.CreateClient().GetAsync(""); Assert.Equal("/foo", response.Headers.Location.OriginalString); } [Theory] [InlineData("IsFile")] [InlineData("isfile")] [InlineData("IsDirectory")] [InlineData("isdirectory")] public async Task VerifyIsFileAndIsDirectoryParsing(string matchType) { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader($@"<rewrite> <rules> <rule name=""Test""> <match url=""(.*[^/])$"" /> <conditions> <add input=""{{REQUEST_FILENAME}}"" matchType=""{matchType}"" negate=""true""/> </conditions> <action type=""Redirect"" url=""{{R:1}}/"" /> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetAsync("hey/hello"); Assert.Equal("/hey/hello/", response.Headers.Location.OriginalString); } [Fact] public async Task VerifyTrackAllCaptures() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Test""> <match url=""(.*)"" ignoreCase=""false"" /> <conditions trackAllCaptures = ""true"" > <add input=""{REQUEST_URI}"" pattern=""^/([a-zA-Z]+)/([0-9]+)$"" /> <add input=""{QUERY_STRING}"" pattern=""p2=([a-z]+)"" /> </conditions> <action type=""Redirect"" url =""blogposts/{C:1}/{C:4}"" /> <!--rewrite action uses back - references to both conditions --> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetAsync("article/23?p1=123&p2=abc"); Assert.Equal("/blogposts/article/abc?p1=123&p2=abc", response.Headers.Location.OriginalString); } [Fact] public async Task VerifyTrackAllCapturesRuleAndConditionCapture() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Test""> <match url=""(.*)"" ignoreCase=""false"" /> <conditions trackAllCaptures = ""true"" > <add input=""{REQUEST_URI}"" pattern=""^/([a-zA-Z]+)/([0-9]+)$"" /> <add input=""{QUERY_STRING}"" pattern=""p2=([a-z]+)"" /> </conditions> <action type=""Redirect"" url =""blog/{R:0}/{C:4}"" /> <!--rewrite action uses back - references to both conditions --> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetAsync("article/23?p1=123&p2=abc"); Assert.Equal("/blog/article/23/abc?p1=123&p2=abc", response.Headers.Location.OriginalString); } [Fact] public async Task ThrowArgumentOutOfRangeExceptionWithCorrectMessage() { // Arrange, Act, Assert var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Test""> <match url=""(.*)"" ignoreCase=""false"" /> <conditions trackAllCaptures = ""true"" > <add input=""{REQUEST_URI}"" pattern=""^/([a-zA-Z]+)/([0-9]+)$"" /> <add input=""{QUERY_STRING}"" pattern=""p2=([a-z]+)"" /> </conditions> <action type=""Redirect"" url =""blog/{R:0}/{C:9}"" /> <!--rewrite action uses back - references to both conditions --> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var ex = await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => server.CreateClient().GetAsync("article/23?p1=123&p2=abc")); Assert.Equal("Cannot access back reference at index 9. Only 5 back references were captured.", ex.Message); } [Fact] public async Task Invoke_GlobalRuleConditionMatchesAgainstFullUri_ParsedRule() { // arrange var xml = @"<rewrite> <globalRules> <rule name=""Test"" patternSyntax=""ECMAScript"" stopProcessing=""true""> <match url="".*"" /> <conditions logicalGrouping=""MatchAll"" trackAllCaptures=""false""> <add input=""{REQUEST_URI}"" pattern=""^http://localhost/([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})(/.*)"" /> </conditions> <action type=""Rewrite"" url=""http://www.test.com{C:2}"" /> </rule> </globalRules> </rewrite>"; var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(xml)); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); // act var response = await server.CreateClient().GetStringAsync($"http://localhost/{Guid.NewGuid()}/foo/bar"); // assert Assert.Equal("http://www.test.com/foo/bar", response); } [Theory] [InlineData("http://fetch.environment.local/dev/path", "http://1.1.1.1/path")] [InlineData("http://fetch.environment.local/qa/path", "http://fetch.environment.local/qa/path")] public async Task Invoke_ReverseProxyToAnotherSiteUsingXmlConfiguredRewriteMap(string requestUri, string expectedRewrittenUri) { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" <rewrite> <rules> <rule name=""Proxy""> <match url=""([^/]*)(/?.*)"" /> <conditions> <add input=""{environmentMap:{R:1}}"" pattern=""(.+)"" /> </conditions> <action type=""Rewrite"" url=""http://{C:1}{R:2}"" appendQueryString=""true"" /> </rule> </rules> <rewriteMaps> <rewriteMap name=""environmentMap""> <add key=""dev"" value=""1.1.1.1"" /> </rewriteMap> </rewriteMaps> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetStringAsync(new Uri(requestUri)); Assert.Equal(expectedRewrittenUri, response); } [Fact] public async Task Invoke_CustomResponse() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@"<rewrite> <rules> <rule name=""Forbidden""> <match url = "".*"" /> <action type=""CustomResponse"" statusCode=""403"" statusReason=""reason"" statusDescription=""description"" /> </rule> </rules> </rewrite>")); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); var response = await server.CreateClient().GetAsync("article/10/hey"); var content = await response.Content.ReadAsStringAsync(); Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); Assert.Equal("reason", response.ReasonPhrase); Assert.Equal("description", content); } [Theory] [InlineData(@"^http://localhost(/.*)", "http://localhost/foo/bar", (int)UriMatchPart.Path)] [InlineData(@"^http://localhost(/.*)", "http://www.test.com/foo/bar", (int)UriMatchPart.Full)] public async Task Invoke_GlobalRuleConditionMatchesAgainstFullUri_CodedRule(string conditionInputPattern, string expectedResult, int uriMatchPart) { // arrange var inputParser = new InputParser(); var ruleBuilder = new UrlRewriteRuleBuilder { Name = "test", Global = false }; ruleBuilder.AddUrlMatch(".*"); var condition = new UriMatchCondition( inputParser, "{REQUEST_URI}", conditionInputPattern, (UriMatchPart)uriMatchPart, ignoreCase: true, negate: false); ruleBuilder.ConfigureConditionBehavior(LogicalGrouping.MatchAll, trackAllCaptures: true); ruleBuilder.AddUrlCondition(condition); var action = new RewriteAction( RuleResult.SkipRemainingRules, inputParser.ParseInputString(@"http://www.test.com{C:1}", (UriMatchPart)uriMatchPart), queryStringAppend: false); ruleBuilder.AddUrlAction(action); var options = new RewriteOptions().Add(ruleBuilder.Build()); using var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseTestServer() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); }); }).Build(); await host.StartAsync(); var server = host.GetTestServer(); // act var response = await server.CreateClient().GetStringAsync("http://localhost/foo/bar"); // assert Assert.Equal(expectedResult, response); } }