diff --git a/src/Middleware/Rewrite/src/RedirectToHttpsRule.cs b/src/Middleware/Rewrite/src/RedirectToHttpsRule.cs index 4231a5bd9415..a26b2c30e17b 100644 --- a/src/Middleware/Rewrite/src/RedirectToHttpsRule.cs +++ b/src/Middleware/Rewrite/src/RedirectToHttpsRule.cs @@ -1,8 +1,8 @@ // 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.Text; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Rewrite.Logging; using Microsoft.Net.Http.Headers; @@ -18,10 +18,11 @@ public virtual void ApplyRule(RewriteContext context) if (!context.HttpContext.Request.IsHttps) { var host = context.HttpContext.Request.Host; - if (SSLPort.HasValue && SSLPort.Value > 0) + int port; + if (SSLPort.HasValue && (port = SSLPort.GetValueOrDefault()) > 0) { // a specific SSL port is specified - host = new HostString(host.Host, SSLPort.Value); + host = new HostString(host.Host, port); } else { @@ -30,10 +31,10 @@ public virtual void ApplyRule(RewriteContext context) } var req = context.HttpContext.Request; - var newUrl = new StringBuilder().Append("https://").Append(host).Append(req.PathBase).Append(req.Path).Append(req.QueryString); + var newUrl = UriHelper.BuildAbsolute("https", host, req.PathBase, req.Path, req.QueryString, default); var response = context.HttpContext.Response; response.StatusCode = StatusCode; - response.Headers[HeaderNames.Location] = newUrl.ToString(); + response.Headers[HeaderNames.Location] = newUrl; context.Result = RuleResult.EndResponse; context.Logger.RedirectedToHttps(); } diff --git a/src/Middleware/Rewrite/test/MiddlewareTests.cs b/src/Middleware/Rewrite/test/MiddlewareTests.cs index 21fb4bdef8f6..dbffe3e4e3ad 100644 --- a/src/Middleware/Rewrite/test/MiddlewareTests.cs +++ b/src/Middleware/Rewrite/test/MiddlewareTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -128,7 +129,7 @@ public async Task CheckRedirectPathWithQueryString() [InlineData(StatusCodes.Status302Found)] [InlineData(StatusCodes.Status307TemporaryRedirect)] [InlineData(StatusCodes.Status308PermanentRedirect)] - public async Task CheckRedirectToHttps(int statusCode) + public async Task CheckRedirectToHttpsStatus(int statusCode) { var options = new RewriteOptions().AddRedirectToHttps(statusCode: statusCode); using var host = new HostBuilder() @@ -152,6 +153,78 @@ public async Task CheckRedirectToHttps(int statusCode) Assert.Equal(statusCode, (int)response.StatusCode); } + [Theory] + [InlineData(null)] + [InlineData(123)] + public async Task CheckRedirectToHttpsSslPort(int? sslPort) + { + var options = new RewriteOptions().AddRedirectToHttps(statusCode: StatusCodes.Status302Found, sslPort: sslPort); + using var host = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseTestServer() + .Configure(app => + { + app.UseRewriter(options); + }); + }).Build(); + + await host.StartAsync(); + + var server = host.GetTestServer(); + + var response = await server.CreateClient().GetAsync(new Uri("http://example.com")); + + if (sslPort.HasValue) + { + Assert.Equal($"https://example.com:{sslPort.GetValueOrDefault().ToString(CultureInfo.InvariantCulture)}/", response.Headers.Location.OriginalString); + } + else + { + Assert.Equal("https://example.com/", response.Headers.Location.OriginalString); + } + } + + [Theory] + [InlineData(null, "example.com", "example.com/")] + [InlineData(null, "example.com/path", "example.com/path")] + [InlineData(null, "example.com/path?name=value", "example.com/path?name=value")] + [InlineData(null, "hoψst.com", "xn--host-cpd.com/")] + [InlineData(null, "hoψst.com/path", "xn--host-cpd.com/path")] + [InlineData(null, "hoψst.com/path?name=value", "xn--host-cpd.com/path?name=value")] + [InlineData(null, "example.com/pãth", "example.com/p%C3%A3th")] + [InlineData(null, "example.com/path?näme=valüe", "example.com/path?n%C3%A4me=val%C3%BCe")] + [InlineData("example.com/pathBase", "example.com/pathBase/path", "example.com/pathBase/path")] + [InlineData("example.com/pathBase", "example.com/pathBase", "example.com/pathBase")] + [InlineData("example.com/pâthBase", "example.com/pâthBase/path", "example.com/p%C3%A2thBase/path")] + public async Task CheckRedirectToHttpsUrl(string baseAddress, string hostPathAndQuery, string expectedHostPathAndQuery) + { + var options = new RewriteOptions().AddRedirectToHttps(); + using var host = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseTestServer() + .Configure(app => + { + app.UseRewriter(options); + }); + }).Build(); + + await host.StartAsync(); + + var server = host.GetTestServer(); + if (!string.IsNullOrEmpty(baseAddress)) + { + server.BaseAddress = new Uri("http://" + baseAddress); + } + + var response = await server.CreateClient().GetAsync(new Uri("http://" + hostPathAndQuery)); + + Assert.Equal("https://" + expectedHostPathAndQuery, response.Headers.Location.OriginalString); + } + [Fact] public async Task CheckPermanentRedirectToHttps() {