From 246af92f5d8dab444b109df4f8f72069f6ac713b Mon Sep 17 00:00:00 2001 From: Meik Date: Sun, 29 Mar 2026 22:35:00 +0200 Subject: [PATCH] Add NTFS folder blacklist support --- LiamNtfs/C4IT.LIAM.Ntfs.cs | 116 +++++++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 4 deletions(-) diff --git a/LiamNtfs/C4IT.LIAM.Ntfs.cs b/LiamNtfs/C4IT.LIAM.Ntfs.cs index 59bd96f..0c08329 100644 --- a/LiamNtfs/C4IT.LIAM.Ntfs.cs +++ b/LiamNtfs/C4IT.LIAM.Ntfs.cs @@ -371,7 +371,7 @@ namespace C4IT.LIAM foreach (var childPath in GetServerRootChildPaths(parentClassification.NormalizedPath)) { var childClassification = ClassifyPath(childPath); - if (!ShouldIncludeDataArea(childClassification.DisplayName)) + if (!ShouldIncludeDataArea(childClassification)) continue; var childDataArea = await BuildDataAreaAsync(childClassification); @@ -393,7 +393,7 @@ namespace C4IT.LIAM foreach (var entry in folderEntries.Values.OfType()) { var childClassification = ClassifyPath(entry.Path); - if (!ShouldIncludeDataArea(childClassification.DisplayName)) + if (!ShouldIncludeDataArea(childClassification)) continue; var childDataArea = await BuildDataAreaAsync(childClassification, entry); @@ -420,7 +420,26 @@ namespace C4IT.LIAM .Select(shareName => BuildUncPath(new[] { serverName, shareName }, 2)); } - private bool ShouldIncludeDataArea(string displayName) + private bool ShouldIncludeDataArea(cNtfsPathClassification classification) + { + if (classification == null) + return false; + + if (!MatchesDataAreaRegEx(classification.DisplayName)) + return false; + + string matchingConfigurationKey; + string matchingRule; + if (IsBlacklistedFolderPath(classification, out matchingConfigurationKey, out matchingRule)) + { + LogEntry($"Skip NTFS path '{classification.NormalizedPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'", LogLevels.Debug); + return false; + } + + return true; + } + + private bool MatchesDataAreaRegEx(string displayName) { if (string.IsNullOrEmpty(this.DataAreaRegEx)) return true; @@ -428,6 +447,90 @@ namespace C4IT.LIAM return Regex.Match(displayName ?? string.Empty, this.DataAreaRegEx).Success; } + private bool IsBlacklistedFolderPath(cNtfsPathClassification classification, out string matchingConfigurationKey, out string matchingRule) + { + matchingConfigurationKey = null; + matchingRule = null; + + if (classification == null || classification.Kind != eNtfsPathKind.Folder) + return false; + + foreach (var excludedFolderName in GetAdditionalConfigurationValues("NtfsExcludeFolderNames")) + { + if (!MatchesAdditionalConfigurationPattern(classification.DisplayName, excludedFolderName)) + continue; + + matchingConfigurationKey = "NtfsExcludeFolderNames"; + matchingRule = excludedFolderName; + return true; + } + + var relativePath = GetRelativePathFromRoot(classification.NormalizedPath); + foreach (var excludedRelativePath in GetAdditionalConfigurationValues("NtfsExcludeRelativePaths")) + { + if (!MatchesAdditionalConfigurationPattern(relativePath, excludedRelativePath)) + continue; + + matchingConfigurationKey = "NtfsExcludeRelativePaths"; + matchingRule = excludedRelativePath; + return true; + } + + return false; + } + + private IEnumerable GetAdditionalConfigurationValues(string key) + { + if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key)) + return Enumerable.Empty(); + + string rawValue; + if (!AdditionalConfiguration.TryGetValue(key, out rawValue) || string.IsNullOrWhiteSpace(rawValue)) + return Enumerable.Empty(); + + return rawValue + .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + .Select(i => i.Trim()) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + private string GetRelativePathFromRoot(string path) + { + var normalizedRoot = NormalizeUncPath(this.RootPath); + var normalizedPath = NormalizeUncPath(path); + + if (string.IsNullOrWhiteSpace(normalizedRoot) || string.IsNullOrWhiteSpace(normalizedPath)) + return string.Empty; + + if (PathsEqual(normalizedRoot, normalizedPath)) + return string.Empty; + + var rootWithSeparator = normalizedRoot + "\\"; + if (!normalizedPath.StartsWith(rootWithSeparator, StringComparison.OrdinalIgnoreCase)) + return normalizedPath; + + return normalizedPath.Substring(rootWithSeparator.Length) + .Trim() + .TrimStart('\\') + .Replace('/', '\\'); + } + + private bool MatchesAdditionalConfigurationPattern(string value, string pattern) + { + if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(pattern)) + return false; + + var normalizedValue = value.Trim().Replace('/', '\\'); + var normalizedPattern = pattern.Trim().Replace('/', '\\').Trim('\\'); + if (string.IsNullOrWhiteSpace(normalizedPattern)) + return false; + + var regexPattern = "^" + Regex.Escape(normalizedPattern).Replace("\\*", ".*") + "$"; + return Regex.IsMatch(normalizedValue, regexPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + private List GetDfsObjectPrefixes(string path) { var normalizedPath = NormalizeUncPath(path); @@ -739,7 +842,12 @@ namespace C4IT.LIAM public bool IsPermissionManagedFolderPath(string path) { var classification = ClassifyPath(path); - return classification != null && classification.Kind == eNtfsPathKind.Folder; + if (classification == null || classification.Kind != eNtfsPathKind.Folder) + return false; + + string matchingConfigurationKey; + string matchingRule; + return !IsBlacklistedFolderPath(classification, out matchingConfigurationKey, out matchingRule); } private IEnumerable BuildSecurityGroupTemplates()