1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 using System;
5 using System.Text;
6 using System.Reflection;
7 using System.Collections;
8 using System.Globalization;
9 using System.Diagnostics;
10 using System.Collections.Generic;
11 using System.Configuration.Assemblies;
12 using System.Runtime.Serialization;
13 using System.IO;
14 #if !FEATURE_ASSEMBLY_LOADFROM || MONO
15 using System.Reflection.PortableExecutable;
16 using System.Reflection.Metadata;
17 #endif
18 
19 namespace Microsoft.Build.Shared
20 {
21     /// <summary>
22     /// Specifies the parts of the assembly name to partially match
23     /// </summary>
24     [FlagsAttribute]
25     internal enum PartialComparisonFlags : int
26     {
27         /// <summary>
28         /// Compare SimpleName  A.PartialCompare(B,SimpleName)  match the simple name on A and B if the simple name on A is not null.
29         /// </summary>
30         SimpleName = 1, // 0000 0000 0000 0001
31 
32         /// <summary>
33         /// Compare Version A.PartialCompare(B, Version)  match the Version on A and B if the Version on A is not null.
34         /// </summary>
35         Version = 2, // 0000 0000 0000 0010
36 
37         /// <summary>
38         /// Compare Culture A.PartialCompare(B, Culture)  match the Culture on A and B if the Culture on A is not null.
39         /// </summary>
40         Culture = 4, // 0000 0000 0000 0100
41 
42         /// <summary>
43         /// Compare PublicKeyToken A.PartialCompare(B, PublicKeyToken)  match the PublicKeyToken on A and B if the PublicKeyToken on A is not null.
44         /// </summary>
45         PublicKeyToken = 8, // 0000 0000 0000 1000
46 
47         /// <summary>
48         /// When doing a comparison   A.PartialCompare(B, Default) compare all fields of A which are not null with B.
49         /// </summary>
50         Default = 15, // 0000 0000 0000 1111
51     }
52 
53     /// <summary>
54     /// A replacement for AssemblyName that optimizes calls to FullName which is expensive.
55     /// The assembly name is represented internally by an AssemblyName and a string, conversion
56     /// between the two is done lazily on demand.
57     /// </summary>
58     [Serializable]
59     internal sealed class AssemblyNameExtension : ISerializable, IEquatable<AssemblyNameExtension>
60     {
61         private AssemblyName asAssemblyName = null;
62         private string asString = null;
63         private bool isSimpleName = false;
64         private bool hasProcessorArchitectureInFusionName;
65         private bool immutable;
66 
67         /// <summary>
68         /// Set of assemblyNameExtensions that THIS assemblyname was remapped from.
69         /// </summary>
70         private HashSet<AssemblyNameExtension> remappedFrom;
71 
72         private static readonly AssemblyNameExtension s_unnamedAssembly = new AssemblyNameExtension();
73 
74         /// <summary>
75         /// Construct an unnamed assembly.
76         /// Private because we want only one of these.
77         /// </summary>
AssemblyNameExtension()78         private AssemblyNameExtension()
79         {
80         }
81 
82         /// <summary>
83         /// Construct with AssemblyName.
84         /// </summary>
85         /// <param name="assemblyName"></param>
AssemblyNameExtension(AssemblyName assemblyName)86         internal AssemblyNameExtension(AssemblyName assemblyName) : this()
87         {
88             asAssemblyName = assemblyName;
89         }
90 
91         /// <summary>
92         /// Construct with string.
93         /// </summary>
94         /// <param name="assemblyName"></param>
AssemblyNameExtension(string assemblyName)95         internal AssemblyNameExtension(string assemblyName) : this()
96         {
97             asString = assemblyName;
98         }
99 
100         /// <summary>
101         /// Construct from a string, but immediately construct a real AssemblyName.
102         /// This will cause an exception to be thrown up front if the assembly name
103         /// isn't well formed.
104         /// </summary>
105         /// <param name="assemblyName">
106         /// The string version of the assembly name.
107         /// </param>
108         /// <param name="validate">
109         /// Used when the assembly name comes from a user-controlled source like a project file or config file.
110         /// Does extra checking on the assembly name and will throw exceptions if something is invalid.
111         /// </param>
AssemblyNameExtension(string assemblyName, bool validate)112         internal AssemblyNameExtension(string assemblyName, bool validate) : this()
113         {
114             asString = assemblyName;
115 
116             if (validate)
117             {
118                 // This will throw...
119                 CreateAssemblyName();
120             }
121         }
122 
123         /// <summary>
124         /// Ctor for deserializing from state file (binary serialization).
125         /// <remarks>This is required because AssemblyName is not Serializable on .NET Core.</remarks>
126         /// </summary>
127         /// <param name="info"></param>
128         /// <param name="context"></param>
AssemblyNameExtension(SerializationInfo info, StreamingContext context)129         private AssemblyNameExtension(SerializationInfo info, StreamingContext context)
130         {
131             var hasAssemblyName = info.GetBoolean("hasAN");
132 
133             if (hasAssemblyName)
134             {
135                 var name = info.GetString("name");
136                 var publicKey = (byte[]) info.GetValue("pk", typeof(byte[]));
137                 var publicKeyToken = (byte[]) info.GetValue("pkt", typeof(byte[]));
138                 var version = (Version)info.GetValue("ver", typeof(Version));
139                 var flags = (AssemblyNameFlags) info.GetInt32("flags");
140                 var processorArchitecture = (ProcessorArchitecture) info.GetInt32("cpuarch");
141 
142                 CultureInfo cultureInfo = null;
143                 var hasCultureInfo = info.GetBoolean("hasCI");
144                 if (hasCultureInfo)
145                 {
146                     cultureInfo = new CultureInfo(info.GetInt32("ci"));
147                 }
148 
149                 var hashAlgorithm = (System.Configuration.Assemblies.AssemblyHashAlgorithm) info.GetInt32("hashAlg");
150                 var versionCompatibility = (AssemblyVersionCompatibility) info.GetInt32("verCompat");
151                 var codeBase = info.GetString("codebase");
152                 var keyPair = (StrongNameKeyPair) info.GetValue("keypair", typeof(StrongNameKeyPair));
153 
154                 asAssemblyName = new AssemblyName
155                 {
156                     Name = name,
157                     Version = version,
158                     Flags = flags,
159                     ProcessorArchitecture = processorArchitecture,
160                     CultureInfo = cultureInfo,
161                     HashAlgorithm = hashAlgorithm,
162                     VersionCompatibility = versionCompatibility,
163                     CodeBase = codeBase,
164                     KeyPair = keyPair
165                 };
166 
167                 asAssemblyName.SetPublicKey(publicKey);
168                 asAssemblyName.SetPublicKeyToken(publicKeyToken);
169             }
170 
171             asString = info.GetString("asStr");
172             isSimpleName = info.GetBoolean("isSName");
173             hasProcessorArchitectureInFusionName = info.GetBoolean("hasCpuArch");
174             immutable = info.GetBoolean("immutable");
175             remappedFrom = (HashSet<AssemblyNameExtension>) info.GetValue("remapped", typeof(HashSet<AssemblyNameExtension>));
176         }
177 
178         /// <summary>
179         /// To be used as a delegate. Gets the AssemblyName of the given file.
180         /// </summary>
181         /// <param name="path"></param>
182         /// <returns></returns>
GetAssemblyNameEx(string path)183         internal static AssemblyNameExtension GetAssemblyNameEx(string path)
184         {
185             AssemblyName assemblyName = null;
186 #if FEATURE_ASSEMBLY_LOADFROM && !MONO
187             try
188             {
189                 assemblyName = AssemblyName.GetAssemblyName(path);
190             }
191             catch (System.IO.FileLoadException)
192             {
193                 // Its pretty hard to get here, you need an assembly that contains a valid reference
194                 // to a dependent assembly that, in turn, throws a FileLoadException during GetAssemblyName.
195                 // Still it happened once, with an older version of the CLR.
196 
197                 // ...falling through and relying on the targetAssemblyName==null behavior below...
198             }
199             catch (System.IO.FileNotFoundException)
200             {
201                 // Its pretty hard to get here, also since we do a file existence check right before calling this method so it can only happen if the file got deleted between that check and this call.
202             }
203 #else
204             using (var stream = File.OpenRead(path))
205             using (var peFile = new PEReader(stream))
206             {
207                 var metadataReader = peFile.GetMetadataReader();
208 
209                 var entry = metadataReader.GetAssemblyDefinition();
210 
211                 assemblyName = new AssemblyName();
212                 assemblyName.Name = metadataReader.GetString(entry.Name);
213                 assemblyName.Version = entry.Version;
214                 var cultureString = metadataReader.GetString(entry.Culture);
215                 if (!NativeMethodsShared.IsMono)
216                 {
217                     // set_CultureName throws NotImplementedException on Mono
218                     assemblyName.CultureName = cultureString;
219                 }
220                 else if (cultureString != null)
221                 {
222                     assemblyName.CultureInfo = new CultureInfo(cultureString);
223                 }
224                 assemblyName.SetPublicKey(metadataReader.GetBlobBytes(entry.PublicKey));
225                 assemblyName.Flags = (AssemblyNameFlags)(int)entry.Flags;
226             }
227 #endif
228             return assemblyName == null ? null : new AssemblyNameExtension(assemblyName);
229         }
230 
231         /// <summary>
232         /// Run after the object has been deserialized
233         /// </summary>
234         [OnDeserialized]
SetRemappedFromDefaultAfterSerialization(StreamingContext sc)235         private void SetRemappedFromDefaultAfterSerialization(StreamingContext sc)
236         {
237             InitializeRemappedFrom();
238         }
239 
240         /// <summary>
241         /// Initialize the remapped from structure.
242         /// </summary>
InitializeRemappedFrom()243         private void InitializeRemappedFrom()
244         {
245             if (remappedFrom == null)
246             {
247                 remappedFrom = new HashSet<AssemblyNameExtension>(AssemblyNameComparer.GenericComparerConsiderRetargetable);
248             }
249         }
250 
251         /// <summary>
252         /// Assume there is a string version, create the AssemblyName version.
253         /// </summary>
CreateAssemblyName()254         private void CreateAssemblyName()
255         {
256             if (asAssemblyName == null)
257             {
258                 asAssemblyName = GetAssemblyNameFromDisplayName(asString);
259 
260                 if (asAssemblyName != null)
261                 {
262                     hasProcessorArchitectureInFusionName = asString.IndexOf("ProcessorArchitecture", StringComparison.OrdinalIgnoreCase) != -1;
263                     isSimpleName = ((Version == null) && (CultureInfo == null) && (GetPublicKeyToken() == null) && (!hasProcessorArchitectureInFusionName));
264                 }
265             }
266         }
267 
268         /// <summary>
269         /// Assume there is a string version, create the AssemblyName version.
270         /// </summary>
CreateFullName()271         private void CreateFullName()
272         {
273             if (asString == null)
274             {
275                 asString = asAssemblyName.FullName;
276             }
277         }
278 
279         /// <summary>
280         /// The base name of the assembly.
281         /// </summary>
282         /// <value></value>
283         internal string Name
284         {
285             get
286             {
287                 // Is there a string?
288                 CreateAssemblyName();
289                 return asAssemblyName.Name;
290             }
291         }
292 
293         /// <summary>
294         /// Gets the backing AssemblyName, this can be None.
295         /// </summary>
296         internal ProcessorArchitecture ProcessorArchitecture =>
297             asAssemblyName?.ProcessorArchitecture ?? ProcessorArchitecture.None;
298 
299         /// <summary>
300         /// The assembly's version number.
301         /// </summary>
302         /// <value></value>
303         internal Version Version
304         {
305             get
306             {
307                 // Is there a string?
308                 CreateAssemblyName();
309                 return asAssemblyName.Version;
310             }
311         }
312 
313         /// <summary>
314         /// Is the assembly a complex name or a simple name. A simple name is where only the name is set
315         /// a complex name is where the version, culture or publickeytoken is also set
316         /// </summary>
317         internal bool IsSimpleName
318         {
319             get
320             {
321                 CreateAssemblyName();
322                 return isSimpleName;
323             }
324         }
325 
326         /// <summary>
327         /// Does the fullName have the processor architecture defined
328         /// </summary>
329         internal bool HasProcessorArchitectureInFusionName
330         {
331             get
332             {
333                 CreateAssemblyName();
334                 return hasProcessorArchitectureInFusionName;
335             }
336         }
337 
338         /// <summary>
339         /// Replace the current version with a new version.
340         /// </summary>
341         /// <param name="version"></param>
ReplaceVersion(Version version)342         internal void ReplaceVersion(Version version)
343         {
344             ErrorUtilities.VerifyThrow(!immutable, "Object is immutable cannot replace the version");
345             CreateAssemblyName();
346             if (asAssemblyName.Version != version)
347             {
348                 asAssemblyName.Version = version;
349 
350                 // String would now be invalid.
351                 asString = null;
352             }
353         }
354 
355         /// <summary>
356         /// The assembly's Culture
357         /// </summary>
358         /// <value></value>
359         internal CultureInfo CultureInfo
360         {
361             get
362             {
363                 // Is there a string?
364                 CreateAssemblyName();
365                 return asAssemblyName.CultureInfo;
366             }
367         }
368 
369         /// <summary>
370         /// The assembly's retargetable bit
371         /// </summary>
372         /// <value></value>
373         internal bool Retargetable
374         {
375             get
376             {
377                 // Is there a string?
378                 CreateAssemblyName();
379                 // Cannot use the HasFlag method on the Flags enum because this class needs to work with 3.5
380                 return (asAssemblyName.Flags & AssemblyNameFlags.Retargetable) == AssemblyNameFlags.Retargetable;
381             }
382         }
383 
384         /// <summary>
385         /// The full name of the original extension we were before being remapped.
386         /// </summary>
387         internal IEnumerable<AssemblyNameExtension> RemappedFromEnumerator
388         {
389             get
390             {
391                 InitializeRemappedFrom();
392                 return remappedFrom;
393             }
394         }
395 
396         /// <summary>
397         /// Add an assemblyNameExtension which represents an assembly name which was mapped to THIS assemblyName.
398         /// </summary>
AddRemappedAssemblyName(AssemblyNameExtension extensionToAdd)399         internal void AddRemappedAssemblyName(AssemblyNameExtension extensionToAdd)
400         {
401             ErrorUtilities.VerifyThrow(extensionToAdd.Immutable, "ExtensionToAdd is not immutable");
402             InitializeRemappedFrom();
403             remappedFrom.Add(extensionToAdd);
404         }
405 
406         /// <summary>
407         /// As an AssemblyName
408         /// </summary>
409         /// <value></value>
410         internal AssemblyName AssemblyName
411         {
412             get
413             {
414                 // Is there a string?
415                 CreateAssemblyName();
416                 return asAssemblyName;
417             }
418         }
419 
420         /// <summary>
421         /// The assembly's full name.
422         /// </summary>
423         /// <value></value>
424         internal string FullName
425         {
426             get
427             {
428                 // Is there a string?
429                 CreateFullName();
430                 return asString;
431             }
432         }
433 
434         /// <summary>
435         /// Get the assembly's public key token.
436         /// </summary>
437         /// <returns></returns>
GetPublicKeyToken()438         internal byte[] GetPublicKeyToken()
439         {
440             // Is there a string?
441             CreateAssemblyName();
442             return asAssemblyName.GetPublicKeyToken();
443         }
444 
445 
446         /// <summary>
447         /// A special "unnamed" instance of AssemblyNameExtension.
448         /// </summary>
449         /// <value></value>
450         internal static AssemblyNameExtension UnnamedAssembly => s_unnamedAssembly;
451 
452         /// <summary>
453         /// Compare one assembly name to another.
454         /// </summary>
455         /// <param name="that"></param>
456         /// <returns></returns>
CompareTo(AssemblyNameExtension that)457         internal int CompareTo(AssemblyNameExtension that)
458         {
459             return CompareTo(that, false);
460         }
461 
462         /// <summary>
463         /// Compare one assembly name to another.
464         /// </summary>
CompareTo(AssemblyNameExtension that, bool considerRetargetableFlag)465         internal int CompareTo(AssemblyNameExtension that, bool considerRetargetableFlag)
466         {
467             // Are they identical?
468             if (this.Equals(that, considerRetargetableFlag))
469             {
470                 return 0;
471             }
472 
473             // Are the base names not identical?
474             int result = CompareBaseNameTo(that);
475             if (result != 0)
476             {
477                 return result;
478             }
479 
480             // We would like to compare the version numerically rather than alphabetically (because for example version 10.0.0. should be below 9 not between 1 and 2)
481             if (this.Version != that.Version)
482             {
483                 if (this.Version == null)
484                 {
485                     // This is therefore less than that. Since this is null and that is not null
486                     return -1;
487                 }
488 
489                 // Will not return 0 as the this != that check above takes care of the case where they are equal.
490                 result = this.Version.CompareTo(that.Version);
491                 return result;
492             }
493 
494             // We need some final collating order for these, alphabetical by FullName seems as good as any.
495             return string.Compare(this.FullName, that.FullName, StringComparison.OrdinalIgnoreCase);
496         }
497 
498         /// <summary>
499         /// Get a hash code for this assembly name.
500         /// </summary>
501         /// <returns></returns>
GetHashCode()502         internal new int GetHashCode()
503         {
504             // Ok, so this isn't a great hashing algorithm. However, basenames with different
505             // versions or PKTs are relatively uncommon and so collisions should be low.
506             // Hashing on FullName is wrong because the order of tuple fields is undefined.
507             int hash = StringComparer.OrdinalIgnoreCase.GetHashCode(this.Name);
508             return hash;
509         }
510 
511         /// <summary>
512         /// Compare two base names as quickly as possible.
513         /// </summary>
514         /// <param name="that"></param>
515         /// <returns></returns>
CompareBaseNameTo(AssemblyNameExtension that)516         internal int CompareBaseNameTo(AssemblyNameExtension that)
517         {
518             int result = CompareBaseNameToImpl(that);
519 #if DEBUG
520             // Now, compare to the real value to make sure the result was accurate.
521             AssemblyName a1 = asAssemblyName;
522             AssemblyName a2 = that.asAssemblyName;
523             if (a1 == null)
524             {
525                 a1 = new AssemblyName(asString);
526             }
527             if (a2 == null)
528             {
529                 a2 = new AssemblyName(that.asString);
530             }
531 
532             int baselineResult = string.Compare(a1.Name, a2.Name, StringComparison.OrdinalIgnoreCase);
533             ErrorUtilities.VerifyThrow(result == baselineResult, "Optimized version of CompareBaseNameTo didn't return the same result as the baseline.");
534 #endif
535             return result;
536         }
537 
538         /// <summary>
539         /// An implementation of compare that compares two base
540         /// names as quickly as possible.
541         /// </summary>
542         /// <param name="that"></param>
543         /// <returns></returns>
CompareBaseNameToImpl(AssemblyNameExtension that)544         private int CompareBaseNameToImpl(AssemblyNameExtension that)
545         {
546             // Pointer compare, if identical then base names are equal.
547             if (this == that)
548             {
549                 return 0;
550             }
551 
552             // Do both have assembly names?
553             if (asAssemblyName != null && that.asAssemblyName != null)
554             {
555                 // Pointer compare or base name compare.
556                 return asAssemblyName == that.asAssemblyName
557                     ? 0
558                     : string.Compare(asAssemblyName.Name, that.asAssemblyName.Name, StringComparison.OrdinalIgnoreCase);
559             }
560 
561             // Do both have strings?
562             if (asString != null && that.asString != null)
563             {
564                 // If we have two random-case strings, then we need to compare case sensitively.
565                 return CompareBaseNamesStringWise(asString, that.asString);
566             }
567 
568             // Fall back to comparing by name. This is the slow path.
569             return string.Compare(this.Name, that.Name, StringComparison.OrdinalIgnoreCase);
570         }
571 
572         /// <summary>
573         /// Compare two basenames.
574         /// </summary>
575         /// <param name="asString1"></param>
576         /// <param name="asString2"></param>
577         /// <returns></returns>
CompareBaseNamesStringWise(string asString1, string asString2)578         private static int CompareBaseNamesStringWise(string asString1, string asString2)
579         {
580             // Identical strings just match.
581             if (asString1 == asString2)
582             {
583                 return 0;
584             }
585 
586             // Get the lengths of base names to compare.
587             int baseLenThis = asString1.IndexOf(',');
588             int baseLenThat = asString2.IndexOf(',');
589             if (baseLenThis == -1)
590             {
591                 baseLenThis = asString1.Length;
592             }
593             if (baseLenThat == -1)
594             {
595                 baseLenThat = asString2.Length;
596             }
597 
598             // If the lengths are the same then we can compare without copying.
599             if (baseLenThis == baseLenThat)
600             {
601                 return string.Compare(asString1, 0, asString2, 0, baseLenThis, StringComparison.OrdinalIgnoreCase);
602             }
603 
604             // Lengths are different, so string copy is required.
605             string nameThis = asString1.Substring(0, baseLenThis);
606             string nameThat = asString2.Substring(0, baseLenThat);
607             return string.Compare(nameThis, nameThat, StringComparison.OrdinalIgnoreCase);
608         }
609 
610         /// <summary>
611         /// Clone this assemblyNameExtension
612         /// </summary>
Clone()613         internal AssemblyNameExtension Clone()
614         {
615             AssemblyNameExtension newExtension = new AssemblyNameExtension();
616 
617             if (asAssemblyName != null)
618             {
619                 newExtension.asAssemblyName = asAssemblyName.CloneIfPossible();
620             }
621 
622             newExtension.asString = asString;
623             newExtension.isSimpleName = isSimpleName;
624             newExtension.hasProcessorArchitectureInFusionName = hasProcessorArchitectureInFusionName;
625             newExtension.remappedFrom = remappedFrom;
626 
627             // We are cloning so we can now party on the object even if the parent was immutable
628             newExtension.immutable = false;
629 
630             return newExtension;
631         }
632 
633         /// <summary>
634         /// Clone the object but mark and mark the cloned object as immutable
635         /// </summary>
636         /// <returns></returns>
CloneImmutable()637         internal AssemblyNameExtension CloneImmutable()
638         {
639             AssemblyNameExtension clonedExtension = Clone();
640             clonedExtension.MarkImmutable();
641             return clonedExtension;
642         }
643 
644         /// <summary>
645         /// Is this object immutable
646         /// </summary>
647         public bool Immutable => immutable;
648 
649         /// <summary>
650         /// Mark this object as immutable
651         /// </summary>
MarkImmutable()652         internal void MarkImmutable()
653         {
654             immutable = true;
655         }
656 
657         /// <summary>
658         /// Compare two assembly names for equality.
659         /// </summary>
660         /// <param name="that"></param>
661         /// <returns></returns>
Equals(AssemblyNameExtension that)662         internal bool Equals(AssemblyNameExtension that)
663         {
664             return EqualsImpl(that, false, false);
665         }
666 
667         /// <summary>
668         /// Interface method for IEquatable&lt;AssemblyNameExtension&gt;
669         /// </summary>
670         /// <param name="other"></param>
671         /// <returns></returns>
Equals(AssemblyNameExtension other)672         bool IEquatable<AssemblyNameExtension>.Equals(AssemblyNameExtension other)
673         {
674             return Equals(other);
675         }
676 
677         /// <summary>
678         /// Compare two assembly names for equality ignoring version.
679         /// </summary>
680         /// <param name="that"></param>
681         /// <returns></returns>
EqualsIgnoreVersion(AssemblyNameExtension that)682         internal bool EqualsIgnoreVersion(AssemblyNameExtension that)
683         {
684             return EqualsImpl(that, true, false);
685         }
686 
687         /// <summary>
688         /// Compare two assembly names and consider the retargetable flag during the comparison
689         /// </summary>
Equals(AssemblyNameExtension that, bool considerRetargetableFlag)690         internal bool Equals(AssemblyNameExtension that, bool considerRetargetableFlag)
691         {
692             return EqualsImpl(that, false, considerRetargetableFlag);
693         }
694 
695         /// <summary>
696         /// Compare two assembly names for equality.
697         /// </summary>
EqualsImpl(AssemblyNameExtension that, bool ignoreVersion, bool considerRetargetableFlag)698         private bool EqualsImpl(AssemblyNameExtension that, bool ignoreVersion, bool considerRetargetableFlag)
699         {
700             // Pointer compare.
701             if (object.ReferenceEquals(this, that))
702             {
703                 return true;
704             }
705 
706             // If that is null then this and that are not equal. Also, this would cause a crash on the next line.
707             if (object.ReferenceEquals(that, null))
708             {
709                 return false;
710             }
711 
712             // Do both have assembly names?
713             if (asAssemblyName != null && that.asAssemblyName != null)
714             {
715                 // Pointer compare.
716                 if (object.ReferenceEquals(asAssemblyName, that.asAssemblyName))
717                 {
718                     return true;
719                 }
720             }
721 
722             // Do both have strings that equal each-other?
723             if (asString != null && that.asString != null)
724             {
725                 if (asString == that.asString)
726                 {
727                     return true;
728                 }
729 
730                 // If they weren't identical then they might still differ only by
731                 // case. So we can't assume that they don't match. So fall through...
732             }
733 
734             // Do the names match?
735             if (0 != string.Compare(Name, that.Name, StringComparison.OrdinalIgnoreCase))
736             {
737                 return false;
738             }
739 
740             if (!ignoreVersion && (this.Version != that.Version))
741             {
742                 return false;
743             }
744 
745             if (!CompareCultures(AssemblyName, that.AssemblyName))
746             {
747                 return false;
748             }
749 
750             if (!ComparePublicKeyToken(that))
751             {
752                 return false;
753             }
754 
755             if (considerRetargetableFlag && this.Retargetable != that.Retargetable)
756             {
757                 return false;
758             }
759 
760             return true;
761         }
762 
763         /// <summary>
764         /// Allows the comparison of the culture.
765         /// </summary>
CompareCultures(AssemblyName a, AssemblyName b)766         internal static bool CompareCultures(AssemblyName a, AssemblyName b)
767         {
768             // Do the Cultures match?
769             CultureInfo aCulture = a.CultureInfo;
770             CultureInfo bCulture = b.CultureInfo;
771             if (aCulture == null)
772             {
773                 aCulture = CultureInfo.InvariantCulture;
774             }
775             if (bCulture == null)
776             {
777                 bCulture = CultureInfo.InvariantCulture;
778             }
779 
780             return aCulture.LCID == bCulture.LCID;
781         }
782 
783         /// <summary>
784         ///  Allows the comparison of just the PublicKeyToken
785         /// </summary>
ComparePublicKeyToken(AssemblyNameExtension that)786         internal bool ComparePublicKeyToken(AssemblyNameExtension that)
787         {
788             // Do the PKTs match?
789             byte[] aPKT = GetPublicKeyToken();
790             byte[] bPKT = that.GetPublicKeyToken();
791             return ComparePublicKeyTokens(aPKT, bPKT);
792         }
793 
794         /// <summary>
795         /// Compare two public key tokens.
796         /// </summary>
ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT)797         internal static bool ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT)
798         {
799             // Some assemblies (real case was interop assembly) may have null PKTs.
800             if (aPKT == null)
801             {
802                 aPKT = new byte[0];
803             }
804             if (bPKT == null)
805             {
806                 bPKT = new byte[0];
807             }
808 
809             if (aPKT.Length != bPKT.Length)
810             {
811                 return false;
812             }
813             for (int i = 0; i < aPKT.Length; ++i)
814             {
815                 if (aPKT[i] != bPKT[i])
816                 {
817                     return false;
818                 }
819             }
820             return true;
821         }
822 
823         /// <summary>
824         /// Only the unnamed assembly has both null assemblyname and null string.
825         /// </summary>
826         /// <returns></returns>
827         internal bool IsUnnamedAssembly => asAssemblyName == null && asString == null;
828 
829         /// <summary>
830         /// Given a display name, construct an assembly name.
831         /// </summary>
832         /// <param name="displayName">The display name.</param>
833         /// <returns>The assembly name.</returns>
GetAssemblyNameFromDisplayName(string displayName)834         private static AssemblyName GetAssemblyNameFromDisplayName(string displayName)
835         {
836             AssemblyName assemblyName = new AssemblyName(displayName);
837             return assemblyName;
838         }
839 
840         /// <summary>
841         /// Return a string that has AssemblyName special characters escaped.
842         /// Those characters are Equals(=), Comma(,), Quote("), Apostrophe('), Backslash(\).
843         /// </summary>
844         /// <remarks>
845         /// WARNING! This method is not meant as a general purpose escaping method for assembly names.
846         /// Use only if you really know that this does what you need.
847         /// </remarks>
848         /// <param name="displayName"></param>
849         /// <returns></returns>
EscapeDisplayNameCharacters(string displayName)850         internal static string EscapeDisplayNameCharacters(string displayName)
851         {
852             StringBuilder sb = new StringBuilder(displayName);
853             sb = sb.Replace("\\", "\\\\");
854             sb = sb.Replace("=", "\\=");
855             sb = sb.Replace(",", "\\,");
856             sb = sb.Replace("\"", "\\\"");
857             sb = sb.Replace("'", "\\'");
858             return sb.ToString();
859         }
860 
861         /// <summary>
862         /// Convert to a string for display.
863         /// </summary>
864         /// <returns></returns>
ToString()865         public override string ToString()
866         {
867             CreateFullName();
868             return asString;
869         }
870 
871         /// <summary>
872         /// Compare the fields of this with that if they are not null.
873         /// </summary>
PartialNameCompare(AssemblyNameExtension that)874         internal bool PartialNameCompare(AssemblyNameExtension that)
875         {
876             return PartialNameCompare(that, PartialComparisonFlags.Default, false /* do not consider retargetable flag*/);
877         }
878 
879         /// <summary>
880         /// Compare the fields of this with that if they are not null.
881         /// </summary>
PartialNameCompare(AssemblyNameExtension that, bool considerRetargetableFlag)882         internal bool PartialNameCompare(AssemblyNameExtension that, bool considerRetargetableFlag)
883         {
884             return PartialNameCompare(that, PartialComparisonFlags.Default, considerRetargetableFlag);
885         }
886 
887         /// <summary>
888         /// Do a partial comparison between two assembly name extensions.
889         /// Compare the fields of A and B on the following conditions:
890         /// 1) A.Field has a non null value
891         /// 2) The field has been selected in the comparison flags or the default comparison flags are passed in.
892         ///
893         /// If A.Field is null then we will not compare A.Field and B.Field even when the comparison flag is set for that field unless skipNullFields is false.
894         /// </summary>
PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags)895         internal bool PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags)
896         {
897             return PartialNameCompare(that, comparisonFlags, false /* do not consider retargetable flag*/);
898         }
899 
900         /// <summary>
901         /// Do a partial comparison between two assembly name extensions.
902         /// Compare the fields of A and B on the following conditions:
903         /// 1) A.Field has a non null value
904         /// 2) The field has been selected in the comparison flags or the default comparison flags are passed in.
905         ///
906         /// If A.Field is null then we will not compare A.Field and B.Field even when the comparison flag is set for that field unless skipNullFields is false.
907         /// </summary>
PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags, bool considerRetargetableFlag)908         internal bool PartialNameCompare(AssemblyNameExtension that, PartialComparisonFlags comparisonFlags, bool considerRetargetableFlag)
909         {
910             // Pointer compare.
911             if (object.ReferenceEquals(this, that))
912             {
913                 return true;
914             }
915 
916             // If that is null then this and that are not equal. Also, this would cause a crash on the next line.
917             if (object.ReferenceEquals(that, null))
918             {
919                 return false;
920             }
921 
922             // Do both have assembly names?
923             if (asAssemblyName != null && that.asAssemblyName != null)
924             {
925                 // Pointer compare.
926                 if (object.ReferenceEquals(asAssemblyName, that.asAssemblyName))
927                 {
928                     return true;
929                 }
930             }
931 
932             // Do the names match?
933             if ((comparisonFlags & PartialComparisonFlags.SimpleName) != 0 && Name != null && !string.Equals(Name, that.Name, StringComparison.OrdinalIgnoreCase))
934             {
935                 return false;
936             }
937 
938             if ((comparisonFlags & PartialComparisonFlags.Version) != 0 && Version != null && this.Version != that.Version)
939             {
940                 return false;
941             }
942 
943             if ((comparisonFlags & PartialComparisonFlags.Culture) != 0 && CultureInfo != null && (that.CultureInfo == null || !CompareCultures(AssemblyName, that.AssemblyName)))
944             {
945                 return false;
946             }
947 
948             if ((comparisonFlags & PartialComparisonFlags.PublicKeyToken) != 0 && GetPublicKeyToken() != null && !ComparePublicKeyToken(that))
949             {
950                 return false;
951             }
952 
953             if (considerRetargetableFlag && (Retargetable != that.Retargetable))
954             {
955                 return false;
956             }
957             return true;
958         }
959 
GetObjectData(SerializationInfo info, StreamingContext context)960         public void GetObjectData(SerializationInfo info, StreamingContext context)
961         {
962             info.AddValue("hasAN", asAssemblyName != null);
963             if (asAssemblyName != null)
964             {
965                 info.AddValue("name", asAssemblyName.Name);
966                 info.AddValue("pk", asAssemblyName.GetPublicKey());
967                 info.AddValue("pkt", asAssemblyName.GetPublicKeyToken());
968                 info.AddValue("ver", asAssemblyName.Version);
969                 info.AddValue("flags", (int) asAssemblyName.Flags);
970                 info.AddValue("cpuarch", (int) asAssemblyName.ProcessorArchitecture);
971 
972                 info.AddValue("hasCI", asAssemblyName.CultureInfo != null);
973                 if (asAssemblyName.CultureInfo != null)
974                 {
975                     info.AddValue("ci", asAssemblyName.CultureInfo.LCID);
976                 }
977 
978                 info.AddValue("hashAlg", asAssemblyName.HashAlgorithm);
979                 info.AddValue("verCompat", asAssemblyName.VersionCompatibility);
980                 info.AddValue("codebase", asAssemblyName.CodeBase);
981                 info.AddValue("keypair", asAssemblyName.KeyPair);
982             }
983 
984             info.AddValue("asStr", asString);
985             info.AddValue("isSName", isSimpleName);
986             info.AddValue("hasCpuArch", hasProcessorArchitectureInFusionName);
987             info.AddValue("immutable", immutable);
988             info.AddValue("remapped", remappedFrom);
989         }
990     }
991 }
992