|
// 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);
}
[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);
}
}
|