461 lines
21 KiB
C#
461 lines
21 KiB
C#
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 string AdUnsafeGroupNameCharactersPattern = @"[\x00-\x1F\s\-\/\\\[\]:;\|=,\+\*\?<>\@()'""]";
|
|
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<string>();
|
|
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<string>();
|
|
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, @"(?<loopTag>{{(?<prefix>[^}]*)(?<loop>LOOP)(?<postfix>[^{]*)}})", loop <= 0 ? "" : "${prefix}" + loop + "${postfix}");
|
|
}
|
|
public static string ReplaceTags(this string str, IDictionary<string, string> 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<string, string> replacementTags,
|
|
int maxLength,
|
|
string logContext,
|
|
string valueLabel = "AD-Gruppenname",
|
|
RootPathTemplateContext rootContext = null,
|
|
bool preserveCase = false,
|
|
string pathSegmentSeparator = DefaultGroupNameSanitizeReplacement)
|
|
{
|
|
var effectiveSegments = (sanitizedSegments ?? Array.Empty<string>()).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, AdUnsafeGroupNameCharactersPattern, match => replacement);
|
|
}
|
|
|
|
public static string NormalizeGroupNameSanitizeReplacement(string replacement)
|
|
{
|
|
if (replacement == null)
|
|
return DefaultGroupNameSanitizeReplacement;
|
|
|
|
var trimmed = replacement.Trim();
|
|
if (trimmed.Equals("<empty>", 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<string> 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<string, string> 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<string, string> 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<string>();
|
|
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<string>();
|
|
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,
|
|
@"{{(?<prefix>[^}]*)(?<loop>LOOP)(?<postfix>[^{]*)}}",
|
|
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, @"{{(?<prefix>[^}]*)(?<loop>LOOP)(?<postfix>[^{]*)}}", 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;
|
|
}
|
|
}
|
|
}
|