From fdd91e97e0508b01d3248079f9899d48890db91e Mon Sep 17 00:00:00 2001 From: Ken Dale Date: Fri, 9 Aug 2019 08:16:13 -0400 Subject: [PATCH 1/4] Add www to root domain redirects Add extension methods to redirect from www.example.com to example.com. Addresses #12978 --- ...Microsoft.AspNetCore.Rewrite.netcoreapp.cs | 6 ++ .../RewriteMiddlewareLoggingExtensions.cs | 11 +++ .../Rewrite/src/RedirectToNonWwwRule.cs | 83 +++++++++++++++++++ .../Rewrite/src/RewriteOptionsExtensions.cs | 63 ++++++++++++++ .../Rewrite/test/MiddlewareTests.cs | 61 +++++++++++++- 5 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs diff --git a/src/Middleware/Rewrite/ref/Microsoft.AspNetCore.Rewrite.netcoreapp.cs b/src/Middleware/Rewrite/ref/Microsoft.AspNetCore.Rewrite.netcoreapp.cs index 4a2f96518607..49ff64cecb0b 100644 --- a/src/Middleware/Rewrite/ref/Microsoft.AspNetCore.Rewrite.netcoreapp.cs +++ b/src/Middleware/Rewrite/ref/Microsoft.AspNetCore.Rewrite.netcoreapp.cs @@ -54,6 +54,12 @@ public static partial class RewriteOptionsExtensions public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToHttps(this Microsoft.AspNetCore.Rewrite.RewriteOptions options, int statusCode) { throw null; } public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToHttps(this Microsoft.AspNetCore.Rewrite.RewriteOptions options, int statusCode, int? sslPort) { throw null; } public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToHttpsPermanent(this Microsoft.AspNetCore.Rewrite.RewriteOptions options) { throw null; } + public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToNonWww(this Microsoft.AspNetCore.Rewrite.RewriteOptions options) { throw null; } + public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToNonWww(this Microsoft.AspNetCore.Rewrite.RewriteOptions options, int statusCode) { throw null; } + public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToNonWww(this Microsoft.AspNetCore.Rewrite.RewriteOptions options, int statusCode, params string[] domains) { throw null; } + public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToNonWww(this Microsoft.AspNetCore.Rewrite.RewriteOptions options, params string[] domains) { throw null; } + public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToNonWwwPermanent(this Microsoft.AspNetCore.Rewrite.RewriteOptions options) { throw null; } + public static Microsoft.AspNetCore.Rewrite.RewriteOptions AddRedirectToNonWwwPermanent(this Microsoft.AspNetCore.Rewrite.RewriteOptions options, params string[] domains) { 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; } diff --git a/src/Middleware/Rewrite/src/Extensions/RewriteMiddlewareLoggingExtensions.cs b/src/Middleware/Rewrite/src/Extensions/RewriteMiddlewareLoggingExtensions.cs index 4e35fc478c62..177469464f4f 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 root domain from www subdomain"); } 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/RedirectToNonWwwRule.cs b/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs new file mode 100644 index 000000000000..5217208a02b7 --- /dev/null +++ b/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs @@ -0,0 +1,83 @@ +// 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 RedirectToNonWwwRule : IRule + { + public readonly int _statusCode; + public readonly string[] _domains; + + public RedirectToNonWwwRule(int statusCode) + { + _statusCode = statusCode; + } + + public RedirectToNonWwwRule(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; + return; + } + + 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 nonWwwHost = new HostString(req.Host.Value.Substring(4)); // We verified the hostname begins with "www." already. + var newUrl = UriHelper.BuildAbsolute(req.Scheme, nonWwwHost, 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.RedirectedToNonWww(); + } + } +} diff --git a/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs b/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs index 88b6d783b4db..28d2660faec8 100644 --- a/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs +++ b/src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs @@ -179,5 +179,68 @@ public static RewriteOptions AddRedirectToWww(this RewriteOptions options, int s options.Rules.Add(new RedirectToWwwRule(statusCode, domains)); return options; } + + /// + /// Permanently redirects the request to the root domain if the request is from the www subdomain. + /// + /// The . + /// + public static RewriteOptions AddRedirectToNonWwwPermanent(this RewriteOptions options) + { + return AddRedirectToNonWww(options, statusCode: StatusCodes.Status308PermanentRedirect); + } + + /// + /// Permanently redirects the request to the root domain if the request is from the www subdomain. + /// + /// The . + /// Limit the rule to apply only on the specified domain(s). + /// + public static RewriteOptions AddRedirectToNonWwwPermanent(this RewriteOptions options, params string[] domains) + { + return AddRedirectToNonWww(options, statusCode: StatusCodes.Status308PermanentRedirect, domains); + } + + /// + /// Redirect the request to the root domain if the incoming request is from the www subdomain. + /// + /// The . + public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options) + { + return AddRedirectToNonWww(options, statusCode: StatusCodes.Status307TemporaryRedirect); + } + + /// + /// Redirect the request to the root domain if the incoming request is from the www subdomain. + /// + /// The . + /// Limit the rule to apply only on the specified domain(s). + public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options, params string[] domains) + { + return AddRedirectToNonWww(options, statusCode: StatusCodes.Status307TemporaryRedirect, domains); + } + + /// + /// Redirect the request to the root domain if the incoming request is from the www subdomain. + /// + /// The . + /// The status code to add to the response. + public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options, int statusCode) + { + options.Rules.Add(new RedirectToNonWwwRule(statusCode)); + return options; + } + + /// + /// Redirect the request to the root domain if the incoming request is from the www subdomain. + /// + /// The . + /// The status code to add to the response. + /// Limit the rule to apply only on the specified domain(s). + public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options, int statusCode, params string[] 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 0e7eb2c0479e..e8a4a4aec893 100644 --- a/src/Middleware/Rewrite/test/MiddlewareTests.cs +++ b/src/Middleware/Rewrite/test/MiddlewareTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -232,6 +232,65 @@ public async Task CheckNoRedirectToWww(string requestUri) Assert.Null(response.Headers.Location); } + [Theory] + [InlineData(StatusCodes.Status301MovedPermanently)] + [InlineData(StatusCodes.Status302Found)] + [InlineData(StatusCodes.Status307TemporaryRedirect)] + [InlineData(StatusCodes.Status308PermanentRedirect)] + public async Task CheckRedirectToNonWwwWithStatusCode(int statusCode) + { + var options = new RewriteOptions().AddRedirectToNonWww(statusCode: statusCode); + 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); + } + + [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 CheckRedirectToNonWww(string requestUri, string redirectUri) + { + var options = new RewriteOptions().AddRedirectToNonWww(); + 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 CheckPermanentRedirectToNonWww() + { + var options = new RewriteOptions().AddRedirectToNonWwwPermanent(); + 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); + } + [Fact] public async Task CheckIfEmptyStringRedirectCorrectly() { From 9ccc1ddaf77fee35dd18d545a7f195c16b4d1df4 Mon Sep 17 00:00:00 2001 From: Ken Dale Date: Tue, 13 Aug 2019 15:21:29 -0400 Subject: [PATCH 2/4] Refactor into RedirectToWwwRuleBase --- .../Rewrite/src/RedirectToNonWwwRule.cs | 75 +------------ .../Rewrite/src/RedirectToWwwRule.cs | 75 +------------ .../Rewrite/src/RedirectToWwwRuleBase.cs | 105 ++++++++++++++++++ 3 files changed, 113 insertions(+), 142 deletions(-) create mode 100644 src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs diff --git a/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs b/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs index 5217208a02b7..88dfa92fdd53 100644 --- a/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs +++ b/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs @@ -1,83 +1,16 @@ // 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 RedirectToNonWwwRule : IRule + internal class RedirectToNonWwwRule : RedirectToWwwRuleBase { - public readonly int _statusCode; - public readonly string[] _domains; - public RedirectToNonWwwRule(int statusCode) - { - _statusCode = statusCode; - } + : base(statusCode) { } public RedirectToNonWwwRule(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; - return; - } - - 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; - } - } + : base(statusCode, domains) { } - var nonWwwHost = new HostString(req.Host.Value.Substring(4)); // We verified the hostname begins with "www." already. - var newUrl = UriHelper.BuildAbsolute(req.Scheme, nonWwwHost, 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.RedirectedToNonWww(); - } + protected override bool RedirectToWww => false; } } diff --git a/src/Middleware/Rewrite/src/RedirectToWwwRule.cs b/src/Middleware/Rewrite/src/RedirectToWwwRule.cs index fcf9735aef6b..cd6e785e2891 100644 --- a/src/Middleware/Rewrite/src/RedirectToWwwRule.cs +++ b/src/Middleware/Rewrite/src/RedirectToWwwRule.cs @@ -1,83 +1,16 @@ // 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 RedirectToWwwRule : IRule + internal class RedirectToWwwRule : RedirectToWwwRuleBase { - public readonly int _statusCode; - public readonly string[] _domains; - public RedirectToWwwRule(int statusCode) - { - _statusCode = statusCode; - } + : base(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; - return; - } - - 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; - } - } + : base(statusCode, domains) { } - 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; - response.StatusCode = _statusCode; - response.Headers[HeaderNames.Location] = newUrl; - context.Result = RuleResult.EndResponse; - context.Logger.RedirectedToWww(); - } + protected override bool RedirectToWww => true; } } diff --git a/src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs b/src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs new file mode 100644 index 000000000000..fe2628385728 --- /dev/null +++ b/src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs @@ -0,0 +1,105 @@ +// 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 abstract class RedirectToWwwRuleBase : IRule + { + private const string WwwDot = "www."; + private const string Localhost = "localhost"; + + public readonly int _statusCode; + public readonly string[] _domains; + + protected RedirectToWwwRuleBase(int statusCode) + { + _statusCode = statusCode; + } + + protected abstract bool RedirectToWww { get; } + + protected RedirectToWwwRuleBase(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; + return; + } + + if (RedirectToWww && req.Host.Value.StartsWith(WwwDot, StringComparison.OrdinalIgnoreCase)) + { + context.Result = RuleResult.ContinueRules; + return; + } + + if (!RedirectToWww && !req.Host.Value.StartsWith(WwwDot, 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 newHost = RedirectToWww + ? new HostString($"www.{req.Host.Value}") + : new HostString(req.Host.Value.Substring(4)); // We verified the hostname begins with "www." already. + + var newUrl = UriHelper.BuildAbsolute(req.Scheme, newHost, req.PathBase, req.Path, req.QueryString); + var response = context.HttpContext.Response; + response.StatusCode = _statusCode; + response.Headers[HeaderNames.Location] = newUrl; + context.Result = RuleResult.EndResponse; + + if (RedirectToWww) + { + context.Logger.RedirectedToWww(); + } + else + { + context.Logger.RedirectedToNonWww(); + } + } + } +} From 120d3ea8ffbbabff532814b4bed7da2e78318ce8 Mon Sep 17 00:00:00 2001 From: Ken Dale Date: Thu, 29 Aug 2019 10:28:34 -0400 Subject: [PATCH 3/4] Update ArgumentException message --- src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs b/src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs index fe2628385728..4c4eda4764b6 100644 --- a/src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs +++ b/src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs @@ -33,7 +33,7 @@ protected RedirectToWwwRuleBase(int statusCode, params string[] domains) if (domains.Length < 1) { - throw new ArgumentException(nameof(domains)); + throw new ArgumentException($"One or more {nameof(domains)} must be provided."); } _domains = domains; From 2acc1b5c09086838336161ff92d7de3d5aaa4bde Mon Sep 17 00:00:00 2001 From: Ken Dale Date: Fri, 20 Sep 2019 12:17:04 -0400 Subject: [PATCH 4/4] Refactor into RedirectToWwwHelper --- .../Rewrite/src/RedirectToNonWwwRule.cs | 56 +++++++++- .../Rewrite/src/RedirectToWwwHelper.cs | 61 ++++++++++ .../Rewrite/src/RedirectToWwwRule.cs | 56 +++++++++- .../Rewrite/src/RedirectToWwwRuleBase.cs | 105 ------------------ 4 files changed, 165 insertions(+), 113 deletions(-) create mode 100644 src/Middleware/Rewrite/src/RedirectToWwwHelper.cs delete mode 100644 src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs diff --git a/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs b/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs index 88dfa92fdd53..1c6c0a7fbc6f 100644 --- a/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs +++ b/src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs @@ -1,16 +1,64 @@ // 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.Rewrite.Logging; + namespace Microsoft.AspNetCore.Rewrite { - internal class RedirectToNonWwwRule : RedirectToWwwRuleBase + internal class RedirectToNonWwwRule : IRule { + private const string WwwDot = "www."; + + private readonly string[] _domains; + private readonly int _statusCode; + public RedirectToNonWwwRule(int statusCode) - : base(statusCode) { } + { + _statusCode = statusCode; + } public RedirectToNonWwwRule(int statusCode, params string[] domains) - : base(statusCode, domains) { } + { + if (domains == null) + { + throw new ArgumentNullException(nameof(domains)); + } + + if (domains.Length < 1) + { + throw new ArgumentException($"One or more {nameof(domains)} must be provided."); + } + + _domains = domains; + _statusCode = statusCode; + } + + public void ApplyRule(RewriteContext context) + { + var request = context.HttpContext.Request; + + var hostInDomains = RedirectToWwwHelper.IsHostInDomains(request, _domains); + + if (!hostInDomains) + { + context.Result = RuleResult.ContinueRules; + return; + } + + if (!request.Host.Value.StartsWith(WwwDot, StringComparison.OrdinalIgnoreCase)) + { + context.Result = RuleResult.ContinueRules; + return; + } + + RedirectToWwwHelper.SetRedirect( + context, + new HostString(request.Host.Value.Substring(4)), // We verified the hostname begins with "www." already. + _statusCode); - protected override bool RedirectToWww => false; + context.Logger.RedirectedToNonWww(); + } } } diff --git a/src/Middleware/Rewrite/src/RedirectToWwwHelper.cs b/src/Middleware/Rewrite/src/RedirectToWwwHelper.cs new file mode 100644 index 000000000000..4550e7ad9d49 --- /dev/null +++ b/src/Middleware/Rewrite/src/RedirectToWwwHelper.cs @@ -0,0 +1,61 @@ +// 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.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Rewrite +{ + internal static class RedirectToWwwHelper + { + private const string Localhost = "localhost"; + + public static bool IsHostInDomains(HttpRequest request, string[] domains) + { + if (request.Host.Host.Equals(Localhost, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (domains != null) + { + var isHostInDomains = false; + + foreach (var domain in domains) + { + if (domain.Equals(request.Host.Host, StringComparison.OrdinalIgnoreCase)) + { + isHostInDomains = true; + break; + } + } + + if (!isHostInDomains) + { + return false; + } + } + + return true; + } + + public static void SetRedirect(RewriteContext context, HostString newHost, int statusCode) + { + var request = context.HttpContext.Request; + var response = context.HttpContext.Response; + + var newUrl = UriHelper.BuildAbsolute( + request.Scheme, + newHost, + request.PathBase, + request.Path, + request.QueryString); + + response.StatusCode = statusCode; + response.Headers[HeaderNames.Location] = newUrl; + context.Result = RuleResult.EndResponse; + } + } +} diff --git a/src/Middleware/Rewrite/src/RedirectToWwwRule.cs b/src/Middleware/Rewrite/src/RedirectToWwwRule.cs index cd6e785e2891..f5b715a6488f 100644 --- a/src/Middleware/Rewrite/src/RedirectToWwwRule.cs +++ b/src/Middleware/Rewrite/src/RedirectToWwwRule.cs @@ -1,16 +1,64 @@ // 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.Rewrite.Logging; + namespace Microsoft.AspNetCore.Rewrite { - internal class RedirectToWwwRule : RedirectToWwwRuleBase + internal class RedirectToWwwRule : IRule { + private const string WwwDot = "www."; + + private readonly string[] _domains; + private readonly int _statusCode; + public RedirectToWwwRule(int statusCode) - : base(statusCode) { } + { + _statusCode = statusCode; + } public RedirectToWwwRule(int statusCode, params string[] domains) - : base(statusCode, domains) { } + { + if (domains == null) + { + throw new ArgumentNullException(nameof(domains)); + } + + if (domains.Length < 1) + { + throw new ArgumentException($"One or more {nameof(domains)} must be provided."); + } + + _domains = domains; + _statusCode = statusCode; + } + + public void ApplyRule(RewriteContext context) + { + var req = context.HttpContext.Request; + + var hostInDomains = RedirectToWwwHelper.IsHostInDomains(req, _domains); + + if (!hostInDomains) + { + context.Result = RuleResult.ContinueRules; + return; + } + + if (req.Host.Value.StartsWith(WwwDot, StringComparison.OrdinalIgnoreCase)) + { + context.Result = RuleResult.ContinueRules; + return; + } + + RedirectToWwwHelper.SetRedirect( + context, + new HostString($"www.{context.HttpContext.Request.Host.Value}"), + _statusCode); - protected override bool RedirectToWww => true; + context.Logger.RedirectedToWww(); + } } } diff --git a/src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs b/src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs deleted file mode 100644 index 4c4eda4764b6..000000000000 --- a/src/Middleware/Rewrite/src/RedirectToWwwRuleBase.cs +++ /dev/null @@ -1,105 +0,0 @@ -// 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 abstract class RedirectToWwwRuleBase : IRule - { - private const string WwwDot = "www."; - private const string Localhost = "localhost"; - - public readonly int _statusCode; - public readonly string[] _domains; - - protected RedirectToWwwRuleBase(int statusCode) - { - _statusCode = statusCode; - } - - protected abstract bool RedirectToWww { get; } - - protected RedirectToWwwRuleBase(int statusCode, params string[] domains) - { - if (domains == null) - { - throw new ArgumentNullException(nameof(domains)); - } - - if (domains.Length < 1) - { - throw new ArgumentException($"One or more {nameof(domains)} must be provided."); - } - - _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; - return; - } - - if (RedirectToWww && req.Host.Value.StartsWith(WwwDot, StringComparison.OrdinalIgnoreCase)) - { - context.Result = RuleResult.ContinueRules; - return; - } - - if (!RedirectToWww && !req.Host.Value.StartsWith(WwwDot, 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 newHost = RedirectToWww - ? new HostString($"www.{req.Host.Value}") - : new HostString(req.Host.Value.Substring(4)); // We verified the hostname begins with "www." already. - - var newUrl = UriHelper.BuildAbsolute(req.Scheme, newHost, req.PathBase, req.Path, req.QueryString); - var response = context.HttpContext.Response; - response.StatusCode = _statusCode; - response.Headers[HeaderNames.Location] = newUrl; - context.Result = RuleResult.EndResponse; - - if (RedirectToWww) - { - context.Logger.RedirectedToWww(); - } - else - { - context.Logger.RedirectedToNonWww(); - } - } - } -}