MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Refacto.../InputValidator.cs

276 lines
7.5 KiB
C#
Executable File

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<string> DangerousFileExtensions = new HashSet<string>(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<string> Warnings { get; set; } = new List<string>();
}
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<string>(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>(T value, T min, T max, string parameterName) where T : IComparable<T>
{
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<T>(IEnumerable<T> 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<string>();
var warnings = new List<string>();
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;
}
}
}