167 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			167 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | ||
| using System.Collections.Generic;
 | ||
| using System.Collections.Immutable;
 | ||
| using System.Linq;
 | ||
| using System.Text.RegularExpressions;
 | ||
| 
 | ||
| namespace StellaOps.Concelier.Models;
 | ||
| 
 | ||
| public static class AliasSchemeRegistry
 | ||
| {
 | ||
|     private sealed record AliasScheme(
 | ||
|         string Name,
 | ||
|         Func<string?, bool> Predicate,
 | ||
|         Func<string?, string> Normalizer);
 | ||
| 
 | ||
| private static readonly AliasScheme[] SchemeDefinitions =
 | ||
|     {
 | ||
|         BuildScheme(AliasSchemes.Cve, alias => alias is not null && Matches(CvERegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "CVE")),
 | ||
|         BuildScheme(AliasSchemes.Ghsa, alias => alias is not null && Matches(GhsaRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "GHSA")),
 | ||
|         BuildScheme(AliasSchemes.OsV, alias => alias is not null && Matches(OsVRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "OSV")),
 | ||
|         BuildScheme(AliasSchemes.Jvn, alias => alias is not null && Matches(JvnRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "JVN")),
 | ||
|         BuildScheme(AliasSchemes.Jvndb, alias => alias is not null && Matches(JvndbRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "JVNDB")),
 | ||
|         BuildScheme(AliasSchemes.Bdu, alias => alias is not null && Matches(BduRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "BDU")),
 | ||
|         BuildScheme(AliasSchemes.Vu, alias => alias is not null && alias.StartsWith("VU#", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "VU", preserveSeparator: '#')),
 | ||
|         BuildScheme(AliasSchemes.Msrc, alias => alias is not null && alias.StartsWith("MSRC-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "MSRC")),
 | ||
|         BuildScheme(AliasSchemes.CiscoSa, alias => alias is not null && alias.StartsWith("CISCO-SA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "CISCO-SA")),
 | ||
|         BuildScheme(AliasSchemes.OracleCpu, alias => alias is not null && alias.StartsWith("ORACLE-CPU", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "ORACLE-CPU")),
 | ||
|         BuildScheme(AliasSchemes.Apsb, alias => alias is not null && alias.StartsWith("APSB-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "APSB")),
 | ||
|         BuildScheme(AliasSchemes.Apa, alias => alias is not null && alias.StartsWith("APA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "APA")),
 | ||
|         BuildScheme(AliasSchemes.AppleHt, alias => alias is not null && alias.StartsWith("APPLE-HT", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "APPLE-HT")),
 | ||
|         BuildScheme(AliasSchemes.ChromiumPost, alias => alias is not null && (alias.StartsWith("CHROMIUM-POST", StringComparison.OrdinalIgnoreCase) || alias.StartsWith("CHROMIUM:", StringComparison.OrdinalIgnoreCase)), NormalizeChromium),
 | ||
|         BuildScheme(AliasSchemes.Vmsa, alias => alias is not null && alias.StartsWith("VMSA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "VMSA")),
 | ||
|         BuildScheme(AliasSchemes.Rhsa, alias => alias is not null && alias.StartsWith("RHSA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "RHSA")),
 | ||
|         BuildScheme(AliasSchemes.Usn, alias => alias is not null && alias.StartsWith("USN-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "USN")),
 | ||
|         BuildScheme(AliasSchemes.Dsa, alias => alias is not null && alias.StartsWith("DSA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "DSA")),
 | ||
|         BuildScheme(AliasSchemes.SuseSu, alias => alias is not null && alias.StartsWith("SUSE-SU-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "SUSE-SU")),
 | ||
|         BuildScheme(AliasSchemes.Icsa, alias => alias is not null && alias.StartsWith("ICSA-", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "ICSA")),
 | ||
|         BuildScheme(AliasSchemes.Cwe, alias => alias is not null && Matches(CweRegex, alias), alias => alias is null ? string.Empty : NormalizePrefix(alias, "CWE")),
 | ||
|         BuildScheme(AliasSchemes.Cpe, alias => alias is not null && alias.StartsWith("cpe:", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "cpe", uppercase:false)),
 | ||
|         BuildScheme(AliasSchemes.Purl, alias => alias is not null && alias.StartsWith("pkg:", StringComparison.OrdinalIgnoreCase), alias => NormalizePrefix(alias, "pkg", uppercase:false)),
 | ||
|     };
 | ||
| 
 | ||
|     private static AliasScheme BuildScheme(string name, Func<string?, bool> predicate, Func<string?, string> normalizer)
 | ||
|         => new(
 | ||
|             name,
 | ||
|             predicate,
 | ||
|             alias => normalizer(alias));
 | ||
| 
 | ||
|     private static readonly ImmutableHashSet<string> SchemeNames = SchemeDefinitions
 | ||
|         .Select(static scheme => scheme.Name)
 | ||
|         .ToImmutableHashSet(StringComparer.OrdinalIgnoreCase);
 | ||
| 
 | ||
|     private static readonly Regex CvERegex = new("^CVE-\\d{4}-\\d{4,}$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
 | ||
|     private static readonly Regex GhsaRegex = new("^GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
 | ||
|     private static readonly Regex OsVRegex = new("^OSV-\\d{4}-\\d+$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
 | ||
|     private static readonly Regex JvnRegex = new("^JVN-\\d{4}-\\d{6}$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
 | ||
|     private static readonly Regex JvndbRegex = new("^JVNDB-\\d{4}-\\d{6}$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
 | ||
|     private static readonly Regex BduRegex = new("^BDU-\\d{4}-\\d+$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
 | ||
|     private static readonly Regex CweRegex = new("^CWE-\\d+$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
 | ||
| 
 | ||
|     public static IReadOnlyCollection<string> KnownSchemes => SchemeNames;
 | ||
| 
 | ||
|     public static bool IsKnownScheme(string? scheme)
 | ||
|         => !string.IsNullOrWhiteSpace(scheme) && SchemeNames.Contains(scheme);
 | ||
| 
 | ||
|     public static bool TryGetScheme(string? alias, out string scheme)
 | ||
|     {
 | ||
|         if (string.IsNullOrWhiteSpace(alias))
 | ||
|         {
 | ||
|             scheme = string.Empty;
 | ||
|             return false;
 | ||
|         }
 | ||
| 
 | ||
|         var candidate = alias.Trim();
 | ||
|         foreach (var entry in SchemeDefinitions)
 | ||
|         {
 | ||
|             if (entry.Predicate(candidate))
 | ||
|             {
 | ||
|                 scheme = entry.Name;
 | ||
|                 return true;
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         scheme = string.Empty;
 | ||
|         return false;
 | ||
|     }
 | ||
| 
 | ||
|     public static bool TryNormalize(string? alias, out string normalized, out string scheme)
 | ||
|     {
 | ||
|         normalized = string.Empty;
 | ||
|         scheme = string.Empty;
 | ||
| 
 | ||
|         if (string.IsNullOrWhiteSpace(alias))
 | ||
|         {
 | ||
|             return false;
 | ||
|         }
 | ||
| 
 | ||
|         var candidate = alias.Trim();
 | ||
|         foreach (var entry in SchemeDefinitions)
 | ||
|         {
 | ||
|             if (entry.Predicate(candidate))
 | ||
|             {
 | ||
|                 scheme = entry.Name;
 | ||
|                 normalized = entry.Normalizer(candidate);
 | ||
|                 return true;
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         normalized = candidate;
 | ||
|         return false;
 | ||
|     }
 | ||
| 
 | ||
|     private static string NormalizePrefix(string? alias, string prefix, bool uppercase = true, char? preserveSeparator = null)
 | ||
|     {
 | ||
|         if (string.IsNullOrWhiteSpace(alias))
 | ||
|         {
 | ||
|             return string.Empty;
 | ||
|         }
 | ||
| 
 | ||
|         var comparison = StringComparison.OrdinalIgnoreCase;
 | ||
|         if (!alias.StartsWith(prefix, comparison))
 | ||
|         {
 | ||
|             return uppercase ? alias : alias.ToLowerInvariant();
 | ||
|         }
 | ||
| 
 | ||
|         var remainder = alias[prefix.Length..];
 | ||
|         if (preserveSeparator is { } separator && remainder.Length > 0 && remainder[0] != separator)
 | ||
|         {
 | ||
|             // Edge case: alias is expected to use a specific separator but does not – return unchanged.
 | ||
|             return uppercase ? prefix.ToUpperInvariant() + remainder : prefix + remainder;
 | ||
|         }
 | ||
| 
 | ||
|         var normalizedPrefix = uppercase ? prefix.ToUpperInvariant() : prefix.ToLowerInvariant();
 | ||
|         return normalizedPrefix + remainder;
 | ||
|     }
 | ||
| 
 | ||
|     private static string NormalizeChromium(string? alias)
 | ||
|     {
 | ||
|         if (string.IsNullOrWhiteSpace(alias))
 | ||
|         {
 | ||
|             return string.Empty;
 | ||
|         }
 | ||
| 
 | ||
|         if (alias.StartsWith("CHROMIUM-POST", StringComparison.OrdinalIgnoreCase))
 | ||
|         {
 | ||
|             return NormalizePrefix(alias, "CHROMIUM-POST");
 | ||
|         }
 | ||
| 
 | ||
|         if (alias.StartsWith("CHROMIUM:", StringComparison.OrdinalIgnoreCase))
 | ||
|         {
 | ||
|             var remainder = alias["CHROMIUM".Length..];
 | ||
|             return "CHROMIUM" + remainder;
 | ||
|         }
 | ||
| 
 | ||
|         return alias;
 | ||
|     }
 | ||
|     private static bool Matches(Regex? regex, string? candidate)
 | ||
|     {
 | ||
|         if (regex is null || string.IsNullOrWhiteSpace(candidate))
 | ||
|         {
 | ||
|             return false;
 | ||
|         }
 | ||
| 
 | ||
|         return regex.IsMatch(candidate);
 | ||
|     }
 | ||
| }
 |