From 124064ef47019370eb68da7abc80b6a2c35a2040 Mon Sep 17 00:00:00 2001 From: Bruno Date: Wed, 15 May 2019 03:33:03 +1200 Subject: [PATCH] Add option to limit domains on AddRedirectToWww (#9676) --- ...rosoft.AspNetCore.Rewrite.netcoreapp3.0.cs | 5 ++ .../Rewrite/src/Internal/RedirectToWwwRule.cs | 38 +++++++++ .../Rewrite/src/RewriteOptionsExtensions.cs | 33 ++++++++ .../Rewrite/test/MiddlewareTests.cs | 81 +++++++++++++++++++ 4 files changed, 157 insertions(+) diff --git a/src/Middleware/Rewrite/ref/Microsoft.AspNetCore.Rewrite.netcoreapp3.0.cs b/src/Middleware/Rewrite/ref/Microsoft.AspNetCore.Rewrite.netcoreapp3.0.cs index 180d85f51d..6fb44d5428 100644 --- a/src/Middleware/Rewrite/ref/Microsoft.AspNetCore.Rewrite.netcoreapp3.0.cs +++ b/src/Middleware/Rewrite/ref/Microsoft.AspNetCore.Rewrite.netcoreapp3.0.cs @@ -56,7 +56,10 @@ namespace Microsoft.AspNetCore.Rewrite public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToHttpsPermanent(this Microsoft.AspNetCore.Rewrite.RewriteOptions options) { throw null; } public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToWww(this Microsoft.AspNetCore.Rewrite.RewriteOptions options) { throw null; } public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToWww(this Microsoft.AspNetCore.Rewrite.RewriteOptions options, int statusCode) { throw null; } + public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToWww(this Microsoft.AspNetCore.Rewrite.RewriteOptions options, int statusCode, params string[] domains) { throw null; } + public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToWww(this Microsoft.AspNetCore.Rewrite.RewriteOptions options, params string[] domains) { throw null; } public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToWwwPermanent(this Microsoft.AspNetCore.Rewrite.RewriteOptions options) { throw null; } + public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToWwwPermanent(this Microsoft.AspNetCore.Rewrite.RewriteOptions options, params string[] domains) { throw null; } public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRewrite(this Microsoft.AspNetCore.Rewrite.RewriteOptions options, string regex, string replacement, bool skipRemainingRules) { throw null; } } public enum RuleResult @@ -129,8 +132,10 @@ namespace Microsoft.AspNetCore.Rewrite.Internal } public partial class RedirectToWwwRule : Microsoft.AspNetCore.Rewrite.IRule { + public readonly string[] _domains; public readonly int _statusCode; public RedirectToWwwRule(int statusCode) { } + public RedirectToWwwRule(int statusCode, params string[] domains) { } public virtual void ApplyRule(Microsoft.AspNetCore.Rewrite.RewriteContext context) { } } public partial class RewriteRule : Microsoft.AspNetCore.Rewrite.IRule diff --git a/src/Middleware/Rewrite/src/Internal/RedirectToWwwRule.cs b/src/Middleware/Rewrite/src/Internal/RedirectToWwwRule.cs index 4675476e60..a445d7227e 100644 --- a/src/Middleware/Rewrite/src/Internal/RedirectToWwwRule.cs +++ b/src/Middleware/Rewrite/src/Internal/RedirectToWwwRule.cs @@ -12,15 +12,33 @@ namespace Microsoft.AspNetCore.Rewrite.Internal public class RedirectToWwwRule : IRule { public readonly int _statusCode; + public readonly string[] _domains; public RedirectToWwwRule(int statusCode) { _statusCode = statusCode; } + public RedirectToWwwRule(int statusCode, params string[] domains) + { + if (domains == null) + { + throw new ArgumentNullException(nameof(domains)); + } + + if (domains.Length < 1) + { + throw new ArgumentException(nameof(domains)); + } + + _domains = domains; + _statusCode = statusCode; + } + public virtual void ApplyRule(RewriteContext context) { var req = context.HttpContext.Request; + if (req.Host.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) { context.Result = RuleResult.ContinueRules; @@ -33,6 +51,26 @@ namespace Microsoft.AspNetCore.Rewrite.Internal return; } + if (_domains != null) + { + var isHostInDomains = false; + + foreach (var domain in _domains) + { + if (domain.Equals(req.Host.Host, StringComparison.OrdinalIgnoreCase)) + { + isHostInDomains = true; + break; + } + } + + if (!isHostInDomains) + { + context.Result = RuleResult.ContinueRules; + return; + } + } + var wwwHost = new HostString($"www.{req.Host.Value}"); var newUrl = UriHelper.BuildAbsolute(req.Scheme, wwwHost, req.PathBase, req.Path, req.QueryString); var response = context.HttpContext.Response; diff --git a/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs b/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs index d1c839899c..d1b69dbd34 100644 --- a/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs +++ b/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs @@ -128,6 +128,17 @@ namespace Microsoft.AspNetCore.Rewrite return AddRedirectToWww(options, statusCode: StatusCodes.Status308PermanentRedirect); } + /// + /// Permanently redirects the request to the www subdomain if the request is non-www. + /// + /// The . + /// Limit the rule to apply only on the specified domain(s). + /// + public static RewriteOptions AddRedirectToWwwPermanent(this RewriteOptions options, params string[] domains) + { + return AddRedirectToWww(options, statusCode: StatusCodes.Status308PermanentRedirect, domains); + } + /// /// Redirect the request to the www subdomain if the incoming request is non-www. /// @@ -137,6 +148,16 @@ namespace Microsoft.AspNetCore.Rewrite return AddRedirectToWww(options, statusCode: StatusCodes.Status307TemporaryRedirect); } + /// + /// Redirect the request to the www subdomain if the incoming request is non-www. + /// + /// The . + /// Limit the rule to apply only on the specified domain(s). + public static RewriteOptions AddRedirectToWww(this RewriteOptions options, params string[] domains) + { + return AddRedirectToWww(options, statusCode: StatusCodes.Status307TemporaryRedirect, domains); + } + /// /// Redirect the request to the www subdomain if the incoming request is non-www. /// @@ -147,5 +168,17 @@ namespace Microsoft.AspNetCore.Rewrite options.Rules.Add(new RedirectToWwwRule(statusCode)); return options; } + + /// + /// Redirect the request to the www subdomain if the incoming request is non-www. + /// + /// The . + /// The status code to add to the response. + /// Limit the rule to apply only on the specified domain(s). + public static RewriteOptions AddRedirectToWww(this RewriteOptions options, int statusCode, params string[] domains) + { + options.Rules.Add(new RedirectToWwwRule(statusCode, domains)); + return options; + } } } diff --git a/src/Middleware/Rewrite/test/MiddlewareTests.cs b/src/Middleware/Rewrite/test/MiddlewareTests.cs index 5c6fb1e4ec..0e7eb2c047 100644 --- a/src/Middleware/Rewrite/test/MiddlewareTests.cs +++ b/src/Middleware/Rewrite/test/MiddlewareTests.cs @@ -284,5 +284,86 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.CodeRules Assert.Equal("/foo", response.Headers.Location.OriginalString); } + + [Theory] + [InlineData("http://example.com")] + [InlineData("https://example.com")] + [InlineData("http://example.com:8081")] + [InlineData("https://example.com:8081")] + [InlineData("https://example.com:8081/example?q=1")] + public async Task CheckNoRedirectToWwwInNonWhitelistedDomains(string requestUri) + { + var options = new RewriteOptions().AddRedirectToWww("example2.com"); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseRewriter(options); + }); + var server = new TestServer(builder); + + var response = await server.CreateClient().GetAsync(new Uri(requestUri)); + + Assert.Null(response.Headers.Location); + } + + [Theory] + [InlineData("http://example.com/", "http://www.example.com/")] + [InlineData("https://example.com/", "https://www.example.com/")] + [InlineData("http://example.com:8081", "http://www.example.com:8081/")] + [InlineData("http://example.com:8081/example?q=1", "http://www.example.com:8081/example?q=1")] + public async Task CheckRedirectToWwwInWhitelistedDomains(string requestUri, string redirectUri) + { + var options = new RewriteOptions().AddRedirectToWww("example.com"); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseRewriter(options); + }); + var server = new TestServer(builder); + + var response = await server.CreateClient().GetAsync(new Uri(requestUri)); + + Assert.Equal(redirectUri, response.Headers.Location.OriginalString); + Assert.Equal(StatusCodes.Status307TemporaryRedirect, (int)response.StatusCode); + } + + [Fact] + public async Task CheckPermanentRedirectToWwwInWhitelistedDomains() + { + var options = new RewriteOptions().AddRedirectToWwwPermanent("example.com"); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseRewriter(options); + }); + var server = new TestServer(builder); + + var response = await server.CreateClient().GetAsync(new Uri("https://example.com")); + + Assert.Equal("https://www.example.com/", response.Headers.Location.OriginalString); + Assert.Equal(StatusCodes.Status308PermanentRedirect, (int)response.StatusCode); + } + + [Theory] + [InlineData(StatusCodes.Status301MovedPermanently)] + [InlineData(StatusCodes.Status302Found)] + [InlineData(StatusCodes.Status307TemporaryRedirect)] + [InlineData(StatusCodes.Status308PermanentRedirect)] + public async Task CheckRedirectToWwwWithStatusCodeInWhitelistedDomains(int statusCode) + { + var options = new RewriteOptions().AddRedirectToWww(statusCode: statusCode, "example.com"); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseRewriter(options); + }); + var server = new TestServer(builder); + + var response = await server.CreateClient().GetAsync(new Uri("https://example.com")); + + Assert.Equal("https://www.example.com/", response.Headers.Location.OriginalString); + Assert.Equal(statusCode, (int)response.StatusCode); + } + } }