using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.Json; namespace StellaOps.Scanner.Analyzers.Lang.Go.Internal; internal static class GoBuildInfoParser { private const string PathPrefix = "path\t"; private const string ModulePrefix = "mod\t"; private const string DependencyPrefix = "dep\t"; private const string ReplacementPrefix = "=>\t"; private const string BuildPrefix = "build\t"; public static bool TryParse(string goVersion, string absoluteBinaryPath, string rawModuleData, out GoBuildInfo? info) { info = null; if (string.IsNullOrWhiteSpace(goVersion) || string.IsNullOrWhiteSpace(rawModuleData)) { return false; } string? modulePath = null; GoModule? mainModule = null; var dependencies = new List(); var settings = new SortedDictionary(StringComparer.Ordinal); GoModule? lastModule = null; using var reader = new StringReader(rawModuleData); while (reader.ReadLine() is { } line) { if (string.IsNullOrWhiteSpace(line)) { continue; } if (line.StartsWith(PathPrefix, StringComparison.Ordinal)) { modulePath = line[PathPrefix.Length..].Trim(); continue; } if (line.StartsWith(ModulePrefix, StringComparison.Ordinal)) { mainModule = ParseModule(line.AsSpan(ModulePrefix.Length), isMain: true); lastModule = mainModule; continue; } if (line.StartsWith(DependencyPrefix, StringComparison.Ordinal)) { var dependency = ParseModule(line.AsSpan(DependencyPrefix.Length), isMain: false); if (dependency is not null) { dependencies.Add(dependency); lastModule = dependency; } continue; } if (line.StartsWith(ReplacementPrefix, StringComparison.Ordinal)) { if (lastModule is null) { continue; } var replacement = ParseReplacement(line.AsSpan(ReplacementPrefix.Length)); if (replacement is not null) { lastModule.SetReplacement(replacement); } continue; } if (line.StartsWith(BuildPrefix, StringComparison.Ordinal)) { var pair = ParseBuildSetting(line.AsSpan(BuildPrefix.Length)); if (!string.IsNullOrEmpty(pair.Key)) { settings[pair.Key] = pair.Value; } } } if (mainModule is null) { return false; } if (string.IsNullOrEmpty(modulePath)) { modulePath = mainModule.Path; } info = new GoBuildInfo( goVersion, absoluteBinaryPath, modulePath, mainModule, dependencies, settings); return true; } private static GoModule? ParseModule(ReadOnlySpan span, bool isMain) { var fields = SplitFields(span, expected: 4); if (fields.Count == 0) { return null; } var path = fields[0]; if (string.IsNullOrWhiteSpace(path)) { return null; } var version = fields.Count > 1 ? fields[1] : null; var sum = fields.Count > 2 ? fields[2] : null; return new GoModule(path, version, sum, isMain); } private static GoModuleReplacement? ParseReplacement(ReadOnlySpan span) { var fields = SplitFields(span, expected: 3); if (fields.Count == 0) { return null; } var path = fields[0]; if (string.IsNullOrWhiteSpace(path)) { return null; } var version = fields.Count > 1 ? fields[1] : null; var sum = fields.Count > 2 ? fields[2] : null; return new GoModuleReplacement(path, version, sum); } private static KeyValuePair ParseBuildSetting(ReadOnlySpan span) { span = span.Trim(); if (span.IsEmpty) { return default; } var separatorIndex = span.IndexOf('='); if (separatorIndex <= 0) { return default; } var rawKey = span[..separatorIndex].Trim(); var rawValue = span[(separatorIndex + 1)..].Trim(); var key = Unquote(rawKey.ToString()); if (string.IsNullOrWhiteSpace(key)) { return default; } var value = Unquote(rawValue.ToString()); return new KeyValuePair(key, value); } private static List SplitFields(ReadOnlySpan span, int expected) { var fields = new List(expected); var builder = new StringBuilder(); for (var i = 0; i < span.Length; i++) { var current = span[i]; if (current == '\t') { fields.Add(builder.ToString()); builder.Clear(); continue; } builder.Append(current); } fields.Add(builder.ToString()); return fields; } private static string Unquote(string value) { if (string.IsNullOrEmpty(value)) { return value; } value = value.Trim(); if (value.Length < 2) { return value; } if (value[0] == '"' && value[^1] == '"') { try { return JsonSerializer.Deserialize(value) ?? value; } catch (JsonException) { return value; } } if (value[0] == '`' && value[^1] == '`') { return value[1..^1]; } return value; } }