| File: Resources\ManagedAssemblyMetadataReader.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.Metadata; using System.Resources; namespace Microsoft.DotNet.Cli.Resources.Internal; /// <summary> /// Reads resource-binding metadata without loading the managed assembly. /// </summary> internal static class ManagedAssemblyMetadataReader { private const string ResourcesNamespace = "System.Resources"; private const string NeutralResourcesLanguageAttributeName = "NeutralResourcesLanguageAttribute"; private const string SatelliteContractVersionAttributeName = "SatelliteContractVersionAttribute"; /// <summary> /// Reads the owner identity and resource-binding attributes. /// </summary> /// <param name="metadataReader">The managed metadata reader.</param> /// <returns>The resource source metadata.</returns> internal static SatelliteStringResourceSourceMetadata ReadResourceAssembly(MetadataReader metadataReader) { ManagedAssemblyIdentity identity = ManagedAssemblyIdentity.FromMetadata(metadataReader); string? neutralCultureName = null; Version? satelliteContractVersion = null; bool foundNeutralResourcesLanguage = false; bool foundSatelliteContractVersion = false; AssemblyDefinition definition = metadataReader.GetAssemblyDefinition(); foreach (CustomAttributeHandle handle in definition.GetCustomAttributes()) { CustomAttribute attribute = metadataReader.GetCustomAttribute(handle); if (TryGetAttributeConstructorParameterCount( metadataReader, attribute, NeutralResourcesLanguageAttributeName, out int parameterCount)) { if (foundNeutralResourcesLanguage) { throw new BadImageFormatException( "The assembly contains multiple neutral resources language attributes."); } foundNeutralResourcesLanguage = true; neutralCultureName = ReadNeutralCultureName(metadataReader, attribute, parameterCount); continue; } if (!TryGetAttributeConstructorParameterCount( metadataReader, attribute, SatelliteContractVersionAttributeName, out parameterCount)) { continue; } if (foundSatelliteContractVersion) { throw new BadImageFormatException( "The assembly contains multiple satellite contract version attributes."); } foundSatelliteContractVersion = true; satelliteContractVersion = ReadSatelliteContractVersion(metadataReader, attribute, parameterCount); } return new( identity, $"{identity.Name}.resources.dll", neutralCultureName, satelliteContractVersion); } private static string ReadNeutralCultureName( MetadataReader metadataReader, CustomAttribute attribute, int parameterCount) { if (parameterCount is not 1 and not 2) { throw new BadImageFormatException( "The neutral resources language attribute constructor is invalid."); } BlobReader value = GetAttributeValueReader(metadataReader, attribute); string cultureName = value.ReadSerializedString() ?? throw new BadImageFormatException( "The neutral resources language attribute culture is null."); int fallbackLocation = parameterCount == 2 ? value.ReadInt32() : (int)UltimateResourceFallbackLocation.MainAssembly; ReadAndValidateNamedArgumentCount(ref value); if (fallbackLocation == (int)UltimateResourceFallbackLocation.Satellite) { throw new NotSupportedException("Neutral resources stored in a satellite are not supported."); } if (fallbackLocation != (int)UltimateResourceFallbackLocation.MainAssembly) { throw new BadImageFormatException( "The neutral resources language attribute fallback location is invalid."); } return cultureName; } private static Version ReadSatelliteContractVersion( MetadataReader metadataReader, CustomAttribute attribute, int parameterCount) { if (parameterCount != 1) { throw new BadImageFormatException( "The satellite contract version attribute constructor is invalid."); } BlobReader value = GetAttributeValueReader(metadataReader, attribute); string? versionText = value.ReadSerializedString(); ReadAndValidateNamedArgumentCount(ref value); if (versionText is null || !Version.TryParse(versionText, out Version? version)) { throw new BadImageFormatException("The satellite contract version is invalid."); } return version; } private static BlobReader GetAttributeValueReader( MetadataReader metadataReader, CustomAttribute attribute) { BlobReader value = metadataReader.GetBlobReader(attribute.Value); if (value.ReadUInt16() != 1) { throw new BadImageFormatException("The custom attribute prolog is invalid."); } return value; } private static void ReadAndValidateNamedArgumentCount(ref BlobReader value) { if (value.ReadUInt16() != 0 || value.RemainingBytes != 0) { throw new BadImageFormatException("The resource assembly attribute value is invalid."); } } private static bool TryGetAttributeConstructorParameterCount( MetadataReader metadataReader, CustomAttribute attribute, string attributeName, out int parameterCount) { parameterCount = 0; if (attribute.Constructor.Kind != HandleKind.MemberReference) { return false; } MemberReference constructor = metadataReader.GetMemberReference( (MemberReferenceHandle)attribute.Constructor); if (!metadataReader.StringComparer.Equals(constructor.Name, ".ctor") || constructor.Parent.Kind != HandleKind.TypeReference) { return false; } TypeReference type = metadataReader.GetTypeReference((TypeReferenceHandle)constructor.Parent); if (!metadataReader.StringComparer.Equals(type.Namespace, ResourcesNamespace) || !metadataReader.StringComparer.Equals(type.Name, attributeName)) { return false; } BlobReader signature = metadataReader.GetBlobReader(constructor.Signature); SignatureHeader header = signature.ReadSignatureHeader(); if (header.IsGeneric) { signature.ReadCompressedInteger(); } parameterCount = signature.ReadCompressedInteger(); return true; } }