Fixed IP6 host parsing

This commit is contained in:
BaronGreenback 2021-02-07 17:42:23 +00:00
parent 34e10622c6
commit 053063fd47
2 changed files with 84 additions and 85 deletions

View File

@ -128,62 +128,62 @@ namespace MediaBrowser.Common.Net
/// <returns><c>true</c> if the parsing is successful, <c>false</c> if not.</returns> /// <returns><c>true</c> if the parsing is successful, <c>false</c> if not.</returns>
public static bool TryParse(string host, out IPHost hostObj) public static bool TryParse(string host, out IPHost hostObj)
{ {
if (!string.IsNullOrEmpty(host)) if (string.IsNullOrWhiteSpace(host))
{ {
// See if it's an IPv6 with port address e.g. [::1]:120. hostObj = IPHost.None;
int i = host.IndexOf("]:", StringComparison.OrdinalIgnoreCase); return false;
if (i != -1) }
// See if it's an IPv6 with port address e.g. [::1] or [::1]:120.
int i = host.IndexOf("]", StringComparison.OrdinalIgnoreCase);
if (i != -1)
{
return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj);
}
if (IPNetAddress.TryParse(host, out var netAddress))
{
// Host name is an ip address, so fake resolve.
hostObj = new IPHost(host, netAddress.Address);
return true;
}
// Is it a host, IPv4/6 with/out port?
string[] hosts = host.Split(':');
if (hosts.Length <= 2)
{
// This is either a hostname: port, or an IP4:port.
host = hosts[0];
if (string.Equals("localhost", host, StringComparison.OrdinalIgnoreCase))
{ {
return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj); hostObj = new IPHost(host);
} return true;
else
{
// See if it's an IPv6 in [] with no port.
i = host.IndexOf(']', StringComparison.OrdinalIgnoreCase);
if (i != -1)
{
return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj);
}
// Is it a host or IPv4 with port?
string[] hosts = host.Split(':');
if (hosts.Length > 2)
{
hostObj = new IPHost(string.Empty, IPAddress.None);
return false;
}
// Remove port from IPv4 if it exists.
host = hosts[0];
if (string.Equals("localhost", host, StringComparison.OrdinalIgnoreCase))
{
hostObj = new IPHost(host, new IPAddress(Ipv4Loopback));
return true;
}
if (IPNetAddress.TryParse(host, out IPNetAddress netIP))
{
// Host name is an ip address, so fake resolve.
hostObj = new IPHost(host, netIP.Address);
return true;
}
} }
// Only thing left is to see if it's a host string. if (IPAddress.TryParse(host, out var netIP))
if (!string.IsNullOrEmpty(host))
{ {
// Use regular expression as CheckHostName isn't RFC5892 compliant. // Host name is an ip address, so fake resolve.
// Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation hostObj = new IPHost(host, netIP);
Regex re = new Regex(@"^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)$", RegexOptions.IgnoreCase | RegexOptions.Multiline); return true;
if (re.Match(host).Success)
{
hostObj = new IPHost(host);
return true;
}
} }
} }
else
{
// Invalid host name, as it cannot contain :
hostObj = new IPHost(string.Empty, IPAddress.None);
return false;
}
// Use regular expression as CheckHostName isn't RFC5892 compliant.
// Modified from gSkinner's expression at https://stackoverflow.com/questions/11809631/fully-qualified-domain-name-validation
Regex re = new Regex(@"^(?!:\/\/)(?=.{1,255}$)((.{1,63}\.){0,127}(?![0-9]*$)[a-z0-9-]+\.?)$", RegexOptions.IgnoreCase | RegexOptions.Multiline);
if (re.Match(host).Success)
{
hostObj = new IPHost(host);
return true;
}
hostObj = IPHost.None; hostObj = IPHost.None;
return false; return false;
@ -344,10 +344,14 @@ namespace MediaBrowser.Common.Net
{ {
output += "Any Address,"; output += "Any Address,";
} }
else else if (i.AddressFamily == AddressFamily.InterNetwork)
{ {
output += $"{i}/32,"; output += $"{i}/32,";
} }
else
{
output += $"{i}/128,";
}
} }
output = output[0..^1]; output = output[0..^1];

View File

@ -13,34 +13,6 @@ namespace Jellyfin.Networking.Tests
{ {
public class NetworkParseTests public class NetworkParseTests
{ {
/// <summary>
/// Tries to identify the string and return an object of that class.
/// </summary>
/// <param name="addr">String to parse.</param>
/// <param name="result">IPObject to return.</param>
/// <returns>True if the value parsed successfully.</returns>
private static bool TryParse(string addr, out IPObject result)
{
if (!string.IsNullOrEmpty(addr))
{
// Is it an IP address
if (IPNetAddress.TryParse(addr, out IPNetAddress nw))
{
result = nw;
return true;
}
if (IPHost.TryParse(addr, out IPHost h))
{
result = h;
return true;
}
}
result = IPNetAddress.None;
return false;
}
private static IConfigurationManager GetMockConfig(NetworkConfiguration conf) private static IConfigurationManager GetMockConfig(NetworkConfiguration conf)
{ {
var configManager = new Mock<IConfigurationManager> var configManager = new Mock<IConfigurationManager>
@ -118,11 +90,33 @@ namespace Jellyfin.Networking.Tests
[InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]:124")] [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]:124")]
[InlineData("fe80::7add:12ff:febb:c67b%16")] [InlineData("fe80::7add:12ff:febb:c67b%16")]
[InlineData("[fe80::7add:12ff:febb:c67b%16]:123")] [InlineData("[fe80::7add:12ff:febb:c67b%16]:123")]
[InlineData("fe80::7add:12ff:febb:c67b%16:123")]
[InlineData("[fe80::7add:12ff:febb:c67b%16]")]
[InlineData("192.168.1.2/255.255.255.0")]
[InlineData("192.168.1.2/24")]
public void ValidHostStrings(string address)
{
Assert.True(IPHost.TryParse(address, out _));
}
/// <summary>
/// Checks IP address formats.
/// </summary>
/// <param name="address"></param>
[Theory]
[InlineData("127.0.0.1")]
[InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")]
[InlineData("fd23:184f:2029:0:3139:7386:67d7:d517/56")]
[InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517]")]
[InlineData("fe80::7add:12ff:febb:c67b%16")]
[InlineData("[fe80::7add:12ff:febb:c67b%16]:123")]
[InlineData("fe80::7add:12ff:febb:c67b%16:123")]
[InlineData("[fe80::7add:12ff:febb:c67b%16]")]
[InlineData("192.168.1.2/255.255.255.0")] [InlineData("192.168.1.2/255.255.255.0")]
[InlineData("192.168.1.2/24")] [InlineData("192.168.1.2/24")]
public void ValidIPStrings(string address) public void ValidIPStrings(string address)
{ {
Assert.True(TryParse(address, out _)); Assert.True(IPNetAddress.TryParse(address, out _));
} }
@ -138,7 +132,8 @@ namespace Jellyfin.Networking.Tests
[InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517:1231]")] [InlineData("[fd23:184f:2029:0:3139:7386:67d7:d517:1231]")]
public void InvalidAddressString(string address) public void InvalidAddressString(string address)
{ {
Assert.False(TryParse(address, out _)); Assert.False(IPNetAddress.TryParse(address, out _));
Assert.False(IPHost.TryParse(address, out _));
} }
@ -172,11 +167,11 @@ namespace Jellyfin.Networking.Tests
"[]")] "[]")]
[InlineData( [InlineData(
"192.158.1.2/16, localhost, fd23:184f:2029:0:3139:7386:67d7:d517, !10.10.10.10", "192.158.1.2/16, localhost, fd23:184f:2029:0:3139:7386:67d7:d517, !10.10.10.10",
"[192.158.1.2/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]", "[192.158.1.2/16,[127.0.0.1/32,::1/128],fd23:184f:2029:0:3139:7386:67d7:d517/128]",
"[192.158.1.2/16,127.0.0.1/32]", "[192.158.1.2/16,127.0.0.1/32]",
"[10.10.10.10/32]", "[10.10.10.10/32]",
"[10.10.10.10/32]", "[10.10.10.10/32]",
"[192.158.0.0/16,127.0.0.1/32,fd23:184f:2029:0:3139:7386:67d7:d517/128]")] "[192.158.0.0/16,127.0.0.1/32,::1/128,fd23:184f:2029:0:3139:7386:67d7:d517/128]")]
[InlineData("192.158.1.2/255.255.0.0,192.169.1.2/8", [InlineData("192.158.1.2/255.255.0.0,192.169.1.2/8",
"[192.158.1.2/16,192.169.1.2/8]", "[192.158.1.2/16,192.169.1.2/8]",
"[192.158.1.2/16,192.169.1.2/8]", "[192.158.1.2/16,192.169.1.2/8]",
@ -333,8 +328,8 @@ namespace Jellyfin.Networking.Tests
public void TestSubnetContains(string network, string ip) public void TestSubnetContains(string network, string ip)
{ {
Assert.True(TryParse(network, out IPObject? networkObj)); Assert.True(IPNetAddress.TryParse(network, out var networkObj));
Assert.True(TryParse(ip, out IPObject? ipObj)); Assert.True(IPNetAddress.TryParse(ip, out var ipObj));
Assert.True(networkObj.Contains(ipObj)); Assert.True(networkObj.Contains(ipObj));
} }
@ -468,7 +463,7 @@ namespace Jellyfin.Networking.Tests
// User on internal network, no binding specified - so result is the 1st internal. // User on internal network, no binding specified - so result is the 1st internal.
[InlineData("192.168.1.1", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")] [InlineData("192.168.1.1", "192.168.1.0/24", "", false, "0.0.0.0=http://helloworld.com", "eth16")]
// User on external network, internal binding only - so asumption is a proxy forward, return external override. // User on external network, internal binding only - so assumption is a proxy forward, return external override.
[InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")] [InlineData("jellyfin.org", "192.168.1.0/24", "eth16", false, "0.0.0.0=http://helloworld.com", "http://helloworld.com")]
// User on external network, no binding - so result is the 1st external which is overriden. // User on external network, no binding - so result is the 1st external which is overriden.