Restructure solution layout by module
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Go.Internal;
|
||||
|
||||
internal static class GoBinaryScanner
|
||||
{
|
||||
private static readonly ReadOnlyMemory<byte> BuildInfoMagic = new byte[]
|
||||
{
|
||||
0xFF, (byte)' ', (byte)'G', (byte)'o', (byte)' ', (byte)'b', (byte)'u', (byte)'i', (byte)'l', (byte)'d', (byte)'i', (byte)'n', (byte)'f', (byte)':'
|
||||
};
|
||||
|
||||
private static readonly ReadOnlyMemory<byte> BuildIdMarker = Encoding.ASCII.GetBytes("Go build ID:");
|
||||
private static readonly ReadOnlyMemory<byte> GoPclnTabMarker = Encoding.ASCII.GetBytes(".gopclntab");
|
||||
private static readonly ReadOnlyMemory<byte> GoVersionPrefix = Encoding.ASCII.GetBytes("go1.");
|
||||
|
||||
public static IEnumerable<string> EnumerateCandidateFiles(string rootPath)
|
||||
{
|
||||
var enumeration = new EnumerationOptions
|
||||
{
|
||||
RecurseSubdirectories = true,
|
||||
IgnoreInaccessible = true,
|
||||
AttributesToSkip = FileAttributes.Device | FileAttributes.ReparsePoint,
|
||||
MatchCasing = MatchCasing.CaseSensitive,
|
||||
};
|
||||
|
||||
foreach (var path in Directory.EnumerateFiles(rootPath, "*", enumeration))
|
||||
{
|
||||
yield return path;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryReadBuildInfo(string filePath, out string? goVersion, out string? moduleData)
|
||||
{
|
||||
goVersion = null;
|
||||
moduleData = null;
|
||||
|
||||
FileInfo info;
|
||||
try
|
||||
{
|
||||
info = new FileInfo(filePath);
|
||||
if (!info.Exists || info.Length < 64 || info.Length > 128 * 1024 * 1024)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (System.Security.SecurityException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var length = info.Length;
|
||||
if (length <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inspectLength = (int)Math.Min(length, int.MaxValue);
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(inspectLength);
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
var totalRead = 0;
|
||||
|
||||
while (totalRead < inspectLength)
|
||||
{
|
||||
var read = stream.Read(buffer, totalRead, inspectLength - totalRead);
|
||||
if (read <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
totalRead += read;
|
||||
}
|
||||
|
||||
if (totalRead < 64)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var span = new ReadOnlySpan<byte>(buffer, 0, totalRead);
|
||||
var offset = span.IndexOf(BuildInfoMagic.Span);
|
||||
if (offset < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var view = span[offset..];
|
||||
return GoBuildInfoDecoder.TryDecode(view, out goVersion, out moduleData);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Array.Clear(buffer, 0, inspectLength);
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryClassifyStrippedBinary(string filePath, out GoStrippedBinaryClassification classification)
|
||||
{
|
||||
classification = default;
|
||||
|
||||
FileInfo fileInfo;
|
||||
try
|
||||
{
|
||||
fileInfo = new FileInfo(filePath);
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (System.Security.SecurityException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var length = fileInfo.Length;
|
||||
if (length < 128)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int WindowSize = 128 * 1024;
|
||||
var readSize = (int)Math.Min(length, WindowSize);
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(readSize);
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
var headRead = stream.Read(buffer, 0, readSize);
|
||||
if (headRead <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var headSpan = new ReadOnlySpan<byte>(buffer, 0, headRead);
|
||||
var hasBuildId = headSpan.IndexOf(BuildIdMarker.Span) >= 0;
|
||||
var hasPcln = headSpan.IndexOf(GoPclnTabMarker.Span) >= 0;
|
||||
var goVersion = ExtractGoVersion(headSpan);
|
||||
|
||||
if (length > headRead)
|
||||
{
|
||||
var tailSize = Math.Min(readSize, (int)length);
|
||||
if (tailSize > 0)
|
||||
{
|
||||
stream.Seek(-tailSize, SeekOrigin.End);
|
||||
var tailRead = stream.Read(buffer, 0, tailSize);
|
||||
if (tailRead > 0)
|
||||
{
|
||||
var tailSpan = new ReadOnlySpan<byte>(buffer, 0, tailRead);
|
||||
hasBuildId |= tailSpan.IndexOf(BuildIdMarker.Span) >= 0;
|
||||
hasPcln |= tailSpan.IndexOf(GoPclnTabMarker.Span) >= 0;
|
||||
goVersion ??= ExtractGoVersion(tailSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasBuildId)
|
||||
{
|
||||
classification = new GoStrippedBinaryClassification(
|
||||
filePath,
|
||||
GoStrippedBinaryIndicator.BuildId,
|
||||
goVersion);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasPcln && !string.IsNullOrEmpty(goVersion))
|
||||
{
|
||||
classification = new GoStrippedBinaryClassification(
|
||||
filePath,
|
||||
GoStrippedBinaryIndicator.GoRuntimeMarkers,
|
||||
goVersion);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Array.Clear(buffer, 0, readSize);
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ExtractGoVersion(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var prefix = GoVersionPrefix.Span;
|
||||
var span = data;
|
||||
|
||||
while (!span.IsEmpty)
|
||||
{
|
||||
var index = span.IndexOf(prefix);
|
||||
if (index < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var absoluteIndex = data.Length - span.Length + index;
|
||||
|
||||
if (absoluteIndex > 0)
|
||||
{
|
||||
var previous = (char)data[absoluteIndex - 1];
|
||||
if (char.IsLetterOrDigit(previous))
|
||||
{
|
||||
span = span[(index + 1)..];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var start = absoluteIndex;
|
||||
var end = start + prefix.Length;
|
||||
|
||||
while (end < data.Length && IsVersionCharacter((char)data[end]))
|
||||
{
|
||||
end++;
|
||||
}
|
||||
|
||||
if (end - start <= prefix.Length)
|
||||
{
|
||||
span = span[(index + 1)..];
|
||||
continue;
|
||||
}
|
||||
|
||||
var candidate = data[start..end];
|
||||
return Encoding.ASCII.GetString(candidate);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsVersionCharacter(char value)
|
||||
=> (value >= '0' && value <= '9')
|
||||
|| (value >= 'a' && value <= 'z')
|
||||
|| (value >= 'A' && value <= 'Z')
|
||||
|| value is '.' or '-' or '+' or '_';
|
||||
}
|
||||
Reference in New Issue
Block a user