Merge pull request #323 from crazyjncsu/master
[mono.git] / mcs / class / IKVM.Reflection / AssemblyName.cs
1 /*
2   Copyright (C) 2009-2012 Jeroen Frijters
3
4   This software is provided 'as-is', without any express or implied
5   warranty.  In no event will the authors be held liable for any damages
6   arising from the use of this software.
7
8   Permission is granted to anyone to use this software for any purpose,
9   including commercial applications, and to alter it and redistribute it
10   freely, subject to the following restrictions:
11
12   1. The origin of this software must not be misrepresented; you must not
13      claim that you wrote the original software. If you use this software
14      in a product, an acknowledgment in the product documentation would be
15      appreciated but is not required.
16   2. Altered source versions must be plainly marked as such, and must not be
17      misrepresented as being the original software.
18   3. This notice may not be removed or altered from any source distribution.
19
20   Jeroen Frijters
21   jeroen@frijters.net
22   
23 */
24 using System;
25 using System.Globalization;
26 using System.Configuration.Assemblies;
27 using System.IO;
28 using System.Text;
29 using IKVM.Reflection.Reader;
30
31 namespace IKVM.Reflection
32 {
33         public sealed class AssemblyName : ICloneable
34         {
35                 private string name;
36                 private string culture;
37                 private Version version;
38                 private byte[] publicKeyToken;
39                 private byte[] publicKey;
40                 private StrongNameKeyPair keyPair;
41                 private AssemblyNameFlags flags;
42                 private AssemblyHashAlgorithm hashAlgorithm;
43                 private AssemblyVersionCompatibility versionCompatibility = AssemblyVersionCompatibility.SameMachine;
44                 private string codeBase;
45                 internal byte[] hash;
46
47                 public AssemblyName()
48                 {
49                 }
50
51                 public AssemblyName(string assemblyName)
52                 {
53                         if (assemblyName == null)
54                         {
55                                 throw new ArgumentNullException("assemblyName");
56                         }
57                         if (assemblyName == "")
58                         {
59                                 throw new ArgumentException();
60                         }
61                         ParsedAssemblyName parsed;
62                         switch (Fusion.ParseAssemblyName(assemblyName, out parsed))
63                         {
64                                 case ParseAssemblyResult.GenericError:
65                                         throw new FileLoadException();
66                                 case ParseAssemblyResult.DuplicateKey:
67                                         throw new System.Runtime.InteropServices.COMException();
68                         }
69                         name = parsed.Name;
70                         if (parsed.Culture != null)
71                         {
72                                 if (parsed.Culture.Equals("neutral", StringComparison.InvariantCultureIgnoreCase))
73                                 {
74                                         culture = "";
75                                 }
76                                 else if (parsed.Culture == "")
77                                 {
78                                         throw new FileLoadException();
79                                 }
80                                 else
81                                 {
82                                         culture = new CultureInfo(parsed.Culture).Name;
83                                 }
84                         }
85                         if (parsed.Version != null && parsed.Version.Major != 65535 && parsed.Version.Minor != 65535)
86                         {
87                                 // our Fusion parser returns -1 for build and revision for incomplete version numbers (and we want 65535)
88                                 version = new Version(parsed.Version.Major, parsed.Version.Minor, parsed.Version.Build & 0xFFFF, parsed.Version.Revision & 0xFFFF);
89                         }
90                         if (parsed.PublicKeyToken != null)
91                         {
92                                 if (parsed.PublicKeyToken.Equals("null", StringComparison.InvariantCultureIgnoreCase))
93                                 {
94                                         publicKeyToken = Empty<byte>.Array;
95                                 }
96                                 else if (parsed.PublicKeyToken.Length != 16)
97                                 {
98                                         throw new FileLoadException();
99                                 }
100                                 else
101                                 {
102                                         publicKeyToken = new byte[8];
103                                         for (int i = 0, pos = 0; i < publicKeyToken.Length; i++, pos += 2)
104                                         {
105                                                 publicKeyToken[i] = (byte)("0123456789abcdef".IndexOf(char.ToLowerInvariant(parsed.PublicKeyToken[pos])) * 16
106                                                         + "0123456789abcdef".IndexOf(char.ToLowerInvariant(parsed.PublicKeyToken[pos + 1])));
107                                         }
108                                 }
109                         }
110                         if (parsed.Retargetable.HasValue)
111                         {
112                                 if (parsed.Culture == null || parsed.PublicKeyToken == null || parsed.Version == null || parsed.Version.Build == -1 || parsed.Version.Revision == -1)
113                                 {
114                                         throw new FileLoadException();
115                                 }
116                                 if (parsed.Retargetable.Value)
117                                 {
118                                         flags |= AssemblyNameFlags.Retargetable;
119                                 }
120                         }
121                         ProcessorArchitecture = parsed.ProcessorArchitecture;
122                 }
123
124                 public override string ToString()
125                 {
126                         return FullName;
127                 }
128
129                 public string Name
130                 {
131                         get { return name; }
132                         set { name = value; }
133                 }
134
135                 public CultureInfo CultureInfo
136                 {
137                         get { return culture == null ? null : new CultureInfo(culture); }
138                         set { culture = value == null ? null : value.Name; }
139                 }
140
141                 internal string Culture
142                 {
143                         get { return culture; }
144                         set { culture = value; }
145                 }
146
147                 public Version Version
148                 {
149                         get { return version; }
150                         set { version = value; }
151                 }
152
153                 public StrongNameKeyPair KeyPair
154                 {
155                         get { return keyPair; }
156                         set { keyPair = value; }
157                 }
158
159                 public string CodeBase
160                 {
161                         get { return codeBase; }
162                         set { codeBase = value; }
163                 }
164
165                 public string EscapedCodeBase
166                 {
167                         get
168                         {
169                                 // HACK use the real AssemblyName to escape the codebase
170                                 System.Reflection.AssemblyName tmp = new System.Reflection.AssemblyName();
171                                 tmp.CodeBase = codeBase;
172                                 return tmp.EscapedCodeBase;
173                         }
174                 }
175
176                 public ProcessorArchitecture ProcessorArchitecture
177                 {
178                         get { return (ProcessorArchitecture)(((int)flags & 0x70) >> 4); }
179                         set
180                         {
181                                 if (value >= ProcessorArchitecture.None && value <= ProcessorArchitecture.Arm)
182                                 {
183                                         flags = (flags & ~(AssemblyNameFlags)0x70) | (AssemblyNameFlags)((int)value << 4);
184                                 }
185                         }
186                 }
187
188                 public AssemblyNameFlags Flags
189                 {
190                         get { return flags & (AssemblyNameFlags)~0xF0; }
191                         set { flags = (flags & (AssemblyNameFlags)0xF0) | (value & (AssemblyNameFlags)~0xF0); }
192                 }
193
194                 public AssemblyVersionCompatibility VersionCompatibility
195                 {
196                         get { return versionCompatibility; }
197                         set { versionCompatibility = value; }
198                 }
199
200                 public byte[] GetPublicKey()
201                 {
202                         return publicKey;
203                 }
204
205                 public void SetPublicKey(byte[] publicKey)
206                 {
207                         this.publicKey = publicKey;
208                         flags = (flags & ~AssemblyNameFlags.PublicKey) | (publicKey == null ? 0 : AssemblyNameFlags.PublicKey);
209                 }
210
211                 public byte[] GetPublicKeyToken()
212                 {
213                         if (publicKeyToken == null && publicKey != null)
214                         {
215                                 // note that GetPublicKeyToken() has a side effect in this case, because we retain this token even after the public key subsequently gets changed
216                                 publicKeyToken = ComputePublicKeyToken(publicKey);
217                         }
218                         return publicKeyToken;
219                 }
220
221                 public void SetPublicKeyToken(byte[] publicKeyToken)
222                 {
223                         this.publicKeyToken = publicKeyToken;
224                 }
225
226                 public AssemblyHashAlgorithm HashAlgorithm
227                 {
228                         get { return hashAlgorithm; }
229                         set { hashAlgorithm = value; }
230                 }
231
232                 public byte[] __Hash
233                 {
234                         get { return hash; }
235                 }
236
237                 public string FullName
238                 {
239                         get
240                         {
241                                 if (name == null)
242                                 {
243                                         return "";
244                                 }
245                                 StringBuilder sb = new StringBuilder();
246                                 bool doubleQuotes = name.StartsWith(" ") || name.EndsWith(" ") || name.IndexOf('\'') != -1;
247                                 bool singleQuotes = name.IndexOf('"') != -1;
248                                 if (singleQuotes)
249                                 {
250                                         sb.Append('\'');
251                                 }
252                                 else if (doubleQuotes)
253                                 {
254                                         sb.Append('"');
255                                 }
256                                 if (name.IndexOf(',') != -1 || name.IndexOf('\\') != -1 || name.IndexOf('=') != -1 || (singleQuotes && name.IndexOf('\'') != -1))
257                                 {
258                                         for (int i = 0; i < name.Length; i++)
259                                         {
260                                                 char c = name[i];
261                                                 if (c == ',' || c == '\\' || c == '=' || (singleQuotes && c == '\''))
262                                                 {
263                                                         sb.Append('\\');
264                                                 }
265                                                 sb.Append(c);
266                                         }
267                                 }
268                                 else
269                                 {
270                                         sb.Append(name);
271                                 }
272                                 if (singleQuotes)
273                                 {
274                                         sb.Append('\'');
275                                 }
276                                 else if (doubleQuotes)
277                                 {
278                                         sb.Append('"');
279                                 }
280                                 if (version != null)
281                                 {
282                                         if ((version.Major & 0xFFFF) != 0xFFFF)
283                                         {
284                                                 sb.Append(", Version=").Append(version.Major & 0xFFFF);
285                                                 if ((version.Minor & 0xFFFF) != 0xFFFF)
286                                                 {
287                                                         sb.Append('.').Append(version.Minor & 0xFFFF);
288                                                         if ((version.Build & 0xFFFF) != 0xFFFF)
289                                                         {
290                                                                 sb.Append('.').Append(version.Build & 0xFFFF);
291                                                                 if ((version.Revision & 0xFFFF) != 0xFFFF)
292                                                                 {
293                                                                         sb.Append('.').Append(version.Revision & 0xFFFF);
294                                                                 }
295                                                         }
296                                                 }
297                                         }
298                                 }
299                                 if (culture != null)
300                                 {
301                                         sb.Append(", Culture=").Append(culture == "" ? "neutral" : culture);
302                                 }
303                                 byte[] publicKeyToken = this.publicKeyToken;
304                                 if ((publicKeyToken == null || publicKeyToken.Length == 0) && publicKey != null)
305                                 {
306                                         publicKeyToken = ComputePublicKeyToken(publicKey);
307                                 }
308                                 if (publicKeyToken != null)
309                                 {
310                                         sb.Append(", PublicKeyToken=");
311                                         if (publicKeyToken.Length == 0)
312                                         {
313                                                 sb.Append("null");
314                                         }
315                                         else
316                                         {
317                                                 AppendPublicKey(sb, publicKeyToken);
318                                         }
319                                 }
320                                 if ((Flags & AssemblyNameFlags.Retargetable) != 0)
321                                 {
322                                         sb.Append(", Retargetable=Yes");
323                                 }
324                                 return sb.ToString();
325                         }
326                 }
327
328                 private static byte[] ComputePublicKeyToken(byte[] publicKey)
329                 {
330                         if (publicKey.Length == 0)
331                         {
332                                 return publicKey;
333                         }
334                         // HACK use the real AssemblyName to convert PublicKey to PublicKeyToken
335                         StringBuilder sb = new StringBuilder("Foo, PublicKey=", 20 + publicKey.Length * 2);
336                         AppendPublicKey(sb, publicKey);
337                         string str = sb.ToString();
338                         if (str == "Foo, PublicKey=00000000000000000400000000000000")
339                         {
340                                 // MONOBUG workaround Mono 2.10 bug (fixed in 2.11)
341                                 // it does not return the correct public key token for the ECMA key
342                                 return new byte[] { 0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89 };
343                         }
344                         return new System.Reflection.AssemblyName(str).GetPublicKeyToken();
345                 }
346
347                 private static void AppendPublicKey(StringBuilder sb, byte[] publicKey)
348                 {
349                         for (int i = 0; i < publicKey.Length; i++)
350                         {
351                                 sb.Append("0123456789abcdef"[publicKey[i] >> 4]);
352                                 sb.Append("0123456789abcdef"[publicKey[i] & 0x0F]);
353                         }
354                 }
355
356                 public override bool Equals(object obj)
357                 {
358                         AssemblyName other = obj as AssemblyName;
359                         return other != null && other.FullName == this.FullName;
360                 }
361
362                 public override int GetHashCode()
363                 {
364                         return FullName.GetHashCode();
365                 }
366
367                 public object Clone()
368                 {
369                         AssemblyName copy = (AssemblyName)MemberwiseClone();
370                         copy.publicKey = Copy(publicKey);
371                         copy.publicKeyToken = Copy(publicKeyToken);
372                         return copy;
373                 }
374
375                 private static byte[] Copy(byte[] b)
376                 {
377                         return b == null || b.Length == 0 ? b : (byte[])b.Clone();
378                 }
379
380                 public static bool ReferenceMatchesDefinition(AssemblyName reference, AssemblyName definition)
381                 {
382                         // HACK use the real AssemblyName to implement the (broken) ReferenceMatchesDefinition method
383                         return System.Reflection.AssemblyName.ReferenceMatchesDefinition(new System.Reflection.AssemblyName(reference.FullName), new System.Reflection.AssemblyName(definition.FullName));
384                 }
385
386                 public static AssemblyName GetAssemblyName(string path)
387                 {
388                         try
389                         {
390                                 path = Path.GetFullPath(path);
391                                 using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
392                                 {
393                                         ModuleReader module = new ModuleReader(null, null, fs, path);
394                                         if (module.Assembly == null)
395                                         {
396                                                 throw new BadImageFormatException("Module does not contain a manifest");
397                                         }
398                                         return module.Assembly.GetName();
399                                 }
400                         }
401                         catch (IOException x)
402                         {
403                                 throw new FileNotFoundException(x.Message, x);
404                         }
405                         catch (UnauthorizedAccessException x)
406                         {
407                                 throw new FileNotFoundException(x.Message, x);
408                         }
409                 }
410
411                 internal AssemblyNameFlags RawFlags
412                 {
413                         get { return flags; }
414                         set { flags = value; }
415                 }
416         }
417 }