From 6de2a9155181412ef415cc24e03b08b0a5e7602a Mon Sep 17 00:00:00 2001 From: Saravana Kumar Date: Wed, 28 Aug 2019 00:31:42 +0530 Subject: [PATCH 1/2] Add redirect to domain if incomming request is www. --- .../Rewrite/src/RedirectToDomainRule.cs | 85 ++++++++++++++ .../Rewrite/src/RewriteOptionsExtensions.cs | 67 +++++++++++ .../Rewrite/test/MiddlewareTests.cs | 105 ++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 src/Middleware/Rewrite/src/RedirectToDomainRule.cs diff --git a/src/Middleware/Rewrite/src/RedirectToDomainRule.cs b/src/Middleware/Rewrite/src/RedirectToDomainRule.cs new file mode 100644 index 000000000000..cac5f2e2ab50 --- /dev/null +++ b/src/Middleware/Rewrite/src/RedirectToDomainRule.cs @@ -0,0 +1,85 @@ +// 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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Rewrite.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Rewrite +{ + internal class RedirectToDomainRule : IRule + { + public readonly int _statusCode; + public readonly string[] _domains; + + public RedirectToDomainRule(int statusCode) + { + _statusCode = statusCode; + } + + public RedirectToDomainRule(int statusCode, params string[] domains) + { + if (domains == null) + { + throw new ArgumentNullException(nameof(domains)); + } + + if (domains.Length < 1) + { + throw new ArgumentException(nameof(domains)); + } + + foreach(var domain in domains) + { + if (!domain.StartsWith("www.", StringComparison.OrdinalIgnoreCase)) + { + throw new NotSupportedException($"Domain: {domain}. Not supported for this redirection rule. Domain should start with www."); + } + } + + _domains = domains; + _statusCode = statusCode; + } + + public virtual void ApplyRule(RewriteContext context) + { + var req = context.HttpContext.Request; + + if (!req.Host.Value.StartsWith("www.", StringComparison.OrdinalIgnoreCase)) + { + context.Result = RuleResult.ContinueRules; + 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(req.Host.Value.Remove(0, 4)); + var newUrl = UriHelper.BuildAbsolute(req.Scheme, wwwHost, req.PathBase, req.Path, req.QueryString); + var response = context.HttpContext.Response; + response.StatusCode = _statusCode; + response.Headers[HeaderNames.Location] = newUrl; + context.Result = RuleResult.EndResponse; + context.Logger.RedirectedToWww(); + } + } +} diff --git a/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs b/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs index 88b6d783b4db..8aecd3bb47eb 100644 --- a/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs +++ b/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs @@ -179,5 +179,72 @@ public static RewriteOptions AddRedirectToWww(this RewriteOptions options, int s options.Rules.Add(new RedirectToWwwRule(statusCode, domains)); return options; } + + /// + /// Permanently redirects the request to the domain if the request is www. + /// + /// The . + /// + public static RewriteOptions AddRedirectToDomainPermanent(this RewriteOptions options) + { + return AddRedirectToDomain(options, statusCode: StatusCodes.Status308PermanentRedirect); + } + + /// + /// Permanently redirects the request to the domain if the request is www. + /// + /// The . + /// Limit the rule to apply only on the specified domain(s). + /// + public static RewriteOptions AddRedirectToDomainPermanent(this RewriteOptions options, params string[] domains) + { + return AddRedirectToDomain(options, statusCode: StatusCodes.Status308PermanentRedirect, domains); + } + + /// + /// Redirect the request to the domain if the incoming request is www. + /// + /// The . + /// + public static RewriteOptions AddRedirectToDomain(this RewriteOptions options) + { + return AddRedirectToDomain(options, StatusCodes.Status307TemporaryRedirect); + } + + /// + /// Redirect the request to the domain if the incoming request is www. + /// + /// The . + /// The status code to add the response. + /// + public static RewriteOptions AddRedirectToDomain(this RewriteOptions options, int statusCode) + { + options.Rules.Add(new RedirectToDomainRule(statusCode)); + return options; + } + + /// + /// Redirect the request to the domain if the incoming request is www. + /// + /// The . + /// Limit the rule to apply only on the specified domain(s). + /// + public static RewriteOptions AddRedirectToDomain(this RewriteOptions options, params string[] domains) + { + return AddRedirectToDomain(options, StatusCodes.Status307TemporaryRedirect, domains); + } + + /// + /// Redirect the request to the domain if the incoming request is www. + /// + /// The . + /// The status code to add the response. + /// Limit the rule to apply only on the specified domain(s). + /// + public static RewriteOptions AddRedirectToDomain(this RewriteOptions options, int statusCode, params string[] domains) + { + options.Rules.Add(new RedirectToDomainRule(statusCode, domains)); + return options; + } } } diff --git a/src/Middleware/Rewrite/test/MiddlewareTests.cs b/src/Middleware/Rewrite/test/MiddlewareTests.cs index 0e7eb2c0479e..1c4e89b583b6 100644 --- a/src/Middleware/Rewrite/test/MiddlewareTests.cs +++ b/src/Middleware/Rewrite/test/MiddlewareTests.cs @@ -232,6 +232,32 @@ public async Task CheckNoRedirectToWww(string requestUri) Assert.Null(response.Headers.Location); } + [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")] + [InlineData("http://localhost")] + [InlineData("https://localhost")] + [InlineData("http://localhost:8081")] + [InlineData("https://localhost:8081")] + [InlineData("https://localhost:8081/example?q=1")] + public async Task CheckNoRedirectToDomain(string requestUri) + { + var options = new RewriteOptions().AddRedirectToDomain(); + 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); + } + [Fact] public async Task CheckIfEmptyStringRedirectCorrectly() { @@ -365,5 +391,84 @@ public async Task CheckRedirectToWwwWithStatusCodeInWhitelistedDomains(int statu Assert.Equal(statusCode, (int)response.StatusCode); } + [Theory] + [InlineData("http://www.example.com")] + [InlineData("https://www.example.com")] + [InlineData("http://www.example.com:8081")] + [InlineData("https://www.example.com:8081")] + [InlineData("https://www.example.com:8081/example?q=1")] + public async Task CheckNoRedirectToDomainInNonWhitelistedWwwSubdomain(string requestUri) + { + var options = new RewriteOptions().AddRedirectToDomain("www.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://www.example.com/", "http://example.com/")] + [InlineData("https://www.example.com/", "https://example.com/")] + [InlineData("http://www.example.com:8081", "http://example.com:8081/")] + [InlineData("http://www.example.com:8081/example?q=1", "http://example.com:8081/example?q=1")] + public async Task CheckRedirectToDomainInWhitelistedWwwSubdomain(string requestUri, string redirectUri) + { + var options = new RewriteOptions().AddRedirectToDomain("www.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 CheckPermanentRedirectToDomainInWhitelistedWwwSubDomains() + { + var options = new RewriteOptions().AddRedirectToDomainPermanent("www.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://www.example.com")); + + Assert.Equal("https://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 CheckRedirectToDomainWithStatusCodeInWhitelistedWwwSubDomain(int statusCode) + { + var options = new RewriteOptions().AddRedirectToDomain(statusCode: statusCode, "www.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://www.example.com")); + + Assert.Equal("https://example.com/", response.Headers.Location.OriginalString); + Assert.Equal(statusCode, (int)response.StatusCode); + } } } From a7eabb00edcad5c77b912fe749bcda98a86c27e1 Mon Sep 17 00:00:00 2001 From: Saravana Kumar Date: Wed, 28 Aug 2019 14:51:13 +0530 Subject: [PATCH 2/2] Added Logging functionality and updated the review-1 comments --- .../RewriteMiddlewareLoggingExtensions.cs | 11 +++++++++ ...oDomainRule.cs => RedirectToNonWwwRule.cs} | 10 ++++---- .../Rewrite/src/RewriteOptionsExtensions.cs | 24 +++++++++---------- .../Rewrite/test/MiddlewareTests.cs | 18 +++++++------- 4 files changed, 37 insertions(+), 26 deletions(-) rename src/Middleware/Rewrite/src/{RedirectToDomainRule.cs => RedirectToNonWwwRule.cs} (89%) diff --git a/src/Middleware/Rewrite/src/Extensions/RewriteMiddlewareLoggingExtensions.cs b/src/Middleware/Rewrite/src/Extensions/RewriteMiddlewareLoggingExtensions.cs index 4e35fc478c62..81f1230c1995 100644 --- a/src/Middleware/Rewrite/src/Extensions/RewriteMiddlewareLoggingExtensions.cs +++ b/src/Middleware/Rewrite/src/Extensions/RewriteMiddlewareLoggingExtensions.cs @@ -17,6 +17,7 @@ internal static class RewriteMiddlewareLoggingExtensions private static readonly Action _modRewriteMatchedRule; private static readonly Action _redirectedToHttps; private static readonly Action _redirectedToWww; + private static readonly Action _redirectedToNonWww; private static readonly Action _redirectedRequest; private static readonly Action _rewrittenRequest; private static readonly Action _abortedRequest; @@ -88,6 +89,11 @@ static RewriteMiddlewareLoggingExtensions() LogLevel.Information, new EventId(13, "RedirectedToWww"), "Request redirected to www"); + + _redirectedToNonWww = LoggerMessage.Define( + LogLevel.Information, + new EventId(14, "RedirectedToNonWww"), + "Request redirected to Non-www"); } public static void RewriteMiddlewareRequestContinueResults(this ILogger logger, string currentUrl) @@ -135,6 +141,11 @@ public static void RedirectedToWww(this ILogger logger) _redirectedToWww(logger, null); } + public static void RedirectedToNonWww(this ILogger logger) + { + _redirectedToNonWww(logger, null); + } + public static void RedirectedRequest(this ILogger logger, string redirectedUrl) { _redirectedRequest(logger, redirectedUrl, null); diff --git a/src/Middleware/Rewrite/src/RedirectToDomainRule.cs b/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs similarity index 89% rename from src/Middleware/Rewrite/src/RedirectToDomainRule.cs rename to src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs index cac5f2e2ab50..ada03346cc03 100644 --- a/src/Middleware/Rewrite/src/RedirectToDomainRule.cs +++ b/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs @@ -9,17 +9,17 @@ namespace Microsoft.AspNetCore.Rewrite { - internal class RedirectToDomainRule : IRule + internal class RedirectToNonWwwRule : IRule { public readonly int _statusCode; public readonly string[] _domains; - public RedirectToDomainRule(int statusCode) + public RedirectToNonWwwRule(int statusCode) { _statusCode = statusCode; } - public RedirectToDomainRule(int statusCode, params string[] domains) + public RedirectToNonWwwRule(int statusCode, params string[] domains) { if (domains == null) { @@ -28,7 +28,7 @@ public RedirectToDomainRule(int statusCode, params string[] domains) if (domains.Length < 1) { - throw new ArgumentException(nameof(domains)); + throw new ArgumentException("Atleast provide one value.", nameof(domains)); } foreach(var domain in domains) @@ -79,7 +79,7 @@ public virtual void ApplyRule(RewriteContext context) response.StatusCode = _statusCode; response.Headers[HeaderNames.Location] = newUrl; context.Result = RuleResult.EndResponse; - context.Logger.RedirectedToWww(); + context.Logger.RedirectedToNonWww(); } } } diff --git a/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs b/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs index 8aecd3bb47eb..cd8012c7176d 100644 --- a/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs +++ b/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs @@ -185,9 +185,9 @@ public static RewriteOptions AddRedirectToWww(this RewriteOptions options, int s /// /// The . /// - public static RewriteOptions AddRedirectToDomainPermanent(this RewriteOptions options) + public static RewriteOptions AddRedirectToNonWwwPermanent(this RewriteOptions options) { - return AddRedirectToDomain(options, statusCode: StatusCodes.Status308PermanentRedirect); + return AddRedirectToNonWww(options, statusCode: StatusCodes.Status308PermanentRedirect); } /// @@ -196,9 +196,9 @@ public static RewriteOptions AddRedirectToDomainPermanent(this RewriteOptions op /// The . /// Limit the rule to apply only on the specified domain(s). /// - public static RewriteOptions AddRedirectToDomainPermanent(this RewriteOptions options, params string[] domains) + public static RewriteOptions AddRedirectToNonWwwPermanent(this RewriteOptions options, params string[] domains) { - return AddRedirectToDomain(options, statusCode: StatusCodes.Status308PermanentRedirect, domains); + return AddRedirectToNonWww(options, statusCode: StatusCodes.Status308PermanentRedirect, domains); } /// @@ -206,9 +206,9 @@ public static RewriteOptions AddRedirectToDomainPermanent(this RewriteOptions op /// /// The . /// - public static RewriteOptions AddRedirectToDomain(this RewriteOptions options) + public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options) { - return AddRedirectToDomain(options, StatusCodes.Status307TemporaryRedirect); + return AddRedirectToNonWww(options, StatusCodes.Status307TemporaryRedirect); } /// @@ -217,9 +217,9 @@ public static RewriteOptions AddRedirectToDomain(this RewriteOptions options) /// The . /// The status code to add the response. /// - public static RewriteOptions AddRedirectToDomain(this RewriteOptions options, int statusCode) + public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options, int statusCode) { - options.Rules.Add(new RedirectToDomainRule(statusCode)); + options.Rules.Add(new RedirectToNonWwwRule(statusCode)); return options; } @@ -229,9 +229,9 @@ public static RewriteOptions AddRedirectToDomain(this RewriteOptions options, in /// The . /// Limit the rule to apply only on the specified domain(s). /// - public static RewriteOptions AddRedirectToDomain(this RewriteOptions options, params string[] domains) + public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options, params string[] domains) { - return AddRedirectToDomain(options, StatusCodes.Status307TemporaryRedirect, domains); + return AddRedirectToNonWww(options, StatusCodes.Status307TemporaryRedirect, domains); } /// @@ -241,9 +241,9 @@ public static RewriteOptions AddRedirectToDomain(this RewriteOptions options, pa /// The status code to add the response. /// Limit the rule to apply only on the specified domain(s). /// - public static RewriteOptions AddRedirectToDomain(this RewriteOptions options, int statusCode, params string[] domains) + public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options, int statusCode, params string[] domains) { - options.Rules.Add(new RedirectToDomainRule(statusCode, domains)); + options.Rules.Add(new RedirectToNonWwwRule(statusCode, domains)); return options; } } diff --git a/src/Middleware/Rewrite/test/MiddlewareTests.cs b/src/Middleware/Rewrite/test/MiddlewareTests.cs index 1c4e89b583b6..4457d267c18e 100644 --- a/src/Middleware/Rewrite/test/MiddlewareTests.cs +++ b/src/Middleware/Rewrite/test/MiddlewareTests.cs @@ -245,7 +245,7 @@ public async Task CheckNoRedirectToWww(string requestUri) [InlineData("https://localhost:8081/example?q=1")] public async Task CheckNoRedirectToDomain(string requestUri) { - var options = new RewriteOptions().AddRedirectToDomain(); + var options = new RewriteOptions().AddRedirectToNonWww(); var builder = new WebHostBuilder() .Configure(app => { @@ -397,9 +397,9 @@ public async Task CheckRedirectToWwwWithStatusCodeInWhitelistedDomains(int statu [InlineData("http://www.example.com:8081")] [InlineData("https://www.example.com:8081")] [InlineData("https://www.example.com:8081/example?q=1")] - public async Task CheckNoRedirectToDomainInNonWhitelistedWwwSubdomain(string requestUri) + public async Task CheckNoRedirectToNonWwwInNonWhitelistedWwwSubdomain(string requestUri) { - var options = new RewriteOptions().AddRedirectToDomain("www.example2.com"); + var options = new RewriteOptions().AddRedirectToNonWww("www.example2.com"); var builder = new WebHostBuilder() .Configure(app => { @@ -417,9 +417,9 @@ public async Task CheckNoRedirectToDomainInNonWhitelistedWwwSubdomain(string req [InlineData("https://www.example.com/", "https://example.com/")] [InlineData("http://www.example.com:8081", "http://example.com:8081/")] [InlineData("http://www.example.com:8081/example?q=1", "http://example.com:8081/example?q=1")] - public async Task CheckRedirectToDomainInWhitelistedWwwSubdomain(string requestUri, string redirectUri) + public async Task CheckRedirectToNonWwwInWhitelistedWwwSubdomain(string requestUri, string redirectUri) { - var options = new RewriteOptions().AddRedirectToDomain("www.example.com"); + var options = new RewriteOptions().AddRedirectToNonWww("www.example.com"); var builder = new WebHostBuilder() .Configure(app => { @@ -434,9 +434,9 @@ public async Task CheckRedirectToDomainInWhitelistedWwwSubdomain(string requestU } [Fact] - public async Task CheckPermanentRedirectToDomainInWhitelistedWwwSubDomains() + public async Task CheckPermanentRedirectToNonWwwInWhitelistedWwwSubDomains() { - var options = new RewriteOptions().AddRedirectToDomainPermanent("www.example.com"); + var options = new RewriteOptions().AddRedirectToNonWwwPermanent("www.example.com"); var builder = new WebHostBuilder() .Configure(app => { @@ -455,9 +455,9 @@ public async Task CheckPermanentRedirectToDomainInWhitelistedWwwSubDomains() [InlineData(StatusCodes.Status302Found)] [InlineData(StatusCodes.Status307TemporaryRedirect)] [InlineData(StatusCodes.Status308PermanentRedirect)] - public async Task CheckRedirectToDomainWithStatusCodeInWhitelistedWwwSubDomain(int statusCode) + public async Task CheckRedirectToNonWwwWithStatusCodeInWhitelistedWwwSubDomain(int statusCode) { - var options = new RewriteOptions().AddRedirectToDomain(statusCode: statusCode, "www.example.com"); + var options = new RewriteOptions().AddRedirectToNonWww(statusCode: statusCode, "www.example.com"); var builder = new WebHostBuilder() .Configure(app => {