Skip to content

Add redirect to domain if request is www. #13486

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal static class RewriteMiddlewareLoggingExtensions
private static readonly Action<ILogger, Exception> _modRewriteMatchedRule;
private static readonly Action<ILogger, Exception> _redirectedToHttps;
private static readonly Action<ILogger, Exception> _redirectedToWww;
private static readonly Action<ILogger, Exception> _redirectedToNonWww;
private static readonly Action<ILogger, string, Exception> _redirectedRequest;
private static readonly Action<ILogger, string, Exception> _rewrittenRequest;
private static readonly Action<ILogger, string, Exception> _abortedRequest;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down
85 changes: 85 additions & 0 deletions src/Middleware/Rewrite/src/RedirectToNonWwwRule.cs
Original file line number Diff line number Diff line change
@@ -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 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("Atleast provide one value.", 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.RedirectedToNonWww();
}
}
}
67 changes: 67 additions & 0 deletions src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,5 +179,72 @@ public static RewriteOptions AddRedirectToWww(this RewriteOptions options, int s
options.Rules.Add(new RedirectToWwwRule(statusCode, domains));
return options;
}

/// <summary>
/// Permanently redirects the request to the domain if the request is www.
/// </summary>
/// <param name="options">The <see cref="RewriteOptions"/>.</param>
/// <returns></returns>
public static RewriteOptions AddRedirectToNonWwwPermanent(this RewriteOptions options)
{
return AddRedirectToNonWww(options, statusCode: StatusCodes.Status308PermanentRedirect);
}

/// <summary>
/// Permanently redirects the request to the domain if the request is www.
/// </summary>
/// <param name="options">The <see cref="RewriteOptions"/>.</param>
/// <param name="domains">Limit the rule to apply only on the specified domain(s).</param>
/// <returns></returns>
public static RewriteOptions AddRedirectToNonWwwPermanent(this RewriteOptions options, params string[] domains)
{
return AddRedirectToNonWww(options, statusCode: StatusCodes.Status308PermanentRedirect, domains);
}

/// <summary>
/// Redirect the request to the domain if the incoming request is www.
/// </summary>
/// <param name="options">The <see cref="RewriteOptions"/>.</param>
/// <returns></returns>
public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options)
{
return AddRedirectToNonWww(options, StatusCodes.Status307TemporaryRedirect);
}

/// <summary>
/// Redirect the request to the domain if the incoming request is www.
/// </summary>
/// <param name="options">The <see cref="RewriteOptions"/>.</param>
/// <param name="statusCode">The status code to add the response.</param>
/// <returns></returns>
public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options, int statusCode)
{
options.Rules.Add(new RedirectToNonWwwRule(statusCode));
return options;
}

/// <summary>
/// Redirect the request to the domain if the incoming request is www.
/// </summary>
/// <param name="options">The <see cref="RewriteOptions"/>.</param>
/// <param name="domains">Limit the rule to apply only on the specified domain(s).</param>
/// <returns></returns>
public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options, params string[] domains)
{
return AddRedirectToNonWww(options, StatusCodes.Status307TemporaryRedirect, domains);
}

/// <summary>
/// Redirect the request to the domain if the incoming request is www.
/// </summary>
/// <param name="options">The <see cref="RewriteOptions"/>.</param>
/// <param name="statusCode">The status code to add the response.</param>
/// <param name="domains">Limit the rule to apply only on the specified domain(s).</param>
/// <returns></returns>
public static RewriteOptions AddRedirectToNonWww(this RewriteOptions options, int statusCode, params string[] domains)
{
options.Rules.Add(new RedirectToNonWwwRule(statusCode, domains));
return options;
}
}
}
105 changes: 105 additions & 0 deletions src/Middleware/Rewrite/test/MiddlewareTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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().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.Null(response.Headers.Location);
}

[Fact]
public async Task CheckIfEmptyStringRedirectCorrectly()
{
Expand Down Expand Up @@ -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 CheckNoRedirectToNonWwwInNonWhitelistedWwwSubdomain(string requestUri)
{
var options = new RewriteOptions().AddRedirectToNonWww("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 CheckRedirectToNonWwwInWhitelistedWwwSubdomain(string requestUri, string redirectUri)
{
var options = new RewriteOptions().AddRedirectToNonWww("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 CheckPermanentRedirectToNonWwwInWhitelistedWwwSubDomains()
{
var options = new RewriteOptions().AddRedirectToNonWwwPermanent("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 CheckRedirectToNonWwwWithStatusCodeInWhitelistedWwwSubDomain(int statusCode)
{
var options = new RewriteOptions().AddRedirectToNonWww(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);
}
}
}