using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Reflection.Metadata; using System.Text.RegularExpressions; namespace Semmle.Extraction.CSharp.DependencyFetching { /// /// Stores information about an assembly file (DLL). /// internal sealed partial class AssemblyInfo { /// /// The file containing the assembly. /// public string Filename { get; } /// /// The short name of this assembly. /// public string Name { get; } /// /// The version number of this assembly. /// public System.Version? Version { get; } /// /// The version number of the .NET Core framework that this assembly targets. /// /// This is extracted from the `TargetFrameworkAttribute` of the assembly, e.g. /// ``` /// [assembly:TargetFramework(".NETCoreApp,Version=v7.0")] /// ``` /// yields version 7.0. /// public Version? NetCoreVersion { get; } /// /// The public key token of the assembly. /// public string? PublicKeyToken { get; } /// /// The culture. /// public string? Culture { get; } /// /// Gets the canonical ID of this assembly. /// public string Id { get { var result = Name; if (Version is not null) result = string.Format("{0}, Version={1}", result, Version); if (Culture is not null) result = string.Format("{0}, Culture={1}", result, Culture); if (PublicKeyToken is not null) result = string.Format("{0}, PublicKeyToken={1}", result, PublicKeyToken); return result; } } public override string ToString() => Id; /// /// Gets a list of canonical search strings for this assembly. /// public IEnumerable IndexStrings { get { yield return Id; if (Version is not null) { if (Culture is not null) yield return string.Format("{0}, Version={1}, Culture={2}", Name, Version, Culture); yield return string.Format("{0}, Version={1}", Name, Version); } yield return Name; yield return Name.ToLowerInvariant(); } } private AssemblyInfo(string id, string filename) { var sections = id.Split(new string[] { ", " }, StringSplitOptions.None); Name = sections.First(); foreach (var section in sections.Skip(1)) { if (section.StartsWith("Version=")) Version = new Version(section.Substring(8)); else if (section.StartsWith("Culture=")) Culture = section.Substring(8); else if (section.StartsWith("PublicKeyToken=")) PublicKeyToken = section.Substring(15); // else: Some other field like processorArchitecture - ignore. } Filename = filename; } private AssemblyInfo(string filename, string name, Version version, string culture, string publicKeyToken, Version? netCoreVersion) { Filename = filename; Name = name; Version = version; Culture = culture; PublicKeyToken = publicKeyToken; NetCoreVersion = netCoreVersion; } /// /// Returns the id and name of the assembly that would be created from the received id. /// public static (string id, string name) ComputeSanitizedAssemblyInfo(string id) { var assembly = new AssemblyInfo(id, string.Empty); return (assembly.Id, assembly.Name); } /// /// Reads the assembly info from a file. /// This uses System.Reflection.Metadata, which is a very performant and low-level /// library. This is very convenient when scanning hundreds of DLLs at a time. /// /// The full filename of the assembly. /// The information about the assembly. public static AssemblyInfo ReadFromFile(string filename) { try { /* This method is significantly faster and more lightweight than using * System.Reflection.Assembly.ReflectionOnlyLoadFrom. It also allows * loading the same assembly from different locations. */ using var pereader = new System.Reflection.PortableExecutable.PEReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)); if (!pereader.HasMetadata) { throw new AssemblyLoadException(); } using var sha1 = SHA1.Create(); var metadata = pereader.GetMetadata(); unsafe { var reader = new MetadataReader(metadata.Pointer, metadata.Length); if (!reader.IsAssembly) { throw new AssemblyLoadException(); } var def = reader.GetAssemblyDefinition(); // This is how you compute the public key token from the full public key. // The last 8 bytes of the SHA1 of the public key. var publicKey = reader.GetBlobBytes(def.PublicKey); var publicKeyToken = sha1.ComputeHash(publicKey); var publicKeyString = new StringBuilder(); foreach (var b in publicKeyToken.Skip(12).Reverse()) publicKeyString.AppendFormat("{0:x2}", b); var culture = def.Culture.IsNil ? "neutral" : reader.GetString(def.Culture); Version? netCoreVersion = null; foreach (var attrHandle in def.GetCustomAttributes().Select(reader.GetCustomAttribute)) { var ctorHandle = attrHandle.Constructor; if (ctorHandle.Kind != HandleKind.MemberReference) { continue; } var mHandle = reader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent; if (mHandle.Kind != HandleKind.TypeReference) { continue; } var name = reader.GetString(reader.GetTypeReference((TypeReferenceHandle)mHandle).Name); if (name is "TargetFrameworkAttribute") { var decoded = attrHandle.DecodeValue(new DummyAttributeDecoder()); if ( decoded.FixedArguments.Length > 0 && decoded.FixedArguments[0].Value is string value && NetCoreAppRegex().Match(value).Groups.TryGetValue("version", out var match)) { netCoreVersion = new Version(match.Value); } break; } } return new AssemblyInfo(filename, reader.GetString(def.Name), def.Version, culture, publicKeyString.ToString(), netCoreVersion); } } catch (BadImageFormatException) { // The DLL wasn't an assembly } catch (InvalidOperationException) { // Some other failure } throw new AssemblyLoadException(); } [GeneratedRegex(@"^\.NETCoreApp,Version=v(?\d+\.\d+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)] private static partial Regex NetCoreAppRegex(); private class DummyAttributeDecoder : ICustomAttributeTypeProvider { public int GetPrimitiveType(PrimitiveTypeCode typeCode) => 0; public int GetSystemType() => throw new NotImplementedException(); public int GetSZArrayType(int elementType) => throw new NotImplementedException(); public int GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) => throw new NotImplementedException(); public int GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) => throw new NotImplementedException(); public int GetTypeFromSerializedName(string name) => throw new NotImplementedException(); public PrimitiveTypeCode GetUnderlyingEnumType(int type) => throw new NotImplementedException(); public bool IsSystemType(int type) => throw new NotImplementedException(); } } }