// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Microsoft.AspNetCore.Rewrite.Internal.UrlActions; using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches; using Microsoft.AspNetCore.TestHost; using Microsoft.Net.Http.Headers; using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite { public class MiddlewareTests { [Fact] public async Task Invoke_RedirectPathToPathAndQuery() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Response.Headers[HeaderNames.Location])); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Response.Headers[HeaderNames.Location])); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Scheme + "://" + context.Request.Host + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Scheme + "://" + context.Request.Host + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Scheme + "://" + context.Request.Host + context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync( context.Request.Path + context.Request.QueryString)); }); var server = new TestServer(builder) { 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($@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("article/23?p1=123&p2=abc"); Assert.Equal("/blogposts/article/abc", response.Headers.Location.OriginalString); } [Fact] public async Task VerifyTrackAllCapturesRuleAndConditionCapture() { var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var response = await server.CreateClient().GetAsync("article/23?p1=123&p2=abc"); Assert.Equal("/blog/article/23/abc", response.Headers.Location.OriginalString); } [Fact] public async Task ThrowIndexOutOfRangeExceptionWithCorrectMessage() { // Arrange, Act, Assert var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); var ex = await Assert.ThrowsAsync(() => 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 = @" "; var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(xml)); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); }); var server = new TestServer(builder); // 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); }); var server = new TestServer(builder); 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(@" ")); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); }); var server = new TestServer(builder); 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", UriMatchPart.Path)] [InlineData(@"^http://localhost(/.*)", "http://www.test.com/foo/bar", UriMatchPart.Full)] public async Task Invoke_GlobalRuleConditionMatchesAgainstFullUri_CodedRule(string conditionInputPattern, string expectedResult, UriMatchPart 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, 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), queryStringAppend: false); ruleBuilder.AddUrlAction(action); var options = new RewriteOptions().Add(ruleBuilder.Build()); var builder = new WebHostBuilder() .Configure(app => { app.UseRewriter(options); app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); }); var server = new TestServer(builder); // act var response = await server.CreateClient().GetStringAsync("http://localhost/foo/bar"); // assert Assert.Equal(expectedResult, response); } } }