audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories
This commit is contained in:
203
src/__Libraries/StellaOps.Spdx3/Spdx3VersionDetector.cs
Normal file
203
src/__Libraries/StellaOps.Spdx3/Spdx3VersionDetector.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
// <copyright file="Spdx3VersionDetector.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
|
||||
using System.Text.Json;
|
||||
|
||||
namespace StellaOps.Spdx3;
|
||||
|
||||
/// <summary>
|
||||
/// Detects the SPDX version of a document.
|
||||
/// </summary>
|
||||
public static class Spdx3VersionDetector
|
||||
{
|
||||
/// <summary>
|
||||
/// Detected SPDX version.
|
||||
/// </summary>
|
||||
public enum SpdxVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown version.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// SPDX 2.2.
|
||||
/// </summary>
|
||||
Spdx22,
|
||||
|
||||
/// <summary>
|
||||
/// SPDX 2.3.
|
||||
/// </summary>
|
||||
Spdx23,
|
||||
|
||||
/// <summary>
|
||||
/// SPDX 3.0.1.
|
||||
/// </summary>
|
||||
Spdx301
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Version detection result.
|
||||
/// </summary>
|
||||
/// <param name="Version">The detected version.</param>
|
||||
/// <param name="VersionString">The raw version string if found.</param>
|
||||
/// <param name="IsJsonLd">Whether the document uses JSON-LD format.</param>
|
||||
public readonly record struct DetectionResult(
|
||||
SpdxVersion Version,
|
||||
string? VersionString,
|
||||
bool IsJsonLd);
|
||||
|
||||
/// <summary>
|
||||
/// Detects the SPDX version from a JSON document.
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON content.</param>
|
||||
/// <returns>The detection result.</returns>
|
||||
public static DetectionResult Detect(string json)
|
||||
{
|
||||
using var document = JsonDocument.Parse(json);
|
||||
return Detect(document.RootElement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects the SPDX version from a JSON element.
|
||||
/// </summary>
|
||||
/// <param name="root">The root JSON element.</param>
|
||||
/// <returns>The detection result.</returns>
|
||||
public static DetectionResult Detect(JsonElement root)
|
||||
{
|
||||
// Check for JSON-LD @context (SPDX 3.x indicator)
|
||||
if (root.TryGetProperty("@context", out var context))
|
||||
{
|
||||
var contextStr = GetContextString(context);
|
||||
if (!string.IsNullOrEmpty(contextStr))
|
||||
{
|
||||
// Check for specific 3.0.1 context
|
||||
if (contextStr.Contains("3.0.1", StringComparison.OrdinalIgnoreCase) ||
|
||||
contextStr.Contains("spdx.org/rdf/3", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DetectionResult(SpdxVersion.Spdx301, "3.0.1", true);
|
||||
}
|
||||
|
||||
// Generic 3.x detection
|
||||
if (contextStr.Contains("spdx.org/rdf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DetectionResult(SpdxVersion.Spdx301, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Has @context but couldn't determine specific version
|
||||
return new DetectionResult(SpdxVersion.Spdx301, null, true);
|
||||
}
|
||||
|
||||
// Check for SPDX 2.x spdxVersion field
|
||||
if (root.TryGetProperty("spdxVersion", out var spdxVersion) &&
|
||||
spdxVersion.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var versionStr = spdxVersion.GetString();
|
||||
if (!string.IsNullOrEmpty(versionStr))
|
||||
{
|
||||
if (versionStr.Contains("2.3", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DetectionResult(SpdxVersion.Spdx23, versionStr, false);
|
||||
}
|
||||
|
||||
if (versionStr.Contains("2.2", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DetectionResult(SpdxVersion.Spdx22, versionStr, false);
|
||||
}
|
||||
|
||||
// Older 2.x versions
|
||||
if (versionStr.StartsWith("SPDX-2", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DetectionResult(SpdxVersion.Spdx22, versionStr, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for creationInfo.specVersion (SPDX 3.x in @graph format)
|
||||
if (root.TryGetProperty("@graph", out var graph) && graph.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var element in graph.EnumerateArray())
|
||||
{
|
||||
if (element.TryGetProperty("creationInfo", out var creationInfo) &&
|
||||
creationInfo.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
if (creationInfo.TryGetProperty("specVersion", out var specVersion) &&
|
||||
specVersion.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var specVersionStr = specVersion.GetString();
|
||||
if (specVersionStr == "3.0.1")
|
||||
{
|
||||
return new DetectionResult(SpdxVersion.Spdx301, specVersionStr, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new DetectionResult(SpdxVersion.Unknown, null, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects the SPDX version from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The input stream.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The detection result.</returns>
|
||||
public static async Task<DetectionResult> DetectAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var document = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return Detect(document.RootElement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recommended parser for the detected version.
|
||||
/// </summary>
|
||||
/// <param name="version">The detected version.</param>
|
||||
/// <returns>Parser recommendation.</returns>
|
||||
public static string GetParserRecommendation(SpdxVersion version) => version switch
|
||||
{
|
||||
SpdxVersion.Spdx22 => "Use SpdxParser (SPDX 2.x parser)",
|
||||
SpdxVersion.Spdx23 => "Use SpdxParser (SPDX 2.x parser)",
|
||||
SpdxVersion.Spdx301 => "Use Spdx3Parser (SPDX 3.0.1 parser)",
|
||||
_ => "Unknown format - manual inspection required"
|
||||
};
|
||||
|
||||
private static string? GetContextString(JsonElement context)
|
||||
{
|
||||
if (context.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return context.GetString();
|
||||
}
|
||||
|
||||
if (context.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var item in context.EnumerateArray())
|
||||
{
|
||||
if (item.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var str = item.GetString();
|
||||
if (!string.IsNullOrEmpty(str) && str.Contains("spdx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return first string if no spdx-specific one found
|
||||
foreach (var item in context.EnumerateArray())
|
||||
{
|
||||
if (item.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
return item.GetString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user