Merge pull request #214 from QuickJack/cd2c570c5543963d987f51080218715407c5d4b9
[mono.git] / mcs / class / IKVM.Reflection / AssemblyName.cs
1 /*
2   Copyright (C) 2009-2011 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 string FullName
233                 {
234                         get
235                         {
236                                 if (name == null)
237                                 {
238                                         return "";
239                                 }
240                                 StringBuilder sb = new StringBuilder();
241                                 bool doubleQuotes = name.StartsWith(" ") || name.EndsWith(" ") || name.IndexOf('\'') != -1;
242                                 bool singleQuotes = name.IndexOf('"') != -1;
243                                 if (singleQuotes)
244                                 {
245                                         sb.Append('\'');
246                                 }
247                                 else if (doubleQuotes)
248                                 {
249                                         sb.Append('"');
250                                 }
251                                 if (name.IndexOf(',') != -1 || name.IndexOf('\\') != -1 || name.IndexOf('=') != -1 || (singleQuotes && name.IndexOf('\'') != -1))
252                                 {
253                                         for (int i = 0; i < name.Length; i++)
254                                         {
255                                                 char c = name[i];
256                                                 if (c == ',' || c == '\\' || c == '=' || (singleQuotes && c == '\''))
257                                                 {
258                                                         sb.Append('\\');
259                                                 }
260                                                 sb.Append(c);
261                                         }
262                                 }
263                                 else
264                                 {
265                                         sb.Append(name);
266                                 }
267                                 if (singleQuotes)
268                                 {
269                                         sb.Append('\'');
270                                 }
271                                 else if (doubleQuotes)
272                                 {
273                                         sb.Append('"');
274                                 }
275                                 if (version != null)
276                                 {
277                                         if ((version.Major & 0xFFFF) != 0xFFFF)
278                                         {
279                                                 sb.AppendFormat(", Version={0}", version.Major & 0xFFFF);
280                                                 if ((version.Minor & 0xFFFF) != 0xFFFF)
281                                                 {
282                                                         sb.AppendFormat(".{0}", version.Minor & 0xFFFF);
283                                                         if ((version.Build & 0xFFFF) != 0xFFFF)
284                                                         {
285                                                                 sb.AppendFormat(".{0}", version.Build & 0xFFFF);
286                                                                 if ((version.Revision & 0xFFFF) != 0xFFFF)
287                                                                 {
288                                                                         sb.AppendFormat(".{0}", version.Revision & 0xFFFF);
289                                                                 }
290                                                         }
291                                                 }
292                                         }
293                                 }
294                                 if (culture != null)
295                                 {
296                                         sb.Append(", Culture=").Append(culture == "" ? "neutral" : culture);
297                                 }
298                                 byte[] publicKeyToken = this.publicKeyToken;
299                                 if ((publicKeyToken == null || publicKeyToken.Length == 0) && publicKey != null)
300                                 {
301                                         publicKeyToken = ComputePublicKeyToken(publicKey);
302                                 }
303                                 if (publicKeyToken != null)
304                                 {
305                                         sb.Append(", PublicKeyToken=");
306                                         if (publicKeyToken.Length == 0)
307                                         {
308                                                 sb.Append("null");
309                                         }
310                                         else
311                                         {
312                                                 for (int i = 0; i < publicKeyToken.Length; i++)
313                                                 {
314                                                         sb.AppendFormat("{0:x2}", publicKeyToken[i]);
315                                                 }
316                                         }
317                                 }
318                                 if ((Flags & AssemblyNameFlags.Retargetable) != 0)
319                                 {
320                                         sb.Append(", Retargetable=Yes");
321                                 }
322                                 return sb.ToString();
323                         }
324                 }
325
326                 private static byte[] ComputePublicKeyToken(byte[] publicKey)
327                 {
328                         if (publicKey.Length == 0)
329                         {
330                                 return publicKey;
331                         }
332                         // HACK use the real AssemblyName to convert PublicKey to PublicKeyToken
333                         StringBuilder sb = new StringBuilder("Foo, PublicKey=", 20 + publicKey.Length * 2);
334                         for (int i = 0; i < publicKey.Length; i++)
335                         {
336                                 sb.AppendFormat("{0:x2}", publicKey[i]);
337                         }
338                         return new System.Reflection.AssemblyName(sb.ToString()).GetPublicKeyToken();
339                 }
340
341                 public override bool Equals(object obj)
342                 {
343                         AssemblyName other = obj as AssemblyName;
344                         return other != null && other.FullName == this.FullName;
345                 }
346
347                 public override int GetHashCode()
348                 {
349                         return FullName.GetHashCode();
350                 }
351
352                 public object Clone()
353                 {
354                         AssemblyName copy = (AssemblyName)MemberwiseClone();
355                         copy.publicKey = Copy(publicKey);
356                         copy.publicKeyToken = Copy(publicKeyToken);
357                         return copy;
358                 }
359
360                 private static byte[] Copy(byte[] b)
361                 {
362                         return b == null || b.Length == 0 ? b : (byte[])b.Clone();
363                 }
364
365                 public static bool ReferenceMatchesDefinition(AssemblyName reference, AssemblyName definition)
366                 {
367                         // HACK use the real AssemblyName to implement the (broken) ReferenceMatchesDefinition method
368                         return System.Reflection.AssemblyName.ReferenceMatchesDefinition(new System.Reflection.AssemblyName(reference.FullName), new System.Reflection.AssemblyName(definition.FullName));
369                 }
370
371                 public static AssemblyName GetAssemblyName(string path)
372                 {
373                         try
374                         {
375                                 path = Path.GetFullPath(path);
376                                 using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
377                                 {
378                                         ModuleReader module = new ModuleReader(null, null, fs, path);
379                                         if (module.Assembly == null)
380                                         {
381                                                 throw new BadImageFormatException("Module does not contain a manifest");
382                                         }
383                                         return module.Assembly.GetName();
384                                 }
385                         }
386                         catch (IOException x)
387                         {
388                                 throw new FileNotFoundException(x.Message, x);
389                         }
390                         catch (UnauthorizedAccessException x)
391                         {
392                                 throw new FileNotFoundException(x.Message, x);
393                         }
394                 }
395
396                 internal AssemblyNameFlags RawFlags
397                 {
398                         get { return flags; }
399                         set { flags = value; }
400                 }
401         }
402 }