using System.Text.RegularExpressions; namespace Launcher.Next.Core; public sealed class PackageManifestValidator : IPackageManifestValidator { private static readonly string[] Forbidden = ["acmgd.dll", "acdbmgd.dll", "accoremgd.dll", "adwindows.dll"]; public ValidationResult Validate(PackageManifest manifest, int productYear, string architecture) { var issues = new List(); if (manifest.SchemaVersion != 1) issues.Add(new("schema", "Unsupported schemaVersion.")); if (string.IsNullOrWhiteSpace(manifest.PackageId) || string.IsNullOrWhiteSpace(manifest.Version)) issues.Add(new("identity", "packageId and version are required.")); if (string.IsNullOrWhiteSpace(manifest.Product) || manifest.ProductYear != productYear) issues.Add(new("product", "Package product or year is incompatible.")); if (!string.Equals(manifest.Architecture, architecture, StringComparison.OrdinalIgnoreCase)) issues.Add(new("architecture", "Package architecture is incompatible.")); var paths = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var file in manifest.Files) { if (string.IsNullOrWhiteSpace(file.Path) || Path.IsPathRooted(file.Path) || file.Path.Split(['/', '\\']).Any(x => x is ".." or "")) issues.Add(new("path", $"Invalid package path: {file.Path}")); if (!paths.Add(file.Path)) issues.Add(new("duplicate", $"Duplicate package path: {file.Path}")); var name = Path.GetFileName(file.Path); if (Forbidden.Contains(name, StringComparer.OrdinalIgnoreCase) || name.StartsWith("PnP", StringComparison.OrdinalIgnoreCase) || name.StartsWith("Autodesk.ProcessPower", StringComparison.OrdinalIgnoreCase)) issues.Add(new("autodesk", $"Forbidden Autodesk assembly: {name}")); if (!Regex.IsMatch(file.Sha256 ?? string.Empty, "^[A-Fa-f0-9]{64}$")) issues.Add(new("hash", $"Invalid SHA-256 for {file.Path}")); } if (!paths.Contains(manifest.EntryAssembly)) issues.Add(new("entry", "entryAssembly must be listed in files.")); return issues.Count == 0 ? ValidationResult.Success : new ValidationResult(issues); } }