276 lines
7.5 KiB
C#
Executable File
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;
|
|
}
|
|
}
|
|
} |