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:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user