diff options
| author | WizardOfYendor1 <WizardOfYendor1@users.noreply.github.com> | 2026-07-10 14:19:08 -0400 |
|---|---|---|
| committer | WizardOfYendor1 <WizardOfYendor1@users.noreply.github.com> | 2026-07-10 14:19:08 -0400 |
| commit | ce43df6f43b851e7b09dd6b91ed52a3335feb7a2 (patch) | |
| tree | 8fc371c02a9adbaea84a82061caaa884a941d34c | |
| parent | 53aafcd38e1f4558ff18f5258d0d46b3a0565783 (diff) | |
Fix host and port handling for published server URI overrides
| -rw-r--r-- | Emby.Server.Implementations/ApplicationHost.cs | 5 | ||||
| -rw-r--r-- | src/Jellyfin.Networking/Manager/NetworkManager.cs | 49 | ||||
| -rw-r--r-- | tests/Jellyfin.Networking.Tests/NetworkParseTests.cs | 34 |
3 files changed, 71 insertions, 17 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 69e23bcb63..1b37e7498d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -965,8 +965,9 @@ namespace Emby.Server.Implementations /// <inheritdoc/> public string GetLocalApiUrl(string hostname, string scheme = null, int? port = null) { - // If the smartAPI doesn't start with http then treat it as a host or ip. - if (hostname.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + // If the smartAPI isn't already a complete URL then treat it as a host or ip. + if (hostname.StartsWith("http://", StringComparison.OrdinalIgnoreCase) + || hostname.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { return hostname.TrimEnd('/'); } diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs index 4559f68ce8..69f36e9bb1 100644 --- a/src/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs @@ -851,7 +851,7 @@ public class NetworkManager : INetworkManager, IDisposable bool isExternal = !IsInLocalNetwork(source); _logger.LogDebug("Trying to get bind address for source {Source} - External: {IsExternal}", source, isExternal); - if (!skipOverrides && MatchesPublishedServerUrl(source, isExternal, out result)) + if (!skipOverrides && MatchesPublishedServerUrl(source, isExternal, out result, out port)) { return result; } @@ -1017,11 +1017,12 @@ public class NetworkManager : INetworkManager, IDisposable /// <param name="source">IP source address to use.</param> /// <param name="isInExternalSubnet">True if the source is in an external subnet.</param> /// <param name="bindPreference">The published server URL that matches the source address.</param> + /// <param name="port">The explicit port parsed from the override, if any.</param> /// <returns><c>true</c> if a match is found, <c>false</c> otherwise.</returns> - private bool MatchesPublishedServerUrl(IPAddress source, bool isInExternalSubnet, out string bindPreference) + private bool MatchesPublishedServerUrl(IPAddress source, bool isInExternalSubnet, out string bindPreference, out int? port) { bindPreference = string.Empty; - int? port = null; + port = null; // Only consider subnets including the source IP, preferring specific overrides List<PublishedServerUriOverride> validPublishedServerUrls; @@ -1063,25 +1064,43 @@ public class NetworkManager : INetworkManager, IDisposable return false; } - // Handle override specifying port - var parts = bindPreference.Split(':'); - if (parts.Length > 1) + // Handle override specifying an explicit port. + (bindPreference, port) = ParseHostAndPort(bindPreference); + + if (port.HasValue) { - if (int.TryParse(parts[1], out int p)) - { - bindPreference = parts[0]; - port = p; - _logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port); - return true; - } + _logger.LogDebug("{Source}: Matching bind address override found: {Address}:{Port}", source, bindPreference, port); + } + else + { + _logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference); } - - _logger.LogDebug("{Source}: Matching bind address override found: {Address}", source, bindPreference); return true; } /// <summary> + /// Splits a published server URL override into its host and explicit port, if any. + /// Full URLs (containing "://") are returned whole, with any port left embedded. + /// </summary> + /// <param name="value">The override value, e.g. "host:port", "[::1]:port", or a full URL.</param> + /// <returns>The parsed host (or the original value if not split) and the explicit port, if any.</returns> + private static (string Host, int? Port) ParseHostAndPort(string value) + { + if (value.Contains("://", StringComparison.Ordinal)) + { + return (value, null); + } + + if (Uri.TryCreate("any://" + value, UriKind.Absolute, out var parsed) && parsed.Port != -1) + { + return (parsed.DnsSafeHost, parsed.Port); + } + + return (value, null); + } + + /// <summary> /// Attempts to match the source against the user defined bind interfaces. /// </summary> /// <param name="source">IP source address to use.</param> diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 1f523f7f21..3a5b874682 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -493,5 +493,39 @@ namespace Jellyfin.Networking.Tests Assert.Equal(result, interfaceToUse); } + + [Theory] + // Internal override with an explicit port. + [InlineData("192.168.1.1", "192.168.1.0/24=internal.jellyfin:8097", "internal.jellyfin", 8097)] + // External/all override with an explicit port. + [InlineData("8.8.8.8", "all=external.jellyfin:8097", "external.jellyfin", 8097)] + // Bracketed IPv6 override with an explicit port. + [InlineData("8.8.8.8", "all=[fd00:1234::1]:8097", "fd00:1234::1", 8097)] + // Bare IPv6 override without a port - must remain whole, not mangled by the extra colons. + [InlineData("8.8.8.8", "all=fd00:1234::1", "fd00:1234::1", null)] + // Full HTTPS URL override with an explicit port - the URL stays whole, port stays embedded. + [InlineData("8.8.8.8", "all=https://secure.jellyfin.org:8920", "https://secure.jellyfin.org:8920", null)] + // Hostname beginning with "http" is a hostname, not a URL scheme. + [InlineData("8.8.8.8", "all=http-proxy.lan:8097", "http-proxy.lan", 8097)] + public void GetBindAddress_PublishedServerOverride_ParsesHostAndPort(string source, string publishedServers, string expectedHost, int? expectedPort) + { + var conf = new NetworkConfiguration + { + LocalNetworkSubnets = new[] { "192.168.1.0/24" }, + LocalNetworkAddresses = new[] { "eth16", "eth11" }, + EnableIPv4 = true, + PublishedServerUriBySubnet = new[] { publishedServers } + }; + + NetworkManager.MockNetworkSettings = "192.168.1.208/24,-16,eth16|200.200.200.200/24,11,eth11"; + var startupConf = new Mock<IConfiguration>(); + using var nm = new NetworkManager(NetworkParseTests.GetMockConfig(conf), startupConf.Object, new NullLogger<NetworkManager>()); + NetworkManager.MockNetworkSettings = string.Empty; + + var intf = nm.GetBindAddress(IPAddress.Parse(source), out int? port); + + Assert.Equal(expectedHost, intf); + Assert.Equal(expectedPort, port); + } } } |
