| File: Resources\ManagedAssemblyIdentity.cs | Web Access |
| Project: Microsoft.DotNet.Cli.CoreUtils.csproj (Microsoft.DotNet.Cli.CoreUtils) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; using System.Reflection.Metadata; namespace Microsoft.DotNet.Cli.Resources.Internal; /// <summary> /// The assembly-definition identity needed to validate a satellite assembly. /// </summary> internal sealed class ManagedAssemblyIdentity { private const int PublicKeyTokenLength = 8; private readonly byte[] _publicKeyToken; /// <summary> /// Initializes an assembly identity. /// </summary> /// <param name="name">The simple assembly name.</param> /// <param name="version">The assembly version.</param> /// <param name="cultureName">The assembly culture name, or an empty string for neutral.</param> /// <param name="publicKeyToken">The public key token, or an empty array for an unsigned assembly.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="publicKeyToken"/> is <see langword="null"/>. /// </exception> /// <exception cref="BadImageFormatException"> /// <paramref name="publicKeyToken"/> is neither empty nor eight bytes. /// </exception> internal ManagedAssemblyIdentity( string name, Version version, string cultureName, byte[] publicKeyToken) { ArgumentNullException.ThrowIfNull(publicKeyToken); if (publicKeyToken.Length != 0 && publicKeyToken.Length != PublicKeyTokenLength) { throw new BadImageFormatException( $"The assembly public key token must be empty or exactly {PublicKeyTokenLength} bytes."); } Name = name; Version = version; CultureName = cultureName; _publicKeyToken = publicKeyToken; } /// <summary> /// The simple assembly name. /// </summary> internal string Name { get; } /// <summary> /// The assembly version. /// </summary> internal Version Version { get; } /// <summary> /// The assembly culture name, or an empty string for neutral. /// </summary> internal string CultureName { get; } /// <summary> /// Creates an identity from an already-loaded assembly name. /// </summary> /// <param name="assemblyName">The assembly name.</param> /// <returns>The normalized identity.</returns> internal static ManagedAssemblyIdentity FromAssemblyName(AssemblyName assemblyName) { string name = assemblyName.Name ?? throw new BadImageFormatException("The assembly does not have a simple name."); Version version = assemblyName.Version ?? throw new BadImageFormatException("The assembly does not have a version."); byte[] publicKeyToken = assemblyName.GetPublicKeyToken() ?? []; return new( name, version, assemblyName.CultureName ?? string.Empty, publicKeyToken); } /// <summary> /// Creates an identity from managed assembly metadata. /// </summary> /// <param name="metadataReader">The metadata reader.</param> /// <returns>The assembly-definition identity.</returns> internal static ManagedAssemblyIdentity FromMetadata(MetadataReader metadataReader) { if (!metadataReader.IsAssembly) { throw new BadImageFormatException("The managed image does not contain an assembly definition."); } AssemblyDefinition definition = metadataReader.GetAssemblyDefinition(); string name = metadataReader.GetString(definition.Name); if (name.Length == 0) { throw new BadImageFormatException("The assembly definition does not have a simple name."); } string cultureName = definition.Culture.IsNil ? string.Empty : metadataReader.GetString(definition.Culture); byte[] publicKeyToken = definition.PublicKey.IsNil ? [] : GetPublicKeyToken( metadataReader.GetBlobBytes(definition.PublicKey), definition.Flags.AreFlagsSet(AssemblyFlags.PublicKey)); return new( name, definition.Version, cultureName, publicKeyToken); } /// <summary> /// Creates an identity with a different simple name and the same remaining identity fields. /// </summary> /// <param name="name">The replacement simple name.</param> /// <returns>The renamed identity.</returns> internal ManagedAssemblyIdentity WithName(string name) => new(name, Version, CultureName, _publicKeyToken); /// <summary> /// Validates that this identity is a satellite of <paramref name="resourceAssembly"/>. /// </summary> /// <param name="resourceAssembly">The resource-owning assembly identity.</param> /// <param name="cultureName">The expected satellite culture.</param> /// <param name="contractVersion">The declared satellite contract version, if any.</param> internal void ValidateSatelliteOf( ManagedAssemblyIdentity resourceAssembly, string cultureName, Version? contractVersion) { string expectedName = $"{resourceAssembly.Name}.resources"; Version expectedVersion = contractVersion ?? resourceAssembly.Version; if (string.Equals(Name, expectedName, StringComparison.OrdinalIgnoreCase) && string.Equals(CultureName, cultureName, StringComparison.OrdinalIgnoreCase) && Version.Equals(expectedVersion) && _publicKeyToken.AsSpan().SequenceEqual(resourceAssembly._publicKeyToken)) { return; } string actualCulture = CultureName.Length == 0 ? "neutral" : CultureName; string expectedCulture = cultureName.Length == 0 ? "neutral" : cultureName; throw new FileLoadException( $"The satellite assembly identity '{Name}, Version={Version}, Culture={actualCulture}' " + $"does not match '{expectedName}, Version={expectedVersion}, Culture={expectedCulture}'."); } /// <summary> /// Validates that this identity matches <paramref name="expected"/>. /// </summary> /// <param name="expected">The expected assembly identity.</param> internal void ValidateMatches(ManagedAssemblyIdentity expected) { if (string.Equals(Name, expected.Name, StringComparison.OrdinalIgnoreCase) && string.Equals(CultureName, expected.CultureName, StringComparison.OrdinalIgnoreCase) && Version.Equals(expected.Version) && _publicKeyToken.AsSpan().SequenceEqual(expected._publicKeyToken)) { return; } string actualCulture = CultureName.Length == 0 ? "neutral" : CultureName; string expectedCulture = expected.CultureName.Length == 0 ? "neutral" : expected.CultureName; throw new FileLoadException( $"The resource assembly identity '{Name}, Version={Version}, Culture={actualCulture}' " + $"does not match '{expected.Name}, Version={expected.Version}, Culture={expectedCulture}'."); } private static byte[] GetPublicKeyToken(byte[] publicKeyOrToken, bool containsPublicKey) { if (!containsPublicKey) { return publicKeyOrToken; } AssemblyName assemblyName = new(); assemblyName.SetPublicKey(publicKeyOrToken); return assemblyName.GetPublicKeyToken() ?? throw new BadImageFormatException("The assembly public key is invalid."); } }