// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; using System.ComponentModel; using System.Configuration.Assemblies; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Text; using CultureInfo = System.Globalization.CultureInfo; namespace System.Reflection { public sealed partial class AssemblyName : ICloneable, IDeserializationCallback, ISerializable { // If you modify any of these fields, you must also update the // AssemblyBaseObject structure in object.h private string? _name; private byte[]? _publicKey; private byte[]? _publicKeyToken; private CultureInfo? _cultureInfo; private string? _codeBase; private Version? _version; private AssemblyHashAlgorithm _hashAlgorithm; private AssemblyVersionCompatibility _versionCompatibility; private AssemblyNameFlags _flags; public AssemblyName(string assemblyName) : this() { ArgumentException.ThrowIfNullOrEmpty(assemblyName); if (assemblyName[0] == '\0') throw new ArgumentException(SR.Format_StringZeroLength); AssemblyNameParser.AssemblyNameParts parts = AssemblyNameParser.Parse(assemblyName); _name = parts._name; _version = parts._version; _flags = parts._flags; if ((parts._flags & AssemblyNameFlags.PublicKey) != 0) { _publicKey = parts._publicKeyOrToken; } else { _publicKeyToken = parts._publicKeyOrToken; } if (parts._cultureName != null) _cultureInfo = new CultureInfo(parts._cultureName); } public AssemblyName() { _versionCompatibility = AssemblyVersionCompatibility.SameMachine; } // Set and get the name of the assembly. If this is a weak Name // then it optionally contains a site. For strong assembly names, // the name partitions up the strong name's namespace public string? Name { get => _name; set => _name = value; } public Version? Version { get => _version; set => _version = value; } // Locales, internally the LCID is used for the match. public CultureInfo? CultureInfo { get => _cultureInfo; set => _cultureInfo = value; } public string? CultureName { get => _cultureInfo?.Name; set => _cultureInfo = (value == null) ? null : new CultureInfo(value); } [Obsolete(Obsoletions.AssemblyNameCodeBaseMessage, DiagnosticId = Obsoletions.AssemblyNameCodeBaseDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public string? CodeBase { [RequiresAssemblyFiles("The code will return an empty string for assemblies embedded in a single-file app")] get => _codeBase; set => _codeBase = value; } [Obsolete(Obsoletions.AssemblyNameCodeBaseMessage, DiagnosticId = Obsoletions.AssemblyNameCodeBaseDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [RequiresAssemblyFiles("The code will return an empty string for assemblies embedded in a single-file app")] public string? EscapedCodeBase { get { if (_codeBase == null) return null; else return EscapeCodeBase(_codeBase); } } [Obsolete(Obsoletions.AssemblyNameMembersMessage, DiagnosticId = Obsoletions.AssemblyNameMembersDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public ProcessorArchitecture ProcessorArchitecture { get { int x = (((int)_flags) & 0x70) >> 4; if (x > 5) x = 0; return (ProcessorArchitecture)x; } set { int x = ((int)value) & 0x07; if (x <= 5) { _flags = (AssemblyNameFlags)((int)_flags & 0xFFFFFF0F); _flags |= (AssemblyNameFlags)(x << 4); } } } public AssemblyContentType ContentType { get { int x = (((int)_flags) & 0x00000E00) >> 9; if (x > 1) x = 0; return (AssemblyContentType)x; } set { int x = ((int)value) & 0x07; if (x <= 1) { _flags = (AssemblyNameFlags)((int)_flags & 0xFFFFF1FF); _flags |= (AssemblyNameFlags)(x << 9); } } } // Make a copy of this assembly name. public object Clone() { var name = new AssemblyName { _name = _name, _publicKey = (byte[]?)_publicKey?.Clone(), _publicKeyToken = (byte[]?)_publicKeyToken?.Clone(), _cultureInfo = _cultureInfo, _version = _version, _flags = _flags, _codeBase = _codeBase, _hashAlgorithm = _hashAlgorithm, _versionCompatibility = _versionCompatibility, }; return name; } /* * Get the AssemblyName for a given file. This will only work * if the file contains an assembly manifest. This method causes * the file to be opened and closed. */ public static AssemblyName GetAssemblyName(string assemblyFile) { return GetAssemblyNameInternal(null, assemblyFile); [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "GetAssemblyName")] static extern AssemblyName GetAssemblyNameInternal( [UnsafeAccessorType("System.Reflection.Metadata.MetadataReader, System.Reflection.Metadata")] object? _, string assemblyFile); } public byte[]? GetPublicKey() { return _publicKey; } public void SetPublicKey(byte[]? publicKey) { _publicKey = publicKey; if (publicKey == null) _flags &= ~AssemblyNameFlags.PublicKey; else _flags |= AssemblyNameFlags.PublicKey; } // The compressed version of the public key formed from a truncated hash. // Will throw a SecurityException if _publicKey is invalid public byte[]? GetPublicKeyToken() => _publicKeyToken ??= AssemblyNameHelpers.ComputePublicKeyToken(_publicKey); public void SetPublicKeyToken(byte[]? publicKeyToken) { _publicKeyToken = publicKeyToken; } // Flags modifying the name. So far the only flag is PublicKey, which // indicates that a full public key and not the compressed version is // present. // Processor Architecture flags are set only through ProcessorArchitecture // property and can't be set or retrieved directly // Content Type flags are set only through ContentType property and can't be // set or retrieved directly public AssemblyNameFlags Flags { get => (AssemblyNameFlags)((uint)_flags & 0xFFFFF10F); set { _flags &= unchecked((AssemblyNameFlags)0x00000EF0); _flags |= (value & unchecked((AssemblyNameFlags)0xFFFFF10F)); } } [Obsolete(Obsoletions.AssemblyNameMembersMessage, DiagnosticId = Obsoletions.AssemblyNameMembersDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public AssemblyHashAlgorithm HashAlgorithm { get => _hashAlgorithm; set => _hashAlgorithm = value; } [Obsolete(Obsoletions.AssemblyNameMembersMessage, DiagnosticId = Obsoletions.AssemblyNameMembersDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public AssemblyVersionCompatibility VersionCompatibility { get => _versionCompatibility; set => _versionCompatibility = value; } [Obsolete(Obsoletions.StrongNameKeyPairMessage, DiagnosticId = Obsoletions.StrongNameKeyPairDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public StrongNameKeyPair? KeyPair { get => throw new PlatformNotSupportedException(SR.PlatformNotSupported_StrongNameSigning); set => throw new PlatformNotSupportedException(SR.PlatformNotSupported_StrongNameSigning); } public string FullName { get { if (string.IsNullOrEmpty(this.Name)) return string.Empty; // Do not call GetPublicKeyToken() here - that latches the result into AssemblyName which isn't a side effect we want. byte[]? pkt = _publicKeyToken ?? AssemblyNameHelpers.ComputePublicKeyToken(_publicKey); return AssemblyNameFormatter.ComputeDisplayName(Name, Version, CultureName, pkt, Flags, ContentType); } } public override string ToString() { string s = FullName; if (s == null) return base.ToString()!; else return s; } [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] public void GetObjectData(SerializationInfo info, StreamingContext context) { throw new PlatformNotSupportedException(); } public void OnDeserialization(object? sender) { throw new PlatformNotSupportedException(); } /// <summary> /// Compares the simple names disregarding Version, Culture and PKT. While this clearly does not /// match the intent of this api, this api has been broken this way since its debut and we cannot /// change its behavior now. /// </summary> public static bool ReferenceMatchesDefinition(AssemblyName? reference, AssemblyName? definition) { if (ReferenceEquals(reference, definition)) return true; ArgumentNullException.ThrowIfNull(reference); ArgumentNullException.ThrowIfNull(definition); string refName = reference.Name ?? string.Empty; string defName = definition.Name ?? string.Empty; return refName.Equals(defName, StringComparison.OrdinalIgnoreCase); } // This implementation of Escape has been copied from UriHelper from System.Private.Uri and adapted to match AssemblyName's requirements. internal static unsafe string EscapeCodeBase(string? codebase) { if (codebase == null) return string.Empty; int indexOfFirstToEscape = codebase.AsSpan().IndexOfAnyExcept(UnreservedReserved); if (indexOfFirstToEscape < 0) { // Nothing to escape, just return the original value. return codebase; } // Otherwise, create a ValueStringBuilder to store the escaped data into, // escape the rest, and concat the result with the characters we skipped above. var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]); // We may throw for very large inputs (when growing the ValueStringBuilder). vsb.EnsureCapacity(codebase.Length); EscapeStringToBuilder(codebase.AsSpan(indexOfFirstToEscape), ref vsb); string result = string.Concat(codebase.AsSpan(0, indexOfFirstToEscape), vsb.AsSpan()); vsb.Dispose(); return result; } internal static void EscapeStringToBuilder(scoped ReadOnlySpan<char> stringToEscape, ref ValueStringBuilder vsb) { // Allocate enough stack space to hold any Rune's UTF8 encoding. Span<byte> utf8Bytes = [0, 0, 0, 0]; while (!stringToEscape.IsEmpty) { char ch = stringToEscape[0]; if (!char.IsAscii(ch)) { if (Rune.DecodeFromUtf16(stringToEscape, out Rune r, out int charsConsumed) != OperationStatus.Done) { r = Rune.ReplacementChar; } Debug.Assert(stringToEscape.EnumerateRunes() is { } e && e.MoveNext() && e.Current == r); Debug.Assert(charsConsumed is 1 or 2); stringToEscape = stringToEscape.Slice(charsConsumed); // The rune is non-ASCII, so encode it as UTF8, and escape each UTF8 byte. r.TryEncodeToUtf8(utf8Bytes, out int bytesWritten); foreach (byte b in utf8Bytes.Slice(0, bytesWritten)) { PercentEncodeByte(b, ref vsb); } } else if (!UnreservedReserved.Contains(ch)) { PercentEncodeByte((byte)ch, ref vsb); stringToEscape = stringToEscape.Slice(1); } else { // We have a character we don't want to escape. It's likely there are more, do a vectorized search. int charsToCopy = stringToEscape.IndexOfAnyExcept(UnreservedReserved); if (charsToCopy < 0) { charsToCopy = stringToEscape.Length; } Debug.Assert(charsToCopy > 0); vsb.Append(stringToEscape.Slice(0, charsToCopy)); stringToEscape = stringToEscape.Slice(charsToCopy); } } } internal static void PercentEncodeByte(byte ch, ref ValueStringBuilder vsb) { vsb.Append('%'); HexConverter.ToCharsBuffer(ch, vsb.AppendSpan(2), 0, HexConverter.Casing.Upper); } private static SearchValues<char> UnreservedReserved => field ??= SearchValues.Create("!#$&'()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~"); private const int StackallocThreshold = 512; } }