Normalize internationalized domain names when adding to CORS
Fixes https://github.com/aspnet/Home/issues/3353
This commit is contained in:
parent
e8caccf009
commit
6f76189846
|
|
@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||||
/// Creates a new instance of the <see cref="CorsPolicyBuilder"/>.
|
/// Creates a new instance of the <see cref="CorsPolicyBuilder"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="origins">list of origins which can be added.</param>
|
/// <param name="origins">list of origins which can be added.</param>
|
||||||
|
/// <remarks> <see cref="WithOrigins(string[])"/> for details on normalizing the origin value.</remarks>
|
||||||
public CorsPolicyBuilder(params string[] origins)
|
public CorsPolicyBuilder(params string[] origins)
|
||||||
{
|
{
|
||||||
WithOrigins(origins);
|
WithOrigins(origins);
|
||||||
|
|
@ -36,16 +37,55 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="origins">The origins that are allowed.</param>
|
/// <param name="origins">The origins that are allowed.</param>
|
||||||
/// <returns>The current policy builder.</returns>
|
/// <returns>The current policy builder.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method normalizes the origin value prior to adding it to <see cref="CorsPolicy.Origins"/> to match
|
||||||
|
/// the normalization performed by the browser on the value sent in the <c>ORIGIN</c> header.
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>
|
||||||
|
/// If the specified origin has an internationalized domain name (IDN), the punycoded value is used. If the origin
|
||||||
|
/// specifies a default port (e.g. 443 for HTTPS or 80 for HTTP), this will be dropped as part of normalization.
|
||||||
|
/// Finally, the scheme and punycoded host name are culture invariant lower cased before being added to the <see cref="CorsPolicy.Origins"/>
|
||||||
|
/// collection.
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// For all other origins, normalization involves performing a culture invariant lower casing of the host name.
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
public CorsPolicyBuilder WithOrigins(params string[] origins)
|
public CorsPolicyBuilder WithOrigins(params string[] origins)
|
||||||
{
|
{
|
||||||
foreach (var origin in origins)
|
foreach (var origin in origins)
|
||||||
{
|
{
|
||||||
_policy.Origins.Add(origin.ToLowerInvariant());
|
var normalizedOrigin = GetNormalizedOrigin(origin);
|
||||||
|
_policy.Origins.Add(normalizedOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static string GetNormalizedOrigin(string origin)
|
||||||
|
{
|
||||||
|
if (Uri.TryCreate(origin, UriKind.Absolute, out var uri) &&
|
||||||
|
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) &&
|
||||||
|
!string.Equals(uri.IdnHost, uri.Host, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var builder = new UriBuilder(uri.Scheme.ToLowerInvariant(), uri.IdnHost.ToLowerInvariant());
|
||||||
|
if (!uri.IsDefaultPort)
|
||||||
|
{
|
||||||
|
// Uri does not have a way to differentiate between a port value inferred by default (e.g. Port = 80 for http://www.example.com) and
|
||||||
|
// a default port value that is specified (e.g. Port = 80 for http://www.example.com:80). Although the HTTP or FETCH spec does not say
|
||||||
|
// anything about including the default port as part of the Origin header, at the time of writing, browsers drop "default" port when navigating
|
||||||
|
// and when sending the Origin header. All this goes to say, it appears OK to drop an explicitly specified port,
|
||||||
|
// if it is the default port when working with an IDN host.
|
||||||
|
builder.Port = uri.Port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
|
||||||
|
}
|
||||||
|
|
||||||
|
return origin.ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the specified <paramref name="headers"/> to the policy.
|
/// Adds the specified <paramref name="headers"/> to the policy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -222,4 +262,4 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -299,5 +299,85 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
||||||
var corsPolicy = builder.Build();
|
var corsPolicy = builder.Build();
|
||||||
Assert.False(corsPolicy.SupportsCredentials);
|
Assert.False(corsPolicy.SupportsCredentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Some-String", "some-string")]
|
||||||
|
[InlineData("x:\\Test", "x:\\test")]
|
||||||
|
[InlineData("FTP://Some-url", "ftp://some-url")]
|
||||||
|
public void GetNormalizedOrigin_ReturnsLowerCasedValue_IfStringIsNotHttpOrHttpsUrl(string origin, string expected)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, normalizedOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetNormalizedOrigin_DoesNotAddPort_IfUriDoesNotSpecifyOne()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var origin = "http://www.example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(origin, normalizedOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetNormalizedOrigin_LowerCasesScheme()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var origin = "HTTP://www.example.com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("http://www.example.com", normalizedOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetNormalizedOrigin_LowerCasesHost()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var origin = "http://www.Example.Com";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("http://www.example.com", normalizedOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("http://www.Example.com:80", "http://www.example.com:80")]
|
||||||
|
[InlineData("https://www.Example.com:8080", "https://www.example.com:8080")]
|
||||||
|
public void GetNormalizedOrigin_PreservesPort_ForNonIdnHosts(string origin, string expected)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, normalizedOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("http://Bücher.example", "http://xn--bcher-kva.example")]
|
||||||
|
[InlineData("http://Bücher.example.com:83", "http://xn--bcher-kva.example.com:83")]
|
||||||
|
[InlineData("https://example.қаз", "https://example.xn--80ao21a")]
|
||||||
|
[InlineData("http://😉.fm", "http://xn--n28h.fm")]
|
||||||
|
// Note that in following case, the default port (443 for HTTPS) is not preserved.
|
||||||
|
[InlineData("https://www.example.இந்தியா:443", "https://www.example.xn--xkc2dl3a5ee0h")]
|
||||||
|
public void GetNormalizedOrigin_ReturnsPunyCodedOrigin(string origin, string expected)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var normalizedOrigin = CorsPolicyBuilder.GetNormalizedOrigin(origin);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, normalizedOrigin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue