using C4IT.Logging; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace C4IT_IAM_Engine { public static class Helper { public const int MaxAdGroupNameLength = 64; public const int MaxAdGroupDescriptionLength = 1024; public const int MaxAdGroupLoopDigits = 3; public const string DefaultGroupNameSanitizeReplacement = "_"; private const int MinLeadingRelativePathSegmentLength = 3; private const int MinSingleLeadingRelativePathSegmentLength = 2; private const int MinLastRelativePathSegmentLength = 12; public sealed class BoundedTemplateContext { public string[] SanitizedSegments { get; set; } = Array.Empty(); public string FolderName { get; set; } = string.Empty; public bool WasShortened { get; set; } public string OriginalValue { get; set; } = string.Empty; public string FinalValue { get; set; } = string.Empty; public string Strategy { get; set; } = string.Empty; } public sealed class RootPathTemplateContext { public string Server { get; set; } = string.Empty; public string[] Segments { get; set; } = Array.Empty(); public string Name { get; set; } = string.Empty; public string Path { get; set; } = string.Empty; public string PathSegmentSeparator { get; set; } = DefaultGroupNameSanitizeReplacement; } public static string ReplaceLoopTag(this string str, int loop) { return Regex.Replace(str, @"(?{{(?[^}]*)(?LOOP)(?[^{]*)}})", loop <= 0 ? "" : "${prefix}" + loop + "${postfix}"); } public static string ReplaceTags(this string str, IDictionary dict) { if (str.Equals(string.Empty) || str == null || dict == null || dict.Count == 0) return str; return dict.Aggregate(str, (current, value) => current.Replace("{{" + value.Key + "}}", value.Value)); } public static string ApplyTemplatePlaceholders(string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName) { return ApplyTemplatePlaceholders(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, null, DefaultGroupNameSanitizeReplacement); } public static string ApplyTemplatePlaceholders(string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName, RootPathTemplateContext rootContext) { return ApplyTemplatePlaceholders(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, rootContext, DefaultGroupNameSanitizeReplacement); } public static string ApplyTemplatePlaceholders(string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName, RootPathTemplateContext rootContext, string pathSegmentSeparator) { if (templateValue == null) return string.Empty; var result = Regex.Replace(templateValue, @"{{\s*NAME\s*}}", folderName ?? string.Empty, RegexOptions.IgnoreCase); result = ApplyRootPathPlaceholders(result, rootContext); if (allowRelativePath) { result = Regex.Replace(result, @"{{\s*RELATIVEPATH(?:\s*\(\s*(\d+)\s*\))?\s*}}", match => { if (sanitizedSegments == null || sanitizedSegments.Length == 0) return string.Empty; if (!match.Groups[1].Success) return defaultRelativePath; if (!int.TryParse(match.Groups[1].Value, out var segmentIndex) || segmentIndex < 0) return defaultRelativePath; var segmentCount = Math.Min(sanitizedSegments.Length, segmentIndex + 1); var skip = sanitizedSegments.Length - segmentCount; return JoinSanitizedPathSegments(sanitizedSegments.Skip(skip), pathSegmentSeparator); }, RegexOptions.IgnoreCase); } return result; } public static RootPathTemplateContext GetRootPathTemplateContext(string rootPath) { return GetRootPathTemplateContext(rootPath, DefaultGroupNameSanitizeReplacement); } public static RootPathTemplateContext GetRootPathTemplateContext(string rootPath, string groupNameSanitizeReplacement) { var segments = SplitPathSegments(rootPath); if (segments.Length == 0) return new RootPathTemplateContext(); var isUncPath = (rootPath ?? string.Empty).Trim().Replace('/', '\\').StartsWith(@"\\", StringComparison.Ordinal); var server = isUncPath ? SanitizePathSegment(segments[0], groupNameSanitizeReplacement) : string.Empty; var pathSegments = isUncPath ? segments.Skip(1).ToArray() : segments; var sanitizedPathSegments = pathSegments.Select(i => SanitizePathSegment(i, groupNameSanitizeReplacement)).ToArray(); return new RootPathTemplateContext { Server = server, Segments = sanitizedPathSegments, Name = sanitizedPathSegments.Length == 0 ? string.Empty : sanitizedPathSegments[sanitizedPathSegments.Length - 1], Path = sanitizedPathSegments.Length == 0 ? string.Empty : JoinSanitizedPathSegments(sanitizedPathSegments, groupNameSanitizeReplacement), PathSegmentSeparator = NormalizeGroupNameSanitizeReplacement(groupNameSanitizeReplacement) }; } public static BoundedTemplateContext GetBoundedAdGroupTemplateContext( string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName, IDictionary replacementTags, int maxLength, string logContext, string valueLabel = "AD-Gruppenname", RootPathTemplateContext rootContext = null, bool preserveCase = false, string pathSegmentSeparator = DefaultGroupNameSanitizeReplacement) { var effectiveSegments = (sanitizedSegments ?? Array.Empty()).Where(i => i != null).ToArray(); var effectiveFolderName = folderName ?? string.Empty; var currentRelativePath = GetCurrentRelativePath(effectiveSegments, defaultRelativePath, pathSegmentSeparator); var originalValue = MaterializeTemplateValue(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext, preserveCase, pathSegmentSeparator); var measuredValue = MaterializeTemplateValueForLength(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext, preserveCase, pathSegmentSeparator); var usesRelativePath = allowRelativePath && Regex.IsMatch(templateValue ?? string.Empty, @"{{\s*RELATIVEPATH", RegexOptions.IgnoreCase); var usesName = Regex.IsMatch(templateValue ?? string.Empty, @"{{\s*NAME\s*}}", RegexOptions.IgnoreCase); var strategy = string.Empty; while (measuredValue.Length > maxLength) { var changed = false; if (usesRelativePath && TryShortenRelativePath(ref effectiveSegments)) { effectiveFolderName = effectiveSegments.Length > 0 ? effectiveSegments[effectiveSegments.Length - 1] : string.Empty; if (string.IsNullOrWhiteSpace(strategy)) strategy = "truncate-relativepath"; changed = true; } else if (usesName && !usesRelativePath && TryShortenName(ref effectiveFolderName)) { if (string.IsNullOrWhiteSpace(strategy)) strategy = "truncate-name"; changed = true; } if (!changed) break; currentRelativePath = GetCurrentRelativePath(effectiveSegments, defaultRelativePath, pathSegmentSeparator); originalValue = MaterializeTemplateValue(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext, preserveCase, pathSegmentSeparator); measuredValue = MaterializeTemplateValueForLength(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext, preserveCase, pathSegmentSeparator); } var initialValue = MaterializeTemplateValue( templateValue, allowRelativePath, GetCurrentRelativePath(sanitizedSegments, defaultRelativePath, pathSegmentSeparator), sanitizedSegments, folderName, replacementTags, rootContext, preserveCase, pathSegmentSeparator); var result = new BoundedTemplateContext { SanitizedSegments = effectiveSegments, FolderName = effectiveSegments.Length > 0 ? effectiveSegments[effectiveSegments.Length - 1] : effectiveFolderName, OriginalValue = initialValue, FinalValue = originalValue, WasShortened = !string.Equals(initialValue, originalValue, StringComparison.Ordinal), Strategy = strategy }; if (result.WasShortened) { cLogManager.DefaultLogger.LogEntry( LogLevels.Warning, $"{valueLabel} gekuerzt ({logContext}): '{result.OriginalValue}' ({GetMeasuredTemplateLength(result.OriginalValue)}) -> '{result.FinalValue}' ({GetMeasuredTemplateLength(result.FinalValue)}), Strategie: {result.Strategy}, Limit: {maxLength}."); } if (measuredValue.Length > maxLength) { cLogManager.DefaultLogger.LogEntry( LogLevels.Warning, $"{valueLabel} ueberschreitet weiterhin das sichere Limit ({logContext}): '{result.FinalValue}' ({measuredValue.Length}), Limit: {maxLength}."); } return result; } public static string SanitizePathSegment(string segment) { return SanitizePathSegment(segment, DefaultGroupNameSanitizeReplacement); } public static string SanitizePathSegment(string segment, string groupNameSanitizeReplacement) { if (string.IsNullOrEmpty(segment)) return string.Empty; var replacement = NormalizeGroupNameSanitizeReplacement(groupNameSanitizeReplacement); return Regex.Replace(segment, @"[\s\-]", match => replacement); } public static string NormalizeGroupNameSanitizeReplacement(string replacement) { if (replacement == null) return DefaultGroupNameSanitizeReplacement; var trimmed = replacement.Trim(); if (trimmed.Equals("", StringComparison.OrdinalIgnoreCase) || trimmed.Equals("empty", StringComparison.OrdinalIgnoreCase) || trimmed.Equals("none", StringComparison.OrdinalIgnoreCase) || trimmed.Equals("remove", StringComparison.OrdinalIgnoreCase)) { return string.Empty; } return trimmed; } public static string JoinSanitizedPathSegments(IEnumerable sanitizedSegments, string groupNameSanitizeReplacement) { if (sanitizedSegments == null) return string.Empty; return string.Join(NormalizeGroupNameSanitizeReplacement(groupNameSanitizeReplacement), sanitizedSegments); } public static string ApplyAdGroupNameCasing(string value, bool preserveCase) { if (value == null) return string.Empty; return preserveCase ? value : value.ToUpper(); } public static void CreatePathWithWriteAccess(string FilePath) { try { var PF = Environment.ExpandEnvironmentVariables(FilePath); Directory.CreateDirectory(PF); } catch { } } public static string MaskAllButLastAndFirst(this string input, char maskingChar = '*') { if (input.Length > 3) { var pattern = @"^(.{1})(.+)(.{1})$"; var match = Regex.Match(input, pattern); var mask = new string(maskingChar, match.Groups[2].Length); return $"{match.Groups[1]}{mask}{match.Groups[3]}"; } else return new string(maskingChar, input.Length); } private static string MaterializeTemplateValue( string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName, IDictionary replacementTags, RootPathTemplateContext rootContext, bool preserveCase, string pathSegmentSeparator) { var materializedValue = ApplyTemplatePlaceholders(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, rootContext, pathSegmentSeparator) .ReplaceTags(replacementTags); return ApplyAdGroupNameCasing(materializedValue, preserveCase); } private static string MaterializeTemplateValueForLength( string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName, IDictionary replacementTags, RootPathTemplateContext rootContext, bool preserveCase, string pathSegmentSeparator) { return NormalizeLoopPlaceholderLength( MaterializeTemplateValue(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, replacementTags, rootContext, preserveCase, pathSegmentSeparator)); } private static string ApplyRootPathPlaceholders(string templateValue, RootPathTemplateContext rootContext) { if (templateValue == null) return string.Empty; var context = rootContext ?? new RootPathTemplateContext(); var result = Regex.Replace(templateValue, @"{{\s*ROOT_SERVER\s*}}", context.Server ?? string.Empty, RegexOptions.IgnoreCase); result = Regex.Replace(result, @"{{\s*ROOT_NAME\s*}}", context.Name ?? string.Empty, RegexOptions.IgnoreCase); result = Regex.Replace(result, @"{{\s*ROOT_PATH(?:\s*\(\s*(\d+)\s*\))?\s*}}", match => { var segments = context.Segments ?? Array.Empty(); if (!match.Groups[1].Success) return context.Path ?? string.Empty; if (!int.TryParse(match.Groups[1].Value, out var segmentCount) || segmentCount <= 0) return string.Empty; var take = Math.Min(segmentCount, segments.Length); return take == 0 ? string.Empty : JoinSanitizedPathSegments(segments.Skip(segments.Length - take), context.PathSegmentSeparator); }, RegexOptions.IgnoreCase); result = Regex.Replace(result, @"{{\s*ROOT_SEGMENT\s*\(\s*(\d+)\s*\)\s*}}", match => { var segments = context.Segments ?? Array.Empty(); if (!int.TryParse(match.Groups[1].Value, out var segmentIndex) || segmentIndex < 0 || segmentIndex >= segments.Length) return string.Empty; return segments[segmentIndex] ?? string.Empty; }, RegexOptions.IgnoreCase); return result; } private static string NormalizeLoopPlaceholderLength(string templateValue) { if (string.IsNullOrWhiteSpace(templateValue)) return templateValue ?? string.Empty; return Regex.Replace( templateValue, @"{{(?[^}]*)(?LOOP)(?[^{]*)}}", match => match.Groups["prefix"].Value + new string('9', MaxAdGroupLoopDigits) + match.Groups["postfix"].Value, RegexOptions.IgnoreCase); } private static int GetMeasuredTemplateLength(string templateValue) { return NormalizeLoopPlaceholderLength(templateValue).Length; } private static string[] SplitPathSegments(string path) { return (path ?? string.Empty) .Trim() .Replace('/', '\\') .Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); } private static string GetCurrentRelativePath(string[] sanitizedSegments, string fallbackRelativePath, string pathSegmentSeparator) { if (sanitizedSegments != null && sanitizedSegments.Length > 0) return JoinSanitizedPathSegments(sanitizedSegments, pathSegmentSeparator); return fallbackRelativePath ?? string.Empty; } private static int GetLoopReservationLength(string templateValue) { if (string.IsNullOrWhiteSpace(templateValue)) return 0; var reservation = 0; foreach (Match match in Regex.Matches(templateValue, @"{{(?[^}]*)(?LOOP)(?[^{]*)}}", RegexOptions.IgnoreCase)) { reservation += match.Groups["prefix"].Value.Length + MaxAdGroupLoopDigits + match.Groups["postfix"].Value.Length; } return reservation; } private static bool TryShortenRelativePath(ref string[] segments) { if (segments == null || segments.Length == 0) return false; var lastIndex = segments.Length - 1; if (segments.Length > 2) { var candidateIndex = -1; var candidateLength = MinLeadingRelativePathSegmentLength; for (var i = 0; i < lastIndex; i++) { if (string.IsNullOrWhiteSpace(segments[i]) || segments[i].Length <= candidateLength) continue; candidateIndex = i; candidateLength = segments[i].Length; } if (candidateIndex >= 0) { segments[candidateIndex] = segments[candidateIndex].Substring(0, segments[candidateIndex].Length - 1); return true; } segments = segments.Skip(1).ToArray(); return true; } if (segments.Length == 2) { if (segments[0].Length > MinSingleLeadingRelativePathSegmentLength) { segments[0] = segments[0].Substring(0, segments[0].Length - 1); return true; } if (segments[lastIndex].Length > MinLastRelativePathSegmentLength) { segments[lastIndex] = segments[lastIndex].Substring(0, segments[lastIndex].Length - 1); return true; } if (segments[0].Length > 1) { segments[0] = segments[0].Substring(0, segments[0].Length - 1); return true; } if (segments[lastIndex].Length > 1) { segments[lastIndex] = segments[lastIndex].Substring(0, segments[lastIndex].Length - 1); return true; } return false; } if (segments[lastIndex].Length > 1) { segments[lastIndex] = segments[lastIndex].Substring(0, segments[lastIndex].Length - 1); return true; } return false; } private static bool TryShortenName(ref string folderName) { if (string.IsNullOrWhiteSpace(folderName) || folderName.Length <= 1) return false; folderName = folderName.Substring(0, folderName.Length - 1); return true; } } }