using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; namespace Semmle.BuildAnalyser { /// /// Stores information about an assembly file (DLL). /// sealed class AssemblyInfo { /// /// The file containing the assembly. /// public string Filename { get; private set; } /// /// Was the information correctly determined? /// public bool Valid { get; private set; } /// /// The short name of this assembly. /// public string Name { get; private set; } /// /// The version number of this assembly. /// public System.Version Version { get; private set; } /// /// The public key token of the assembly. /// public string PublicKeyToken { get; private set; } /// /// The culture. /// public string Culture { get; private set; } /// /// Get/parse a canonical ID of this assembly. /// public string Id { get { var result = Name; if (Version != null) result = string.Format("{0}, Version={1}", result, Version); if (Culture != null) result = string.Format("{0}, Culture={1}", result, Culture); if (PublicKeyToken != null) result = string.Format("{0}, PublicKeyToken={1}", result, PublicKeyToken); return result; } private set { var sections = value.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. } } } public override string ToString() => Id; /// /// Gets a list of canonical search strings for this assembly. /// public IEnumerable IndexStrings { get { yield return Id; if (Version != null) { if (Culture != 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(); } } /// /// Get an invalid assembly info (Valid==false). /// public static AssemblyInfo Invalid { get; } = new AssemblyInfo(); private AssemblyInfo() { } /// /// Get AssemblyInfo from a loaded Assembly. /// /// The assembly. /// Info about the assembly. public static AssemblyInfo MakeFromAssembly(Assembly assembly) => new AssemblyInfo() { Valid = true, Filename = assembly.Location, Id = assembly.FullName }; /// /// Parse an assembly name/Id into an AssemblyInfo, /// populating the available fields and leaving the others null. /// /// The assembly name/Id. /// The deconstructed assembly info. public static AssemblyInfo MakeFromId(string id) => new AssemblyInfo() { Valid = true, Id = id }; /// /// 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) { var result = new AssemblyInfo() { Filename = 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))) { var metadata = pereader.GetMetadata(); unsafe { var reader = new System.Reflection.Metadata.MetadataReader(metadata.Pointer, metadata.Length); 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); result.Name = reader.GetString(def.Name); result.Version = def.Version; result.Culture = def.Culture.IsNil ? "neutral" : reader.GetString(def.Culture); result.PublicKeyToken = publicKeyString.ToString(); result.Valid = true; } } } catch (BadImageFormatException) { // The DLL wasn't an assembly -> result.Valid = false. } catch (InvalidOperationException) { // Some other failure -> result.Valid = false. } return result; } static readonly SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider(); } }