New: Improve path validation when handling paths from different OSes

This commit is contained in:
Mark McDowall 2023-03-23 17:34:19 -07:00
parent ed140dd396
commit 0321368cc3
18 changed files with 95 additions and 72 deletions

View File

@ -1,4 +1,5 @@
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Test.Common;
@ -12,14 +13,14 @@ namespace NzbDrone.Common.Test.EnsureTest
public void EnsureWindowsPath(string path)
{
WindowsOnly();
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
}
[TestCase(@"/var/user/file with, comma.mkv")]
public void EnsureLinuxPath(string path)
{
PosixOnly();
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
}
}
}

View File

@ -35,6 +35,7 @@ namespace NzbDrone.Common.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(@"//CAPITAL//lower// ", @"\\CAPITAL\lower")]
public void Clean_Path_Windows(string dirty, string clean)
{
WindowsOnly();

View File

@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.EnvironmentInfo;
@ -67,7 +65,7 @@ namespace NzbDrone.Common.Disk
private void CheckFolderExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
if (!FolderExists(path))
{
@ -77,7 +75,7 @@ namespace NzbDrone.Common.Disk
private void CheckFileExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
if (!FileExists(path))
{
@ -95,19 +93,19 @@ namespace NzbDrone.Common.Disk
public bool FolderExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return Directory.Exists(path);
}
public bool FileExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return FileExists(path, PathStringComparison);
}
public bool FileExists(string path, StringComparison stringComparison)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
switch (stringComparison)
{
@ -127,7 +125,7 @@ namespace NzbDrone.Common.Disk
public bool FolderWritable(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
try
{
@ -146,35 +144,35 @@ namespace NzbDrone.Common.Disk
public bool FolderEmpty(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return Directory.EnumerateFileSystemEntries(path).Empty();
}
public string[] GetDirectories(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return Directory.GetDirectories(path);
}
public string[] GetFiles(string path, SearchOption searchOption)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return Directory.GetFiles(path, "*.*", searchOption);
}
public long GetFolderSize(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return GetFiles(path, SearchOption.AllDirectories).Sum(e => new FileInfo(e).Length);
}
public long GetFileSize(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
if (!FileExists(path))
{
@ -187,13 +185,13 @@ namespace NzbDrone.Common.Disk
public void CreateFolder(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Directory.CreateDirectory(path);
}
public void DeleteFile(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Logger.Trace("Deleting file: {0}", path);
RemoveReadOnly(path);
@ -203,8 +201,8 @@ namespace NzbDrone.Common.Disk
public void CloneFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
if (source.PathEquals(destination))
{
@ -221,8 +219,8 @@ namespace NzbDrone.Common.Disk
public void CopyFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
if (source.PathEquals(destination))
{
@ -239,8 +237,8 @@ namespace NzbDrone.Common.Disk
public void MoveFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
if (source.PathEquals(destination))
{
@ -258,8 +256,8 @@ namespace NzbDrone.Common.Disk
public void MoveFolder(string source, string destination)
{
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
Directory.Move(source, destination);
}
@ -288,7 +286,7 @@ namespace NzbDrone.Common.Disk
public void DeleteFolder(string path, bool recursive)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var files = Directory.GetFiles(path, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
Array.ForEach(files, RemoveReadOnly);
@ -298,28 +296,28 @@ namespace NzbDrone.Common.Disk
public string ReadAllText(string filePath)
{
Ensure.That(filePath, () => filePath).IsValidPath();
Ensure.That(filePath, () => filePath).IsValidPath(PathValidationType.CurrentOs);
return File.ReadAllText(filePath);
}
public void WriteAllText(string filename, string contents)
{
Ensure.That(filename, () => filename).IsValidPath();
Ensure.That(filename, () => filename).IsValidPath(PathValidationType.CurrentOs);
RemoveReadOnly(filename);
File.WriteAllText(filename, contents);
}
public void FolderSetLastWriteTime(string path, DateTime dateTime)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Directory.SetLastWriteTimeUtc(path, dateTime);
}
public void FileSetLastWriteTime(string path, DateTime dateTime)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
File.SetLastWriteTime(path, dateTime);
}
@ -341,14 +339,14 @@ namespace NzbDrone.Common.Disk
public virtual string GetPathRoot(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
return Path.GetPathRoot(path);
}
public string GetParentFolder(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var parent = Directory.GetParent(path.TrimEnd(Path.DirectorySeparatorChar));
@ -381,7 +379,7 @@ namespace NzbDrone.Common.Disk
public void EmptyFolder(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
foreach (var file in GetFiles(path, SearchOption.TopDirectoryOnly))
{
@ -471,7 +469,7 @@ namespace NzbDrone.Common.Disk
public List<DirectoryInfo> GetDirectoryInfos(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var di = new DirectoryInfo(path);
@ -480,7 +478,7 @@ namespace NzbDrone.Common.Disk
public List<FileInfo> GetFileInfos(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var di = new DirectoryInfo(path);

View File

@ -43,8 +43,8 @@ namespace NzbDrone.Common.Disk
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);
@ -140,8 +140,8 @@ namespace NzbDrone.Common.Disk
{
var filesCopied = 0;
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);
@ -255,8 +255,8 @@ namespace NzbDrone.Common.Disk
public TransferMode TransferFile(string sourcePath, string targetPath, TransferMode mode, bool overwrite = false)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);

View File

@ -71,7 +71,7 @@ namespace NzbDrone.Common.Disk
if (
allowFoldersWithoutTrailingSlashes &&
query.IsPathValid() &&
query.IsPathValid(PathValidationType.CurrentOs) &&
_diskProvider.FolderExists(query))
{
return GetResult(query, includeFiles);

View File

@ -162,7 +162,7 @@ namespace NzbDrone.Common.Disk
}
}
public bool IsValid => _path.IsPathValid();
public bool IsValid => _path.IsPathValid(PathValidationType.CurrentOs);
private int GetFileNameIndex()
{

View File

@ -0,0 +1,8 @@
namespace NzbDrone.Common.Disk
{
public enum PathValidationType
{
CurrentOs,
AnyOs
}
}

View File

@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Text.RegularExpressions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat.Resources;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@ -111,14 +112,14 @@ namespace NzbDrone.Common.EnsureThat
}
[DebuggerStepThrough]
public static Param<string> IsValidPath(this Param<string> param)
public static Param<string> IsValidPath(this Param<string> param, PathValidationType validationType)
{
if (string.IsNullOrWhiteSpace(param.Value))
{
throw ExceptionFactory.CreateForParamValidation(param.Name, ExceptionMessages.EnsureExtensions_IsNotNullOrWhiteSpace);
}
if (param.Value.IsPathValid())
if (param.Value.IsPathValid(validationType))
{
return param;
}

View File

@ -30,12 +30,12 @@ namespace NzbDrone.Common.Extensions
public static string CleanFilePath(this string path)
{
Ensure.That(path, () => path).IsNotNullOrWhiteSpace();
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.AnyOs);
var info = new FileInfo(path.Trim());
// UNC
if (OsInfo.IsWindows && info.FullName.StartsWith(@"\\"))
if (!info.FullName.Contains('/') && info.FullName.StartsWith(@"\\"))
{
return info.FullName.TrimEnd('/', '\\', ' ');
}
@ -147,24 +147,24 @@ namespace NzbDrone.Common.Extensions
private static readonly Regex WindowsPathWithDriveRegex = new Regex(@"^[a-zA-Z]:\\", RegexOptions.Compiled);
public static bool IsPathValid(this string path)
public static bool IsPathValid(this string path, PathValidationType validationType)
{
if (path.ContainsInvalidPathChars() || string.IsNullOrWhiteSpace(path))
{
return false;
}
if (validationType == PathValidationType.AnyOs)
{
return IsPathValidForWindows(path) || IsPathValidForNonWindows(path);
}
if (OsInfo.IsNotWindows)
{
return path.StartsWith(Path.DirectorySeparatorChar.ToString());
return IsPathValidForNonWindows(path);
}
if (path.StartsWith("\\") || WindowsPathWithDriveRegex.IsMatch(path))
{
return true;
}
return false;
return IsPathValidForWindows(path);
}
public static bool ContainsInvalidPathChars(this string text)
@ -375,5 +375,15 @@ namespace NzbDrone.Common.Extensions
{
return Path.Combine(appFolderInfo.StartUpFolder, NLOG_CONFIG_FILE);
}
private static bool IsPathValidForWindows(string path)
{
return path.StartsWith("\\") || WindowsPathWithDriveRegex.IsMatch(path);
}
private static bool IsPathValidForNonWindows(string path)
{
return path.StartsWith("/");
}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;

View File

@ -70,7 +70,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
if (newFormatScore <= currentFormatScore)
{
_logger.Debug("New item's custom formats [{0}] ({1}) do not improve on [{2}] ({3}, skipping",
_logger.Debug("New item's custom formats [{0}] ({1}) do not improve on [{2}] ({3}), skipping",
newCustomFormats.ConcatToString(),
newFormatScore,
currentCustomFormats.ConcatToString(),

View File

@ -99,7 +99,7 @@ namespace NzbDrone.Core.MediaFiles
{
Ensure.That(episodeFile, () => episodeFile).IsNotNull();
Ensure.That(series, () => series).IsNotNull();
Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath();
Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath(PathValidationType.CurrentOs);
var episodeFilePath = episodeFile.Path ?? Path.Combine(series.Path, episodeFile.RelativePath);

View File

@ -1,4 +1,5 @@
using System.IO;
using System.IO;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;

View File

@ -72,7 +72,7 @@ namespace NzbDrone.Core.RootFolders
{
try
{
if (folder.Path.IsPathValid())
if (folder.Path.IsPathValid(PathValidationType.CurrentOs))
{
GetDetails(folder, seriesPaths, true);
}

View File

@ -1,4 +1,5 @@
using FluentValidation.Validators;
using FluentValidation.Validators;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Validation
@ -17,7 +18,7 @@ namespace NzbDrone.Core.Validation
return false;
}
return context.PropertyValue.ToString().IsPathValid();
return context.PropertyValue.ToString().IsPathValid(PathValidationType.CurrentOs);
}
}
}

View File

@ -1,5 +1,6 @@
using FluentValidation;
using FluentValidation;
using FluentValidation.Validators;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Validation.Paths
@ -26,7 +27,7 @@ namespace NzbDrone.Core.Validation.Paths
return false;
}
return context.PropertyValue.ToString().IsPathValid();
return context.PropertyValue.ToString().IsPathValid(PathValidationType.CurrentOs);
}
}
}

View File

@ -40,7 +40,7 @@ namespace NzbDrone.Mono.Disk
public override long? GetAvailableSpace(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var mount = GetMount(path);
@ -217,7 +217,7 @@ namespace NzbDrone.Mono.Disk
public override long? GetTotalSize(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var mount = GetMount(path);

View File

@ -35,7 +35,7 @@ namespace NzbDrone.Windows.Disk
public override string GetPathRoot(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var reparsePoint = GetReparsePoint(path);
@ -44,7 +44,7 @@ namespace NzbDrone.Windows.Disk
public override long? GetAvailableSpace(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var root = GetPathRoot(path);
@ -58,7 +58,7 @@ namespace NzbDrone.Windows.Disk
public override void InheritFolderPermissions(string filename)
{
Ensure.That(filename, () => filename).IsValidPath();
Ensure.That(filename, () => filename).IsValidPath(PathValidationType.CurrentOs);
var fileInfo = new FileInfo(filename);
var fs = fileInfo.GetAccessControl(AccessControlSections.Access);
@ -121,7 +121,7 @@ namespace NzbDrone.Windows.Disk
public override long? GetTotalSize(string path)
{
Ensure.That(path, () => path).IsValidPath();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var root = GetPathRoot(path);
@ -135,7 +135,7 @@ namespace NzbDrone.Windows.Disk
private static long DriveFreeSpaceEx(string folderName)
{
Ensure.That(folderName, () => folderName).IsValidPath();
Ensure.That(folderName, () => folderName).IsValidPath(PathValidationType.CurrentOs);
if (!folderName.EndsWith("\\"))
{
@ -156,7 +156,7 @@ namespace NzbDrone.Windows.Disk
private static long DriveTotalSizeEx(string folderName)
{
Ensure.That(folderName, () => folderName).IsValidPath();
Ensure.That(folderName, () => folderName).IsValidPath(PathValidationType.CurrentOs);
if (!folderName.EndsWith("\\"))
{