From 56a158e5c9e3ed52d4b7e42509fe4638b15be66b Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Thu, 16 May 2024 16:10:37 +0200 Subject: [PATCH 1/4] Secure local playlist path handling --- .../Playlists/PlaylistItemsProvider.cs | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index 6256bfa21e..2a2ddb2980 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -78,7 +78,7 @@ namespace MediaBrowser.Providers.Playlists if (string.Equals(".m3u8", extension, StringComparison.OrdinalIgnoreCase)) { - return GetM3u8Items(stream, path); + return GetM3uItems(stream, path); } if (string.Equals(".pls", extension, StringComparison.OrdinalIgnoreCase)) @@ -95,23 +95,9 @@ namespace MediaBrowser.Providers.Playlists var content = new PlsContent(); var playlist = content.GetFromStream(stream); - return playlist.PlaylistEntries.Select(i => new LinkedChild - { - Path = GetPlaylistItemPath(i.Path, path), - Type = LinkedChildType.Manual - }); - } - - private IEnumerable GetM3u8Items(Stream stream, string path) - { - var content = new M3uContent(); - var playlist = content.GetFromStream(stream); - - return playlist.PlaylistEntries.Select(i => new LinkedChild - { - Path = GetPlaylistItemPath(i.Path, path), - Type = LinkedChildType.Manual - }); + return playlist.PlaylistEntries + .Select(i => GetLinkedChild(i.Path, path)) + .Where(i => i is not null); } private IEnumerable GetM3uItems(Stream stream, string path) @@ -119,11 +105,9 @@ namespace MediaBrowser.Providers.Playlists var content = new M3uContent(); var playlist = content.GetFromStream(stream); - return playlist.PlaylistEntries.Select(i => new LinkedChild - { - Path = GetPlaylistItemPath(i.Path, path), - Type = LinkedChildType.Manual - }); + return playlist.PlaylistEntries + .Select(i => GetLinkedChild(i.Path, path)) + .Where(i => i is not null); } private IEnumerable GetZplItems(Stream stream, string path) @@ -131,11 +115,9 @@ namespace MediaBrowser.Providers.Playlists var content = new ZplContent(); var playlist = content.GetFromStream(stream); - return playlist.PlaylistEntries.Select(i => new LinkedChild - { - Path = GetPlaylistItemPath(i.Path, path), - Type = LinkedChildType.Manual - }); + return playlist.PlaylistEntries + .Select(i => GetLinkedChild(i.Path, path)) + .Where(i => i is not null); } private IEnumerable GetWplItems(Stream stream, string path) @@ -143,25 +125,45 @@ namespace MediaBrowser.Providers.Playlists var content = new WplContent(); var playlist = content.GetFromStream(stream); - return playlist.PlaylistEntries.Select(i => new LinkedChild - { - Path = GetPlaylistItemPath(i.Path, path), - Type = LinkedChildType.Manual - }); + return playlist.PlaylistEntries + .Select(i => GetLinkedChild(i.Path, path)) + .Where(i => i is not null); } - private string GetPlaylistItemPath(string itemPath, string containingPlaylistFolder) + private LinkedChild GetLinkedChild(string itemPath, string playlistPath) { - if (!File.Exists(itemPath)) + if (TryGetPlaylistItemPath(itemPath, playlistPath, out var parsedPath)) { - var path = Path.Combine(Path.GetDirectoryName(containingPlaylistFolder), itemPath); - if (File.Exists(path)) + return new LinkedChild { - return path; - } + Path = parsedPath, + Type = LinkedChildType.Manual + }; + } + + return null; + } + + private bool TryGetPlaylistItemPath(string itemPath, string playlistPath, out string path) + { + path = null; + var baseFolder = Path.GetDirectoryName(playlistPath); + + if (itemPath.StartsWith(baseFolder, StringComparison.OrdinalIgnoreCase) && File.Exists(itemPath)) + { + path = itemPath; + return true; + } + + var basePath = Path.Combine(baseFolder, itemPath); + var fullPath = Path.GetFullPath(basePath); + if (fullPath.StartsWith(baseFolder, StringComparison.OrdinalIgnoreCase) && File.Exists(fullPath)) + { + path = fullPath; + return true; } - return itemPath; + return false; } public bool HasChanged(BaseItem item, IDirectoryService directoryService) From 2ca8ce6f60a37fde32d84ec302f0912e53314c68 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Thu, 16 May 2024 17:04:42 +0200 Subject: [PATCH 2/4] Apply review suggestions --- .../Playlists/PlaylistItemsProvider.cs | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index 2a2ddb2980..256ca617bb 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -25,10 +26,13 @@ namespace MediaBrowser.Providers.Playlists IHasItemChangeMonitor { private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly CollectionType[] _ignoredCollections = [CollectionType.livetv, CollectionType.boxsets, CollectionType.playlists]; - public PlaylistItemsProvider(ILogger logger) + public PlaylistItemsProvider(ILogger logger, ILibraryManager libraryManager) { _logger = logger; + _libraryManager = libraryManager; } public string Name => "Playlist Reader"; @@ -59,80 +63,87 @@ namespace MediaBrowser.Providers.Playlists private IEnumerable GetItems(string path, string extension) { + var libraryRoots = _libraryManager.GetUserRootFolder().Children + .OfType() + .Where(f => f.CollectionType.HasValue && !_ignoredCollections.Contains(f.CollectionType.Value)) + .SelectMany(f => f.PhysicalLocations) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + using (var stream = File.OpenRead(path)) { if (string.Equals(".wpl", extension, StringComparison.OrdinalIgnoreCase)) { - return GetWplItems(stream, path); + return GetWplItems(stream, path, libraryRoots); } if (string.Equals(".zpl", extension, StringComparison.OrdinalIgnoreCase)) { - return GetZplItems(stream, path); + return GetZplItems(stream, path, libraryRoots); } if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase)) { - return GetM3uItems(stream, path); + return GetM3uItems(stream, path, libraryRoots); } if (string.Equals(".m3u8", extension, StringComparison.OrdinalIgnoreCase)) { - return GetM3uItems(stream, path); + return GetM3uItems(stream, path, libraryRoots); } if (string.Equals(".pls", extension, StringComparison.OrdinalIgnoreCase)) { - return GetPlsItems(stream, path); + return GetPlsItems(stream, path, libraryRoots); } } return Enumerable.Empty(); } - private IEnumerable GetPlsItems(Stream stream, string path) + private IEnumerable GetPlsItems(Stream stream, string playlistPath, List libraryRoots) { var content = new PlsContent(); var playlist = content.GetFromStream(stream); return playlist.PlaylistEntries - .Select(i => GetLinkedChild(i.Path, path)) + .Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots)) .Where(i => i is not null); } - private IEnumerable GetM3uItems(Stream stream, string path) + private IEnumerable GetM3uItems(Stream stream, string playlistPath, List libraryRoots) { var content = new M3uContent(); var playlist = content.GetFromStream(stream); return playlist.PlaylistEntries - .Select(i => GetLinkedChild(i.Path, path)) + .Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots)) .Where(i => i is not null); } - private IEnumerable GetZplItems(Stream stream, string path) + private IEnumerable GetZplItems(Stream stream, string playlistPath, List libraryRoots) { var content = new ZplContent(); var playlist = content.GetFromStream(stream); return playlist.PlaylistEntries - .Select(i => GetLinkedChild(i.Path, path)) + .Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots)) .Where(i => i is not null); } - private IEnumerable GetWplItems(Stream stream, string path) + private IEnumerable GetWplItems(Stream stream, string playlistPath, List libraryRoots) { var content = new WplContent(); var playlist = content.GetFromStream(stream); return playlist.PlaylistEntries - .Select(i => GetLinkedChild(i.Path, path)) + .Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots)) .Where(i => i is not null); } - private LinkedChild GetLinkedChild(string itemPath, string playlistPath) + private LinkedChild GetLinkedChild(string itemPath, string playlistPath, List libraryRoots) { - if (TryGetPlaylistItemPath(itemPath, playlistPath, out var parsedPath)) + if (TryGetPlaylistItemPath(itemPath, playlistPath, libraryRoots, out var parsedPath)) { return new LinkedChild { @@ -144,23 +155,20 @@ namespace MediaBrowser.Providers.Playlists return null; } - private bool TryGetPlaylistItemPath(string itemPath, string playlistPath, out string path) + private bool TryGetPlaylistItemPath(string itemPath, string playlistPath, List libraryPaths, out string path) { path = null; var baseFolder = Path.GetDirectoryName(playlistPath); - - if (itemPath.StartsWith(baseFolder, StringComparison.OrdinalIgnoreCase) && File.Exists(itemPath)) - { - path = itemPath; - return true; - } - var basePath = Path.Combine(baseFolder, itemPath); var fullPath = Path.GetFullPath(basePath); - if (fullPath.StartsWith(baseFolder, StringComparison.OrdinalIgnoreCase) && File.Exists(fullPath)) + + foreach (var libraryPath in libraryPaths) { - path = fullPath; - return true; + if (fullPath.StartsWith(libraryPath, StringComparison.OrdinalIgnoreCase) && File.Exists(fullPath)) + { + path = fullPath; + return true; + } } return false; From 287e06d6dc13eafe6048c15ab01aa711da2f02f1 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Thu, 16 May 2024 21:55:00 +0200 Subject: [PATCH 3/4] If itemPath exists, use that, otherwise try getting full path from relative path --- .../Playlists/PlaylistItemsProvider.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index 256ca617bb..2eb5352148 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -158,15 +158,27 @@ namespace MediaBrowser.Providers.Playlists private bool TryGetPlaylistItemPath(string itemPath, string playlistPath, List libraryPaths, out string path) { path = null; - var baseFolder = Path.GetDirectoryName(playlistPath); - var basePath = Path.Combine(baseFolder, itemPath); - var fullPath = Path.GetFullPath(basePath); + string pathToCheck; + if (File.Exists(itemPath)) + { + pathToCheck = itemPath; + } + else + { + var baseFolder = Path.GetDirectoryName(playlistPath); + var basePath = Path.Combine(baseFolder, itemPath); + pathToCheck = Path.GetFullPath(basePath); + if (!File.Exists(pathToCheck)) + { + return false; + } + } foreach (var libraryPath in libraryPaths) { - if (fullPath.StartsWith(libraryPath, StringComparison.OrdinalIgnoreCase) && File.Exists(fullPath)) + if (pathToCheck.StartsWith(libraryPath, StringComparison.OrdinalIgnoreCase)) { - path = fullPath; + path = pathToCheck; return true; } } From 18e6c1ef7dc3671c07861f34dbf43b3200ea68e3 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 17 May 2024 10:58:00 +0200 Subject: [PATCH 4/4] Apply review comments --- .../Playlists/PlaylistItemsProvider.cs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index 2eb5352148..0d279a9077 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -14,6 +14,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; using PlaylistsNET.Content; @@ -25,14 +26,16 @@ namespace MediaBrowser.Providers.Playlists IPreRefreshProvider, IHasItemChangeMonitor { - private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; private readonly ILibraryManager _libraryManager; + private readonly ILogger _logger; private readonly CollectionType[] _ignoredCollections = [CollectionType.livetv, CollectionType.boxsets, CollectionType.playlists]; - public PlaylistItemsProvider(ILogger logger, ILibraryManager libraryManager) + public PlaylistItemsProvider(ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem) { _logger = logger; _libraryManager = libraryManager; + _fileSystem = fileSystem; } public string Name => "Playlist Reader"; @@ -158,20 +161,10 @@ namespace MediaBrowser.Providers.Playlists private bool TryGetPlaylistItemPath(string itemPath, string playlistPath, List libraryPaths, out string path) { path = null; - string pathToCheck; - if (File.Exists(itemPath)) - { - pathToCheck = itemPath; - } - else + string pathToCheck = _fileSystem.MakeAbsolutePath(Path.GetDirectoryName(playlistPath), itemPath); + if (!File.Exists(pathToCheck)) { - var baseFolder = Path.GetDirectoryName(playlistPath); - var basePath = Path.Combine(baseFolder, itemPath); - pathToCheck = Path.GetFullPath(basePath); - if (!File.Exists(pathToCheck)) - { - return false; - } + return false; } foreach (var libraryPath in libraryPaths)