// // Copyright (c) StellaOps. Licensed under the BUSL-1.1. // using System.Collections.Concurrent; using System.Collections.Immutable; using System.Text.Json; using StellaOps.Replay.Core.FeedSnapshots; namespace StellaOps.Replay.WebService; internal sealed class InMemoryFeedSnapshotBlobStore : IFeedSnapshotBlobStore { private readonly ConcurrentDictionary _blobs = new(StringComparer.Ordinal); public Task StoreAsync(FeedSnapshotBlob blob, CancellationToken ct = default) { _blobs[blob.Digest] = blob; return Task.CompletedTask; } public Task GetByDigestAsync(string digest, CancellationToken ct = default) { _blobs.TryGetValue(digest, out var blob); return Task.FromResult(blob); } public Task ExistsAsync(string digest, CancellationToken ct = default) { return Task.FromResult(_blobs.ContainsKey(digest)); } public Task DeleteAsync(string digest, CancellationToken ct = default) { _blobs.TryRemove(digest, out _); return Task.CompletedTask; } } internal sealed class InMemoryFeedSnapshotIndexStore : IFeedSnapshotIndexStore { private readonly ConcurrentDictionary> _index = new(StringComparer.Ordinal); public Task IndexSnapshotAsync(FeedSnapshotIndexEntry entry, CancellationToken ct = default) { var entries = _index.GetOrAdd(entry.ProviderId, static _ => []); lock (entries) { entries.Add(entry); } return Task.CompletedTask; } public Task FindSnapshotAtTimeAsync( string providerId, DateTimeOffset pointInTime, CancellationToken ct = default) { if (!_index.TryGetValue(providerId, out var entries)) { return Task.FromResult(null); } lock (entries) { var found = entries .Where(e => e.CapturedAt <= pointInTime) .OrderByDescending(e => e.CapturedAt) .FirstOrDefault(); return Task.FromResult(found); } } public Task> ListSnapshotsAsync( string providerId, DateTimeOffset from, DateTimeOffset to, int limit, CancellationToken ct = default) { if (!_index.TryGetValue(providerId, out var entries)) { return Task.FromResult(ImmutableArray.Empty); } lock (entries) { IEnumerable query = entries .Where(e => e.CapturedAt >= from && e.CapturedAt <= to) .OrderBy(e => e.CapturedAt); if (limit > 0) { query = query.Take(limit); } return Task.FromResult(query.ToImmutableArray()); } } } internal sealed class JsonAdvisoryExtractor : IAdvisoryExtractor { public Task ExtractAdvisoryAsync( string cveId, byte[] content, FeedSnapshotFormat format, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(cveId) || content.Length == 0) { return Task.FromResult(null); } try { using var doc = JsonDocument.Parse(content); var advisory = TryExtract(cveId, doc.RootElement); return Task.FromResult(advisory); } catch (JsonException) { return Task.FromResult(null); } } private static AdvisoryData? TryExtract(string cveId, JsonElement root) { if (root.ValueKind == JsonValueKind.Object) { if (TryParseAdvisoryObject(cveId, root, out var direct)) { return direct; } if (TryGetPropertyIgnoreCase(root, "advisories", out var advisories) && advisories.ValueKind == JsonValueKind.Array) { foreach (var item in advisories.EnumerateArray()) { if (item.ValueKind == JsonValueKind.Object && TryParseAdvisoryObject(cveId, item, out var parsed)) { return parsed; } } } if (TryGetPropertyIgnoreCase(root, "cves", out var cves)) { if (cves.ValueKind == JsonValueKind.Object) { foreach (var prop in cves.EnumerateObject()) { if (string.Equals(prop.Name, cveId, StringComparison.OrdinalIgnoreCase) && prop.Value.ValueKind == JsonValueKind.Object && TryParseAdvisoryObject(cveId, prop.Value, out var parsed)) { return parsed; } } } if (cves.ValueKind == JsonValueKind.Array) { foreach (var item in cves.EnumerateArray()) { if (item.ValueKind == JsonValueKind.Object && TryParseAdvisoryObject(cveId, item, out var parsed)) { return parsed; } } } } } if (root.ValueKind == JsonValueKind.Array) { foreach (var item in root.EnumerateArray()) { if (item.ValueKind == JsonValueKind.Object && TryParseAdvisoryObject(cveId, item, out var parsed)) { return parsed; } } } return null; } private static bool TryParseAdvisoryObject(string requestedCveId, JsonElement obj, out AdvisoryData? advisory) { advisory = null; var cveId = GetString(obj, "cveId", "cve", "cve_id", "id"); if (string.IsNullOrWhiteSpace(cveId)) { return false; } if (!string.Equals(cveId, requestedCveId, StringComparison.OrdinalIgnoreCase)) { return false; } advisory = new AdvisoryData { CveId = cveId, Severity = GetString(obj, "severity"), CvssScore = GetDecimal(obj, "cvssScore", "cvss", "score"), CvssVector = GetString(obj, "cvssVector", "vector"), Description = GetString(obj, "description", "summary"), FixStatus = GetString(obj, "fixStatus", "fix_state", "status"), AffectedProducts = GetStringArray(obj, "affectedProducts", "affected_products", "packages"), References = GetStringArray(obj, "references", "refs", "links"), PublishedAt = GetDateTimeOffset(obj, "publishedAt", "published_at"), LastModifiedAt = GetDateTimeOffset(obj, "lastModifiedAt", "last_modified_at") }; return true; } private static string? GetString(JsonElement obj, params string[] names) { foreach (var name in names) { if (TryGetPropertyIgnoreCase(obj, name, out var value)) { if (value.ValueKind == JsonValueKind.String) { return value.GetString(); } if (value.ValueKind != JsonValueKind.Null && value.ValueKind != JsonValueKind.Undefined) { return value.ToString(); } } } return null; } private static decimal? GetDecimal(JsonElement obj, params string[] names) { foreach (var name in names) { if (!TryGetPropertyIgnoreCase(obj, name, out var value)) { continue; } if (value.ValueKind == JsonValueKind.Number && value.TryGetDecimal(out var num)) { return num; } if (value.ValueKind == JsonValueKind.String && decimal.TryParse(value.GetString(), out var parsed)) { return parsed; } } return null; } private static DateTimeOffset? GetDateTimeOffset(JsonElement obj, params string[] names) { foreach (var name in names) { if (!TryGetPropertyIgnoreCase(obj, name, out var value)) { continue; } if (value.ValueKind == JsonValueKind.String && DateTimeOffset.TryParse(value.GetString(), out var parsed)) { return parsed; } } return null; } private static ImmutableArray GetStringArray(JsonElement obj, params string[] names) { foreach (var name in names) { if (!TryGetPropertyIgnoreCase(obj, name, out var value) || value.ValueKind != JsonValueKind.Array) { continue; } var values = new List(); foreach (var item in value.EnumerateArray()) { if (item.ValueKind == JsonValueKind.String) { var str = item.GetString(); if (!string.IsNullOrWhiteSpace(str)) { values.Add(str); } } } return values.ToImmutableArray(); } return ImmutableArray.Empty; } private static bool TryGetPropertyIgnoreCase(JsonElement obj, string name, out JsonElement value) { foreach (var prop in obj.EnumerateObject()) { if (string.Equals(prop.Name, name, StringComparison.OrdinalIgnoreCase)) { value = prop.Value; return true; } } value = default; return false; } }