From dc7f46027aebf33b77d258a63c2ae973788cedd0 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 1 Aug 2021 16:45:23 -0700 Subject: [PATCH] Fixed: Prevent conflicts with reserved device names Closes #4595 --- .../ReservedDeviceNameFixture.cs | 86 +++++++++++++++++++ .../Organizer/FileNameBuilder.cs | 24 ++++-- 2 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/ReservedDeviceNameFixture.cs diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/ReservedDeviceNameFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/ReservedDeviceNameFixture.cs new file mode 100644 index 000000000..ca940bfc9 --- /dev/null +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/ReservedDeviceNameFixture.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests +{ + [TestFixture] + + public class ReservedDeviceNameFixture : CoreTest + { + private Series _series; + private Episode _episode1; + private EpisodeFile _episodeFile; + private NamingConfig _namingConfig; + + [SetUp] + public void Setup() + { + _series = Builder + .CreateNew() + .With(s => s.Title = "South Park") + .Build(); + + + _namingConfig = NamingConfig.Default; + _namingConfig.RenameEpisodes = true; + + + Mocker.GetMock() + .Setup(c => c.GetConfig()).Returns(_namingConfig); + + _episode1 = Builder.CreateNew() + .With(e => e.Title = "City Sushi") + .With(e => e.SeasonNumber = 15) + .With(e => e.EpisodeNumber = 6) + .With(e => e.AbsoluteEpisodeNumber = 100) + .Build(); + + _episodeFile = new EpisodeFile { Quality = new QualityModel(Quality.HDTV720p), ReleaseGroup = "SonarrTest" }; + + Mocker.GetMock() + .Setup(v => v.Get(Moq.It.IsAny())) + .Returns(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v)); + } + + [Test] + public void should_replace_reserved_device_name_in_series_folder() + { + _series.Title = "Con Man"; + _namingConfig.SeriesFolderFormat = "{Series.Title}"; + + Subject.GetSeriesFolder(_series).Should().Be("Con_Man"); + } + + [Test] + public void should_replace_reserved_device_name_in_season_folder() + { + _series.Title = "Con Man"; + _namingConfig.SeasonFolderFormat = "{Series.Title} - Season {Season:00}"; + + Subject.GetSeasonFolder(_series, 1).Should().Be("Con_Man - Season 01"); + } + + [Test] + public void should_replace_reserved_device_name_in_file_name() + { + _series.Title = "Con Man"; + _namingConfig.StandardEpisodeFormat = "{Series.Title} - S{Season:00}E{Episode:00}"; + + Subject.BuildFileName(new List { _episode1 }, _series, _episodeFile).Should().Be("Con_Man - S15E06"); + } + } +} diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 79d959582..5028d95f8 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text.RegularExpressions; -using FluentMigrator.Builders.Create.Column; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Disk; @@ -13,7 +12,6 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.MediaInfo; -using NzbDrone.Core.Parser; using NzbDrone.Core.Profiles.Releases; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; @@ -82,6 +80,8 @@ namespace NzbDrone.Core.Organizer private static readonly Regex YearRegex = new Regex(@"\(\d{4}\)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex ReservedDeviceNamesRegex = new Regex(@"^(?:aux|com1|com2|com3|com4|com5|com6|com7|com8|com9|con|lpt1|lpt2|lpt3|lpt4|lpt5|lpt6|lpt7|lpt8|lpt9|nul|prn)\.", RegexOptions.Compiled | RegexOptions.IgnoreCase); + public FileNameBuilder(INamingConfigService namingConfigService, IQualityDefinitionService qualityDefinitionService, ICacheManager cacheManager, @@ -176,6 +176,7 @@ namespace NzbDrone.Core.Organizer component = FileNameCleanupRegex.Replace(component, match => match.Captures[0].Value[0].ToString()); component = TrimSeparatorsRegex.Replace(component, string.Empty); component = component.Replace("{ellipsis}", "..."); + component = ReplaceReservedDeviceNames(component); components.Add(component); } @@ -274,7 +275,11 @@ namespace NzbDrone.Core.Organizer AddIdTokens(tokenHandlers, series); var folderName = ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig); - return CleanFolderName(folderName); + + folderName = CleanFolderName(folderName); + folderName = ReplaceReservedDeviceNames(folderName); + + return folderName; } public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null) @@ -291,9 +296,12 @@ namespace NzbDrone.Core.Organizer AddSeasonTokens(tokenHandlers, seasonNumber); var format = seasonNumber == 0 ? namingConfig.SpecialsFolderFormat : namingConfig.SeasonFolderFormat; - var folderName = ReplaceTokens(format, tokenHandlers, namingConfig); - return CleanFolderName(folderName); + + folderName = CleanFolderName(folderName); + folderName = ReplaceReservedDeviceNames(folderName); + + return folderName; } public static string CleanTitle(string title) @@ -1048,6 +1056,12 @@ namespace NzbDrone.Core.Organizer return result.GetByteCount(); } + + private string ReplaceReservedDeviceNames(string input) + { + // Replace reserved windows device names with an alternative + return ReservedDeviceNamesRegex.Replace(input, match => match.Value.Replace(".", "_")); + } } internal sealed class TokenMatch