[Fixes #567] Adding port and host parsing in HostString
This commit is contained in:
parent
90f71aa6ec
commit
c030ef9129
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using Microsoft.AspNetCore.Http.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http
|
namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
|
|
@ -24,6 +25,31 @@ namespace Microsoft.AspNetCore.Http
|
||||||
_value = value;
|
_value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new HostString from its host and port parts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">The value should be Unicode rather than punycode. IPv6 addresses must use square braces.</param>
|
||||||
|
/// <param name="port">A positive, greater than 0 value representing the port in the host string.</param>
|
||||||
|
public HostString(string host, int port)
|
||||||
|
{
|
||||||
|
if(port <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(port), Resources.Exception_PortMustBeGreaterThanZero);
|
||||||
|
}
|
||||||
|
|
||||||
|
int index;
|
||||||
|
if (host.IndexOf('[') == -1
|
||||||
|
&& (index = host.IndexOf(':')) >= 0
|
||||||
|
&& index < host.Length - 1
|
||||||
|
&& host.IndexOf(':', index + 1) >= 0)
|
||||||
|
{
|
||||||
|
// IPv6 without brackets ::1 is the only type of host with 2 or more colons
|
||||||
|
host = $"[{host}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
_value = host + ":" + port.ToString(CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the original value from the constructor.
|
/// Returns the original value from the constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -37,6 +63,45 @@ namespace Microsoft.AspNetCore.Http
|
||||||
get { return !string.IsNullOrEmpty(_value); }
|
get { return !string.IsNullOrEmpty(_value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the value of the host part of the value. The port is removed if it was present.
|
||||||
|
/// IPv6 addresses will have brackets added if they are missing.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string Host
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string host, port;
|
||||||
|
|
||||||
|
GetParts(out host, out port);
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the value of the port part of the host, or <value>null</value> if none is found.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int? Port
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string host, port;
|
||||||
|
int p;
|
||||||
|
|
||||||
|
GetParts(out host, out port);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(port) || !int.TryParse(port, out p))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the value as normalized by ToUriComponent().
|
/// Returns the value as normalized by ToUriComponent().
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -53,35 +118,25 @@ namespace Microsoft.AspNetCore.Http
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public string ToUriComponent()
|
public string ToUriComponent()
|
||||||
{
|
{
|
||||||
int index;
|
|
||||||
if (string.IsNullOrEmpty(_value))
|
if (string.IsNullOrEmpty(_value))
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
else if (_value.IndexOf('[') >= 0)
|
|
||||||
{
|
string host, port;
|
||||||
// IPv6 in brackets [::1], maybe with port
|
|
||||||
return _value;
|
GetParts(out host, out port);
|
||||||
}
|
|
||||||
else if ((index = _value.IndexOf(':')) >= 0
|
if (host.IndexOf('[') == -1)
|
||||||
&& index < _value.Length - 1
|
{
|
||||||
&& _value.IndexOf(':', index + 1) >= 0)
|
|
||||||
{
|
|
||||||
// IPv6 without brackets ::1 is the only type of host with 2 or more colons
|
|
||||||
return $"[{_value}]";
|
|
||||||
}
|
|
||||||
else if (index >= 0)
|
|
||||||
{
|
|
||||||
// Has a port
|
|
||||||
string port = _value.Substring(index);
|
|
||||||
var mapping = new IdnMapping();
|
var mapping = new IdnMapping();
|
||||||
return mapping.GetAscii(_value, 0, index) + port;
|
host = mapping.GetAscii(host);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var mapping = new IdnMapping();
|
|
||||||
return mapping.GetAscii(_value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return string.IsNullOrEmpty(port)
|
||||||
|
? host
|
||||||
|
: string.Concat(host, ":", port);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -197,5 +252,49 @@ namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
return !left.Equals(right);
|
return !left.Equals(right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the current value. IPv6 addresses will have brackets added if they are missing.
|
||||||
|
/// </summary>
|
||||||
|
private void GetParts(out string host, out string port)
|
||||||
|
{
|
||||||
|
int index;
|
||||||
|
port = null;
|
||||||
|
host = null;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if ((index = _value.IndexOf(']')) >= 0)
|
||||||
|
{
|
||||||
|
// IPv6 in brackets [::1], maybe with port
|
||||||
|
host = _value.Substring(0, index + 1);
|
||||||
|
|
||||||
|
if ((index = _value.IndexOf(':', index + 1)) >= 0)
|
||||||
|
{
|
||||||
|
port = _value.Substring(index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((index = _value.IndexOf(':')) >= 0
|
||||||
|
&& index < _value.Length - 1
|
||||||
|
&& _value.IndexOf(':', index + 1) >= 0)
|
||||||
|
{
|
||||||
|
// IPv6 without brackets ::1 is the only type of host with 2 or more colons
|
||||||
|
host = $"[{_value}]";
|
||||||
|
port = null;
|
||||||
|
}
|
||||||
|
else if (index >= 0)
|
||||||
|
{
|
||||||
|
// Has a port
|
||||||
|
host = _value.Substring(0, index);
|
||||||
|
port = _value.Substring(index + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
host = _value;
|
||||||
|
port = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,22 @@ namespace Microsoft.AspNetCore.Http.Abstractions
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_InvokeDoesNotSupportRefOrOutParams"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_InvokeDoesNotSupportRefOrOutParams"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value must be greater than zero.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Exception_PortMustBeGreaterThanZero
|
||||||
|
{
|
||||||
|
get { return GetString("Exception_PortMustBeGreaterThanZero"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value must be greater than zero.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatException_PortMustBeGreaterThanZero()
|
||||||
|
{
|
||||||
|
return GetString("Exception_PortMustBeGreaterThanZero");
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -141,4 +141,7 @@
|
||||||
<data name="Exception_InvokeDoesNotSupportRefOrOutParams" xml:space="preserve">
|
<data name="Exception_InvokeDoesNotSupportRefOrOutParams" xml:space="preserve">
|
||||||
<value>The '{0}' method must not have ref or out parameters.</value>
|
<value>The '{0}' method must not have ref or out parameters.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Exception_PortMustBeGreaterThanZero" xml:space="preserve">
|
||||||
|
<value>The value must be greater than zero.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Testing;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Http
|
||||||
|
{
|
||||||
|
public class HostStringTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0)]
|
||||||
|
[InlineData(-1)]
|
||||||
|
public void CtorThrows_IfPortIsNotGreaterThanZero(int port)
|
||||||
|
{
|
||||||
|
// Act and Assert
|
||||||
|
ExceptionAssert.ThrowsArgumentOutOfRange(() => new HostString("localhost", port), "port", "The value must be greater than zero.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("localhost", "localhost")]
|
||||||
|
[InlineData("1.2.3.4", "1.2.3.4")]
|
||||||
|
[InlineData("[2001:db8:a0b:12f0::1]", "[2001:db8:a0b:12f0::1]")]
|
||||||
|
[InlineData("本地主機", "本地主機")]
|
||||||
|
[InlineData("localhost:5000", "localhost")]
|
||||||
|
[InlineData("1.2.3.4:5000", "1.2.3.4")]
|
||||||
|
[InlineData("[2001:db8:a0b:12f0::1]:5000", "[2001:db8:a0b:12f0::1]")]
|
||||||
|
[InlineData("本地主機:5000", "本地主機")]
|
||||||
|
public void Domain_ExtractsHostFromValue(string sourceValue, string expectedDomain)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var hostString = new HostString(sourceValue);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = hostString.Host;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedDomain, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("localhost", null)]
|
||||||
|
[InlineData("1.2.3.4", null)]
|
||||||
|
[InlineData("[2001:db8:a0b:12f0::1]", null)]
|
||||||
|
[InlineData("本地主機", null)]
|
||||||
|
[InlineData("localhost:5000", 5000)]
|
||||||
|
[InlineData("1.2.3.4:5000", 5000)]
|
||||||
|
[InlineData("[2001:db8:a0b:12f0::1]:5000", 5000)]
|
||||||
|
[InlineData("本地主機:5000", 5000)]
|
||||||
|
public void Port_ExtractsPortFromValue(string sourceValue, int? expectedPort)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var hostString = new HostString(sourceValue);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = hostString.Port;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedPort, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("localhost:BLAH")]
|
||||||
|
public void Port_ExtractsInvalidPortFromValue(string sourceValue)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var hostString = new HostString(sourceValue);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = hostString.Port;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(null, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("localhost", 5000, "localhost", 5000)]
|
||||||
|
[InlineData("1.2.3.4", 5000, "1.2.3.4", 5000)]
|
||||||
|
[InlineData("[2001:db8:a0b:12f0::1]", 5000, "[2001:db8:a0b:12f0::1]", 5000)]
|
||||||
|
[InlineData("2001:db8:a0b:12f0::1", 5000, "[2001:db8:a0b:12f0::1]", 5000)]
|
||||||
|
[InlineData("本地主機", 5000, "本地主機", 5000)]
|
||||||
|
public void Ctor_CreatesFromHostAndPort(string sourceHost, int sourcePort, string expectedHost, int expectedPort)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var hostString = new HostString(sourceHost, sourcePort);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var host = hostString.Host;
|
||||||
|
var port = hostString.Port;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedHost, host);
|
||||||
|
Assert.Equal(expectedPort, port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue