Add Canonical JSON serialization library with tests and documentation

- Implemented CanonJson class for deterministic JSON serialization and hashing.
- Added unit tests for CanonJson functionality, covering various scenarios including key sorting, handling of nested objects, arrays, and special characters.
- Created project files for the Canonical JSON library and its tests, including necessary package references.
- Added README.md for library usage and API reference.
- Introduced RabbitMqIntegrationFactAttribute for conditional RabbitMQ integration tests.
This commit is contained in:
master
2025-12-19 15:35:00 +02:00
parent 43882078a4
commit 951a38d561
192 changed files with 27550 additions and 2611 deletions

View File

@@ -0,0 +1,149 @@
// -----------------------------------------------------------------------------
// NodeMethodKeyBuilder.cs
// Sprint: SPRINT_3700_0002_0001_vuln_surfaces_core (SURF-012)
// Description: Method key builder for Node.js/npm packages.
// -----------------------------------------------------------------------------
using System.Text;
using System.Text.RegularExpressions;
namespace StellaOps.Scanner.VulnSurfaces.MethodKeys;
/// <summary>
/// Builds normalized method keys for JavaScript/Node.js modules.
/// Format: module.path::functionName(param1,param2) or module.path.ClassName::methodName(params)
/// </summary>
public sealed partial class NodeMethodKeyBuilder : IMethodKeyBuilder
{
// Pattern: module.path[.ClassName]::methodName(params)
[GeneratedRegex(@"^([^:]+)::([^(]+)\(([^)]*)\)$", RegexOptions.Compiled)]
private static partial Regex MethodKeyPattern();
/// <inheritdoc />
public string Ecosystem => "npm";
/// <inheritdoc />
public string BuildKey(MethodKeyRequest request)
{
ArgumentNullException.ThrowIfNull(request);
var sb = new StringBuilder();
// Module path
if (!string.IsNullOrEmpty(request.Namespace))
{
sb.Append(NormalizeModulePath(request.Namespace));
}
// Class name (if any)
if (!string.IsNullOrEmpty(request.TypeName))
{
if (sb.Length > 0)
{
sb.Append('.');
}
sb.Append(request.TypeName);
}
// ::functionName
sb.Append("::");
sb.Append(request.MethodName);
// (params)
sb.Append('(');
if (request.ParameterTypes is { Count: > 0 })
{
sb.Append(string.Join(",", request.ParameterTypes));
}
sb.Append(')');
return sb.ToString();
}
/// <inheritdoc />
public MethodKeyComponents? ParseKey(string methodKey)
{
if (string.IsNullOrEmpty(methodKey))
return null;
var match = MethodKeyPattern().Match(methodKey);
if (!match.Success)
return null;
var modulePath = match.Groups[1].Value;
var methodName = match.Groups[2].Value;
var parameters = match.Groups[3].Value;
// Try to extract class name from module path
string? typeName = null;
var lastDot = modulePath.LastIndexOf('.');
if (lastDot > 0)
{
var lastPart = modulePath[(lastDot + 1)..];
// Check if it looks like a class name (starts with uppercase)
if (char.IsUpper(lastPart[0]))
{
typeName = lastPart;
modulePath = modulePath[..lastDot];
}
}
var paramTypes = string.IsNullOrEmpty(parameters)
? []
: parameters.Split(',').Select(p => p.Trim()).ToList();
return new MethodKeyComponents
{
Namespace = modulePath,
TypeName = typeName,
MethodName = methodName,
ParameterTypes = paramTypes
};
}
/// <inheritdoc />
public string NormalizeKey(string methodKey)
{
var components = ParseKey(methodKey);
if (components is null)
return methodKey;
return BuildKey(new MethodKeyRequest
{
Namespace = components.Namespace,
TypeName = components.TypeName,
MethodName = components.MethodName,
ParameterTypes = components.ParameterTypes?.ToList()
});
}
private static string NormalizeModulePath(string path)
{
// Normalize path separators and common patterns
var normalized = path
.Replace('/', '.')
.Replace('\\', '.')
.Replace("..", ".");
// Remove leading/trailing dots
normalized = normalized.Trim('.');
// Remove 'index' from module paths
if (normalized.EndsWith(".index", StringComparison.OrdinalIgnoreCase))
{
normalized = normalized[..^6];
}
// Remove common prefixes like 'src.' or 'lib.'
foreach (var prefix in new[] { "src.", "lib.", "dist." })
{
if (normalized.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
normalized = normalized[prefix.Length..];
break;
}
}
return normalized;
}
}