using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; namespace MarketAlly.AIPlugin.Refactoring.Plugins { public static class InputValidator { private static readonly HashSet DangerousFileExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { ".exe", ".dll", ".bat", ".cmd", ".ps1", ".vbs", ".js", ".jar", ".msi", ".scr" }; private static readonly Regex SafeFilePathPattern = new Regex(@"^[a-zA-Z]:[\\\/](?:[^<>:""|?*\r\n]+[\\\/])*[^<>:""|?*\r\n]*$", RegexOptions.Compiled); private static readonly Regex SafeIdentifierPattern = new Regex(@"^[a-zA-Z_][a-zA-Z0-9_]*$", RegexOptions.Compiled); public class ValidationResult { public bool IsValid { get; set; } public string ErrorMessage { get; set; } public List Warnings { get; set; } = new List(); } public static ValidationResult ValidateFilePath(string filePath, bool mustExist = true, bool allowDirectories = true) { var result = new ValidationResult(); if (string.IsNullOrWhiteSpace(filePath)) { result.ErrorMessage = "File path cannot be null or empty"; return result; } // Check for path traversal attempts if (filePath.Contains("..") || filePath.Contains("~")) { result.ErrorMessage = "Path traversal attempts are not allowed"; return result; } // Validate path format if (!SafeFilePathPattern.IsMatch(filePath)) { result.ErrorMessage = "Invalid file path format"; return result; } // Check for dangerous file extensions var extension = Path.GetExtension(filePath); if (DangerousFileExtensions.Contains(extension)) { result.ErrorMessage = $"File extension '{extension}' is not allowed for security reasons"; return result; } // Check existence if required if (mustExist) { if (allowDirectories && Directory.Exists(filePath)) { result.IsValid = true; return result; } if (File.Exists(filePath)) { result.IsValid = true; return result; } result.ErrorMessage = "File or directory does not exist"; return result; } // Check if parent directory exists for new files var parentDir = Path.GetDirectoryName(filePath); if (!string.IsNullOrEmpty(parentDir) && !Directory.Exists(parentDir)) { result.ErrorMessage = "Parent directory does not exist"; return result; } result.IsValid = true; return result; } public static ValidationResult ValidateIdentifier(string identifier, string context = "identifier") { var result = new ValidationResult(); if (string.IsNullOrWhiteSpace(identifier)) { result.ErrorMessage = $"{context} cannot be null or empty"; return result; } if (!SafeIdentifierPattern.IsMatch(identifier)) { result.ErrorMessage = $"{context} contains invalid characters"; return result; } // Check for C# reserved keywords var reservedKeywords = new HashSet(StringComparer.OrdinalIgnoreCase) { "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void", "volatile", "while" }; if (reservedKeywords.Contains(identifier)) { result.ErrorMessage = $"{context} cannot be a C# reserved keyword"; return result; } result.IsValid = true; return result; } public static ValidationResult ValidateNumericRange(T value, T min, T max, string parameterName) where T : IComparable { var result = new ValidationResult(); if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) { result.ErrorMessage = $"{parameterName} must be between {min} and {max}"; return result; } result.IsValid = true; return result; } public static ValidationResult ValidateString(string value, string parameterName, int minLength = 0, int maxLength = int.MaxValue, bool allowEmpty = false) { var result = new ValidationResult(); if (string.IsNullOrEmpty(value)) { if (!allowEmpty) { result.ErrorMessage = $"{parameterName} cannot be null or empty"; return result; } } else { if (value.Length < minLength) { result.ErrorMessage = $"{parameterName} must be at least {minLength} characters long"; return result; } if (value.Length > maxLength) { result.ErrorMessage = $"{parameterName} cannot exceed {maxLength} characters"; return result; } // Check for potentially dangerous content if (value.Contains('\0')) { result.ErrorMessage = $"{parameterName} contains null characters"; return result; } } result.IsValid = true; return result; } public static ValidationResult ValidateCollection(IEnumerable collection, string parameterName, int minCount = 0, int maxCount = int.MaxValue) { var result = new ValidationResult(); if (collection == null) { result.ErrorMessage = $"{parameterName} cannot be null"; return result; } var count = collection.Count(); if (count < minCount) { result.ErrorMessage = $"{parameterName} must contain at least {minCount} items"; return result; } if (count > maxCount) { result.ErrorMessage = $"{parameterName} cannot contain more than {maxCount} items"; return result; } result.IsValid = true; return result; } public static ValidationResult ValidateUrl(string url, string parameterName, bool requireHttps = true) { var result = new ValidationResult(); if (string.IsNullOrWhiteSpace(url)) { result.ErrorMessage = $"{parameterName} cannot be null or empty"; return result; } if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) { result.ErrorMessage = $"{parameterName} is not a valid URL"; return result; } if (requireHttps && uri.Scheme.ToLower() != "https") { result.ErrorMessage = $"{parameterName} must use HTTPS"; return result; } if (uri.Scheme.ToLower() != "http" && uri.Scheme.ToLower() != "https") { result.ErrorMessage = $"{parameterName} must use HTTP or HTTPS scheme"; return result; } result.IsValid = true; return result; } public static void ThrowIfInvalid(ValidationResult result) { if (!result.IsValid) { throw new ArgumentException(result.ErrorMessage); } } public static ValidationResult CombineResults(params ValidationResult[] results) { var combinedResult = new ValidationResult { IsValid = true }; var errors = new List(); var warnings = new List(); foreach (var result in results) { if (!result.IsValid) { combinedResult.IsValid = false; if (!string.IsNullOrEmpty(result.ErrorMessage)) { errors.Add(result.ErrorMessage); } } warnings.AddRange(result.Warnings); } if (!combinedResult.IsValid) { combinedResult.ErrorMessage = string.Join("; ", errors); } combinedResult.Warnings = warnings; return combinedResult; } } }