diff --git a/src/NzbDrone.Common.Test/PathExtensionFixture.cs b/src/NzbDrone.Common.Test/PathExtensionFixture.cs index 00012a135..967126a1c 100644 --- a/src/NzbDrone.Common.Test/PathExtensionFixture.cs +++ b/src/NzbDrone.Common.Test/PathExtensionFixture.cs @@ -3,6 +3,7 @@ using FluentAssertions; using Moq; using NUnit.Framework; +using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Test.Common; @@ -34,7 +35,7 @@ private IAppFolderInfo GetIAppDirectoryInfo() [TestCase(@"\\Testserver\\Test\", @"\\Testserver\Test")] [TestCase(@"\\Testserver\Test\file.ext", @"\\Testserver\Test\file.ext")] [TestCase(@"\\Testserver\Test\file.ext\\", @"\\Testserver\Test\file.ext")] - [TestCase(@"\\Testserver\Test\file.ext \\", @"\\Testserver\Test\file.ext")] + [TestCase(@"\\Testserver\Test\file.ext ", @"\\Testserver\Test\file.ext")] [TestCase(@"//CAPITAL//lower// ", @"\\CAPITAL\lower")] public void Clean_Path_Windows(string dirty, string clean) { @@ -335,5 +336,30 @@ public void GetAncestorFolders_should_return_all_ancestors_in_path_Linux() result[2].Should().Be(@"TV"); result[3].Should().Be(@"Series Title"); } + + [TestCase(@"C:\Test\")] + [TestCase(@"C:\Test")] + [TestCase(@"C:\Test\TV\")] + [TestCase(@"C:\Test\TV")] + public void IsPathValid_should_be_true(string path) + { + path.AsOsAgnostic().IsPathValid(PathValidationType.CurrentOs).Should().BeTrue(); + } + + [TestCase(@"C:\Test \")] + [TestCase(@"C:\Test ")] + [TestCase(@"C:\ Test\")] + [TestCase(@"C:\ Test")] + [TestCase(@"C:\Test \TV")] + [TestCase(@"C:\ Test\TV")] + [TestCase(@"C:\Test \TV\")] + [TestCase(@"C:\ Test\TV\")] + [TestCase(@" C:\Test\TV\")] + [TestCase(@" C:\Test\TV")] + + public void IsPathValid_should_be_false(string path) + { + path.AsOsAgnostic().IsPathValid(PathValidationType.CurrentOs).Should().BeFalse(); + } } } diff --git a/src/NzbDrone.Common/Extensions/PathExtensions.cs b/src/NzbDrone.Common/Extensions/PathExtensions.cs index 280083e4c..4585326f1 100644 --- a/src/NzbDrone.Common/Extensions/PathExtensions.cs +++ b/src/NzbDrone.Common/Extensions/PathExtensions.cs @@ -29,6 +29,12 @@ public static class PathExtensions public static string CleanFilePath(this string path) { + if (path.IsNotNullOrWhiteSpace()) + { + // Trim trailing spaces before checking if the path is valid so validation doesn't fail for something we can fix. + path = path.TrimEnd(' '); + } + Ensure.That(path, () => path).IsNotNullOrWhiteSpace(); Ensure.That(path, () => path).IsValidPath(PathValidationType.AnyOs); @@ -37,10 +43,10 @@ public static string CleanFilePath(this string path) // UNC if (!info.FullName.Contains('/') && info.FullName.StartsWith(@"\\")) { - return info.FullName.TrimEnd('/', '\\', ' '); + return info.FullName.TrimEnd('/', '\\'); } - return info.FullName.TrimEnd('/').Trim('\\', ' '); + return info.FullName.TrimEnd('/').Trim('\\'); } public static bool PathNotEquals(this string firstPath, string secondPath, StringComparison? comparison = null) @@ -154,6 +160,23 @@ public static bool IsPathValid(this string path, PathValidationType validationTy return false; } + if (path.Trim() != path) + { + return false; + } + + var directoryInfo = new DirectoryInfo(path); + + while (directoryInfo != null) + { + if (directoryInfo.Name.Trim() != directoryInfo.Name) + { + return false; + } + + directoryInfo = directoryInfo.Parent; + } + if (validationType == PathValidationType.AnyOs) { return IsPathValidForWindows(path) || IsPathValidForNonWindows(path); @@ -291,6 +314,11 @@ public static string ProcessNameToExe(this string processName) return processName; } + public static string CleanPath(this string path) + { + return Path.Join(path.Split(Path.DirectorySeparatorChar).Select(s => s.Trim()).ToArray()); + } + public static string GetAppDataPath(this IAppFolderInfo appFolderInfo) { return appFolderInfo.AppDataFolder; diff --git a/src/Sonarr.Api.V3/Series/SeriesController.cs b/src/Sonarr.Api.V3/Series/SeriesController.cs index 14ee8f465..e1d50ec0f 100644 --- a/src/Sonarr.Api.V3/Series/SeriesController.cs +++ b/src/Sonarr.Api.V3/Series/SeriesController.cs @@ -92,8 +92,6 @@ public SeriesController(IBroadcastSignalRMessage signalRBroadcaster, .When(s => s.Path.IsNullOrWhiteSpace()); PostValidator.RuleFor(s => s.Title).NotEmpty(); PostValidator.RuleFor(s => s.TvdbId).GreaterThan(0).SetValidator(seriesExistsValidator); - - PutValidator.RuleFor(s => s.Path).IsValidPath(); } [HttpGet]