BindingFlags.Public needed here as Exception.HResult is now public in .NET 4.5. This...
[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                         return new System.Reflection.AssemblyName(sb.ToString()).GetPublicKeyToken();
338                 }
339
340                 private static void AppendPublicKey(StringBuilder sb, byte[] publicKey)
341                 {
342                         for (int i = 0; i < publicKey.Length; i++)
343                         {
344                                 sb.Append("0123456789abcdef"[publicKey[i] >> 4]);
345                                 sb.Append("0123456789abcdef"[publicKey[i] & 0x0F]);
346                         }
347                 }
348
349                 public override bool Equals(object obj)
350                 {
351                         AssemblyName other = obj as AssemblyName;
352                         return other != null && other.FullName == this.FullName;
353                 }
354
355                 public override int GetHashCode()
356                 {
357                         return FullName.GetHashCode();
358                 }
359
360                 public object Clone()
361                 {
362                         AssemblyName copy = (AssemblyName)MemberwiseClone();
363                         copy.publicKey = Copy(publicKey);
364                         copy.publicKeyToken = Copy(publicKeyToken);
365                         return copy;
366                 }
367
368                 private static byte[] Copy(byte[] b)
369                 {
370                         return b == null || b.Length == 0 ? b : (byte[])b.Clone();
371                 }
372
373                 public static bool ReferenceMatchesDefinition(AssemblyName reference, AssemblyName definition)
374                 {
375                         // HACK use the real AssemblyName to implement the (broken) ReferenceMatchesDefinition method
376                         return System.Reflection.AssemblyName.ReferenceMatchesDefinition(new System.Reflection.AssemblyName(reference.FullName), new System.Reflection.AssemblyName(definition.FullName));
377                 }
378
379                 public static AssemblyName GetAssemblyName(string path)
380                 {
381                         try
382                         {
383                                 path = Path.GetFullPath(path);
384                                 using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
385                                 {
386                                         ModuleReader module = new ModuleReader(null, null, fs, path);
387                                         if (module.Assembly == null)
388                                         {
389                                                 throw new BadImageFormatException("Module does not contain a manifest");
390                                         }
391                                         return module.Assembly.GetName();
392                                 }
393                         }
394                         catch (IOException x)
395                         {
396                                 throw new FileNotFoundException(x.Message, x);
397                         }
398                         catch (UnauthorizedAccessException x)
399                         {
400                                 throw new FileNotFoundException(x.Message, x);
401                         }
402                 }
403
404                 internal AssemblyNameFlags RawFlags
405                 {
406                         get { return flags; }
407                         set { flags = value; }
408                 }
409         }
410 }