diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Jellyfin.Drawing.Skia/SkiaEncoder.cs | 2 | ||||
| -rw-r--r-- | src/Jellyfin.Extensions/StreamExtensions.cs | 7 | ||||
| -rw-r--r-- | src/Jellyfin.LiveTv/Timers/TimerManager.cs | 2 | ||||
| -rw-r--r-- | src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs | 2 | ||||
| -rw-r--r-- | src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs | 2 | ||||
| -rw-r--r-- | src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs | 55 | ||||
| -rw-r--r-- | src/Jellyfin.Networking/Manager/NetworkManager.cs | 47 |
7 files changed, 64 insertions, 53 deletions
diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 75963226a..ede93aaa5 100644 --- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -219,7 +219,7 @@ public class SkiaEncoder : IImageEncoder return path; } - var tempPath = Path.Combine(_appPaths.TempDirectory, string.Concat(Guid.NewGuid().ToString(), Path.GetExtension(path.AsSpan()))); + var tempPath = Path.Join(_appPaths.TempDirectory, string.Concat("skia_", Guid.NewGuid().ToString(), Path.GetExtension(path.AsSpan()))); var directory = Path.GetDirectoryName(tempPath) ?? throw new ResourceNotFoundException($"Provided path ({tempPath}) is not valid."); Directory.CreateDirectory(directory); File.Copy(path, tempPath, true); diff --git a/src/Jellyfin.Extensions/StreamExtensions.cs b/src/Jellyfin.Extensions/StreamExtensions.cs index 182996852..0cfac384e 100644 --- a/src/Jellyfin.Extensions/StreamExtensions.cs +++ b/src/Jellyfin.Extensions/StreamExtensions.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; +using System.Threading; namespace Jellyfin.Extensions { @@ -48,11 +50,12 @@ namespace Jellyfin.Extensions /// Reads all lines in the <see cref="TextReader" />. /// </summary> /// <param name="reader">The <see cref="TextReader" /> to read from.</param> + /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <returns>All lines in the stream.</returns> - public static async IAsyncEnumerable<string> ReadAllLinesAsync(this TextReader reader) + public static async IAsyncEnumerable<string> ReadAllLinesAsync(this TextReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) { string? line; - while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) is not null) + while ((line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false)) is not null) { yield return line; } diff --git a/src/Jellyfin.LiveTv/Timers/TimerManager.cs b/src/Jellyfin.LiveTv/Timers/TimerManager.cs index da5deea36..1cf335159 100644 --- a/src/Jellyfin.LiveTv/Timers/TimerManager.cs +++ b/src/Jellyfin.LiveTv/Timers/TimerManager.cs @@ -22,7 +22,7 @@ namespace Jellyfin.LiveTv.Timers public TimerManager(ILogger<TimerManager> logger, IConfigurationManager config) : base( logger, - Path.Combine(config.CommonApplicationPaths.DataPath, "livetv"), + Path.Combine(config.CommonApplicationPaths.DataPath, "livetv/timers.json"), (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) { } diff --git a/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs b/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs index afc2e4f9c..aba9627ba 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs @@ -219,7 +219,7 @@ namespace Jellyfin.LiveTv.TunerHosts } } - throw new LiveTvConflictException(); + throw new LiveTvConflictException("Unable to find host to play channel"); } protected virtual bool IsValidChannelId(string channelId) diff --git a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 861338727..1dd35da41 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -145,7 +145,7 @@ namespace Jellyfin.LiveTv.TunerHosts.HdHomerun } _activeTuner = -1; - throw new LiveTvConflictException(); + throw new LiveTvConflictException("No tuners available"); } public async Task ChangeChannel(IHdHomerunChannelCommands commands, CancellationToken cancellationToken) diff --git a/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs index b78b60c70..365f0188d 100644 --- a/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs +++ b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs @@ -30,23 +30,8 @@ namespace Jellyfin.LiveTv.TunerHosts { public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost { - private static readonly string[] _disallowedMimeTypes = - { - "video/x-matroska", - "video/mp4", - "application/vnd.apple.mpegurl", - "application/mpegurl", - "application/x-mpegurl", - "video/vnd.mpeg.dash.mpd" - }; - - private static readonly string[] _disallowedSharedStreamExtensions = - { - ".mkv", - ".mp4", - ".m3u8", - ".mpd" - }; + private static readonly string[] _mimeTypesCanShareHttpStream = ["video/MP2T"]; + private static readonly string[] _extensionsCanShareHttpStream = [".ts", ".tsv", ".m2t"]; private readonly IHttpClientFactory _httpClientFactory; private readonly IServerApplicationHost _appHost; @@ -111,31 +96,33 @@ namespace Jellyfin.LiveTv.TunerHosts if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping) { - using var message = new HttpRequestMessage(HttpMethod.Head, mediaSource.Path); - using var response = await _httpClientFactory.CreateClient(NamedClient.Default) - .SendAsync(message, cancellationToken) - .ConfigureAwait(false); + var extension = Path.GetExtension(new UriBuilder(mediaSource.Path).Path); - if (response.IsSuccessStatusCode) + if (string.IsNullOrEmpty(extension)) { - if (!_disallowedMimeTypes.Contains(response.Content.Headers.ContentType?.ToString(), StringComparison.OrdinalIgnoreCase)) + try { - return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); + using var message = new HttpRequestMessage(HttpMethod.Head, mediaSource.Path); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .SendAsync(message, cancellationToken) + .ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + if (_mimeTypesCanShareHttpStream.Contains(response.Content.Headers.ContentType?.MediaType, StringComparison.OrdinalIgnoreCase)) + { + return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); + } + } } - } - else if (response.StatusCode == HttpStatusCode.MethodNotAllowed || response.StatusCode == HttpStatusCode.NotImplemented) - { - // Fallback to check path extension when the server does not support HEAD method - // Use UriBuilder to remove all query string as GetExtension will include them when used directly - var extension = Path.GetExtension(new UriBuilder(mediaSource.Path).Path); - if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + catch (Exception) { - return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); + Logger.LogWarning("HEAD request to check MIME type failed, shared stream disabled"); } } - else + else if (_extensionsCanShareHttpStream.Contains(extension, StringComparison.OrdinalIgnoreCase)) { - response.EnsureSuccessStatusCode(); + return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); } } diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs index 37160b3ba..cf6a2cc55 100644 --- a/src/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs @@ -11,7 +11,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Model.Net; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; @@ -237,7 +236,7 @@ public class NetworkManager : INetworkManager, IDisposable var mac = adapter.GetPhysicalAddress(); // Populate MAC list - if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && PhysicalAddress.None.Equals(mac)) + if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && !PhysicalAddress.None.Equals(mac)) { macAddresses.Add(mac); } @@ -739,7 +738,9 @@ public class NetworkManager : INetworkManager, IDisposable /// <inheritdoc/> public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false) { - if (_interfaces.Count > 0 || individualInterfaces) + var config = _configurationManager.GetNetworkConfiguration(); + var localNetworkAddresses = config.LocalNetworkAddresses; + if ((localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0]) && _interfaces.Count > 0) || individualInterfaces) { return _interfaces; } @@ -902,15 +903,30 @@ public class NetworkManager : INetworkManager, IDisposable return false; } + /// <summary> + /// Get if the IPAddress is Link-local. + /// </summary> + /// <param name="address">The IP Address.</param> + /// <returns>Bool indicates if the address is link-local.</returns> + public bool IsLinkLocalAddress(IPAddress address) + { + ArgumentNullException.ThrowIfNull(address); + return NetworkConstants.IPv4RFC3927LinkLocal.Contains(address) || address.IsIPv6LinkLocal; + } + /// <inheritdoc/> public bool IsInLocalNetwork(IPAddress address) { ArgumentNullException.ThrowIfNull(address); - // See conversation at https://github.com/jellyfin/jellyfin/pull/3515. + // Map IPv6 mapped IPv4 back to IPv4 (happens if Kestrel runs in dual-socket mode) + if (address.IsIPv4MappedToIPv6) + { + address = address.MapToIPv4(); + } + if ((TrustAllIPv6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6) - || address.Equals(IPAddress.Loopback) - || address.Equals(IPAddress.IPv6Loopback)) + || IPAddress.IsLoopback(address)) { return true; } @@ -1083,7 +1099,11 @@ public class NetworkManager : INetworkManager, IDisposable private bool MatchesExternalInterface(IPAddress source, out string result) { // Get the first external interface address that isn't a loopback. - var extResult = _interfaces.Where(p => !IsInLocalNetwork(p.Address)).OrderBy(x => x.Index).ToArray(); + var extResult = _interfaces + .Where(p => !IsInLocalNetwork(p.Address)) + .Where(p => p.Address.AddressFamily.Equals(source.AddressFamily)) + .Where(p => !IsLinkLocalAddress(p.Address)) + .OrderBy(x => x.Index).ToArray(); // No external interface found if (extResult.Length == 0) @@ -1116,12 +1136,13 @@ public class NetworkManager : INetworkManager, IDisposable var logLevel = debug ? LogLevel.Debug : LogLevel.Information; if (_logger.IsEnabled(logLevel)) { - _logger.Log(logLevel, "Defined LAN addresses: {0}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength)); - _logger.Log(logLevel, "Defined LAN exclusions: {0}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength)); - _logger.Log(logLevel, "Using LAN addresses: {0}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength)); - _logger.Log(logLevel, "Using bind addresses: {0}", _interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address)); - _logger.Log(logLevel, "Remote IP filter is {0}", config.IsRemoteIPFilterBlacklist ? "Blocklist" : "Allowlist"); - _logger.Log(logLevel, "Filter list: {0}", _remoteAddressFilter.Select(s => s.Prefix + "/" + s.PrefixLength)); + _logger.Log(logLevel, "Defined LAN subnets: {Subnets}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength)); + _logger.Log(logLevel, "Defined LAN exclusions: {Subnets}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength)); + _logger.Log(logLevel, "Used LAN subnets: {Subnets}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength)); + _logger.Log(logLevel, "Filtered interface addresses: {Addresses}", _interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address)); + _logger.Log(logLevel, "Bind Addresses {Addresses}", GetAllBindInterfaces(false).OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address)); + _logger.Log(logLevel, "Remote IP filter is {Type}", config.IsRemoteIPFilterBlacklist ? "Blocklist" : "Allowlist"); + _logger.Log(logLevel, "Filtered subnets: {Subnets}", _remoteAddressFilter.Select(s => s.Prefix + "/" + s.PrefixLength)); } } } |
