IKVM11  11
Java SE 11 Virtual Machine for .NET
Loading...
Searching...
No Matches
AssemblyName.cs
Go to the documentation of this file.
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*/
24using System;
25using System.Globalization;
26using System.IO;
27using System.Reflection.Metadata;
28using System.Security.Cryptography;
29using System.Text;
30
32
33namespace IKVM.Reflection
34{
35
36 internal sealed class AssemblyName :
37 ICloneable
38 {
39
40 string name;
41 string culture;
42 Version version;
43 byte[] publicKeyToken;
44 byte[] publicKey;
45 StrongNameKeyPair keyPair;
46 AssemblyNameFlags flags;
47 AssemblyHashAlgorithm hashAlgorithm;
48 AssemblyVersionCompatibility versionCompatibility = AssemblyVersionCompatibility.SameMachine;
49 string codeBase;
50 internal byte[] hash;
51
55 public AssemblyName()
56 {
57
58 }
59
67 public AssemblyName(string assemblyName)
68 {
69 if (assemblyName == null)
70 throw new ArgumentNullException("assemblyName");
71 if (assemblyName == "")
72 throw new ArgumentException();
73
74 switch (Fusion.ParseAssemblyName(assemblyName, out var parsed))
75 {
76 case ParseAssemblyResult.GenericError:
77 case ParseAssemblyResult.DuplicateKey:
78 throw new FileLoadException();
79 }
80
81 if (!ParseVersion(parsed.Version, parsed.Retargetable.HasValue, out version))
82 throw new FileLoadException();
83
84 name = parsed.Name;
85 if (parsed.Culture != null)
86 {
87 if (parsed.Culture.Equals("neutral", StringComparison.OrdinalIgnoreCase))
88 culture = "";
89 else if (parsed.Culture == "")
90 throw new FileLoadException();
91 else
92 culture = new CultureInfo(parsed.Culture).Name;
93 }
94
95 if (parsed.PublicKeyToken != null)
96 {
97 if (parsed.PublicKeyToken.Equals("null", StringComparison.OrdinalIgnoreCase))
98 publicKeyToken = Array.Empty<byte>();
99 else if (parsed.PublicKeyToken.Length != 16)
100 throw new FileLoadException();
101 else
102 publicKeyToken = ParseKey(parsed.PublicKeyToken);
103 }
104
105 if (parsed.Retargetable.HasValue)
106 {
107 if (parsed.Culture == null || parsed.PublicKeyToken == null || version == null)
108 throw new FileLoadException();
109 if (parsed.Retargetable.Value)
110 flags |= AssemblyNameFlags.Retargetable;
111 }
112
113 ProcessorArchitecture = parsed.ProcessorArchitecture;
114 if (parsed.WindowsRuntime)
115 ContentType = AssemblyContentType.WindowsRuntime;
116 }
117
118 static byte[] ParseKey(string key)
119 {
120 if ((key.Length & 1) != 0)
121 throw new FileLoadException();
122
123 var buf = new byte[key.Length / 2];
124 for (int i = 0; i < buf.Length; i++)
125 buf[i] = (byte)(ParseHexDigit(key[i * 2]) * 16 + ParseHexDigit(key[i * 2 + 1]));
126
127 return buf;
128 }
129
130 static int ParseHexDigit(char digit)
131 {
132 if (digit >= '0' && digit <= '9')
133 {
134 return digit - '0';
135 }
136 else
137 {
138 digit |= (char)0x20;
139 if (digit >= 'a' && digit <= 'f')
140 return 10 + digit - 'a';
141 else
142 throw new FileLoadException();
143 }
144 }
145
146 public override string ToString()
147 {
148 return FullName;
149 }
150
151 public string Name
152 {
153 get => name;
154 set => name = value;
155 }
156
157 public CultureInfo CultureInfo
158 {
159 get => culture == null ? null : new CultureInfo(culture);
160 set => culture = value?.Name;
161 }
162
163 public string CultureName
164 {
165 get => culture;
166 set => culture = value;
167 }
168
169 public Version Version
170 {
171 get => version;
172 set => version = value;
173 }
174
175 public StrongNameKeyPair KeyPair
176 {
177 get => keyPair;
178 set => keyPair = value;
179 }
180
181 public string CodeBase
182 {
183 get => codeBase;
184 set => codeBase = value;
185 }
186
187 public string EscapedCodeBase
188 {
189 get
190 {
191 // HACK use the real AssemblyName to escape the codebase
192 var tmp = new System.Reflection.AssemblyName();
193 tmp.CodeBase = codeBase;
194 return tmp.EscapedCodeBase;
195 }
196 }
197
198 public ProcessorArchitecture ProcessorArchitecture
199 {
200 get => (ProcessorArchitecture)(((int)flags & 0x70) >> 4);
201 set
202 {
203 if (value >= ProcessorArchitecture.None && value <= ProcessorArchitecture.Arm64)
204 flags = (flags & ~(AssemblyNameFlags)0x70) | (AssemblyNameFlags)((int)value << 4);
205 }
206 }
207
208 public AssemblyNameFlags Flags
209 {
210 get => flags & (AssemblyNameFlags)~0xEF0;
211 set => flags = (flags & (AssemblyNameFlags)0xEF0) | (value & (AssemblyNameFlags)~0xEF0);
212 }
213
214 public AssemblyVersionCompatibility VersionCompatibility
215 {
216 get => versionCompatibility;
217 set => versionCompatibility = value;
218 }
219
220 public AssemblyContentType ContentType
221 {
222 get => (AssemblyContentType)(((int)flags & 0xE00) >> 9);
223 set
224 {
225 if (value >= AssemblyContentType.Default && value <= AssemblyContentType.WindowsRuntime)
226 flags = (flags & ~(AssemblyNameFlags)0xE00) | (AssemblyNameFlags)((int)value << 9);
227 }
228 }
229
230 public byte[] GetPublicKey()
231 {
232 return publicKey;
233 }
234
235 public void SetPublicKey(byte[] publicKey)
236 {
237 this.publicKey = publicKey;
238 flags = (flags & ~AssemblyNameFlags.PublicKey) | (publicKey == null ? 0 : AssemblyNameFlags.PublicKey);
239 }
240
241 public byte[] GetPublicKeyToken()
242 {
243 if (publicKeyToken == null && publicKey != null)
244 {
245 // note that GetPublicKeyToken() has a side effect in this case, because we retain this token even after the public key subsequently gets changed
246 publicKeyToken = ComputePublicKeyToken(publicKey);
247 }
248
249 return publicKeyToken;
250 }
251
252 public void SetPublicKeyToken(byte[] publicKeyToken)
253 {
254 this.publicKeyToken = publicKeyToken;
255 }
256
257 public AssemblyHashAlgorithm HashAlgorithm
258 {
259 get => hashAlgorithm;
260 set => hashAlgorithm = value;
261 }
262
263 public byte[] __Hash => hash;
264
265 public string FullName
266 {
267 get
268 {
269 if (name == null)
270 return "";
271
272 ushort versionMajor = 0xFFFF;
273 ushort versionMinor = 0xFFFF;
274 ushort versionBuild = 0xFFFF;
275 ushort versionRevision = 0xFFFF;
276
277 if (version != null)
278 {
279 versionMajor = (ushort)version.Major;
280 versionMinor = (ushort)version.Minor;
281 versionBuild = (ushort)version.Build;
282 versionRevision = (ushort)version.Revision;
283 }
284
285 var publicKeyToken = this.publicKeyToken;
286 if ((publicKeyToken == null || publicKeyToken.Length == 0) && publicKey != null)
287 publicKeyToken = ComputePublicKeyToken(publicKey);
288
289 return GetFullName(name, versionMajor, versionMinor, versionBuild, versionRevision, culture, publicKeyToken, (int)flags);
290 }
291 }
292
293 internal static string GetFullName(string name, ushort versionMajor, ushort versionMinor, ushort versionBuild, ushort versionRevision, string culture, byte[] publicKeyToken, int flags)
294 {
295 var sb = new StringBuilder();
296 bool doubleQuotes = name.StartsWith(" ") || name.EndsWith(" ") || name.IndexOf('\'') != -1;
297 bool singleQuotes = name.IndexOf('"') != -1;
298 if (singleQuotes)
299 sb.Append('\'');
300 else if (doubleQuotes)
301 sb.Append('"');
302
303 if (name.IndexOf(',') != -1 || name.IndexOf('\\') != -1 || name.IndexOf('=') != -1 || (singleQuotes && name.IndexOf('\'') != -1))
304 {
305 for (int i = 0; i < name.Length; i++)
306 {
307 char c = name[i];
308 if (c == ',' || c == '\\' || c == '=' || (singleQuotes && c == '\''))
309 {
310 sb.Append('\\');
311 }
312 sb.Append(c);
313 }
314 }
315 else
316 {
317 sb.Append(name);
318 }
319 if (singleQuotes)
320 {
321 sb.Append('\'');
322 }
323 else if (doubleQuotes)
324 {
325 sb.Append('"');
326 }
327 if (versionMajor != 0xFFFF)
328 {
329 sb.Append(", Version=").Append(versionMajor);
330 if (versionMinor != 0xFFFF)
331 {
332 sb.Append('.').Append(versionMinor);
333 if (versionBuild != 0xFFFF)
334 {
335 sb.Append('.').Append(versionBuild);
336 if (versionRevision != 0xFFFF)
337 {
338 sb.Append('.').Append(versionRevision);
339 }
340 }
341 }
342 }
343 if (culture != null)
344 {
345 sb.Append(", Culture=").Append(culture == "" ? "neutral" : culture);
346 }
347 if (publicKeyToken != null)
348 {
349 sb.Append(", PublicKeyToken=");
350 if (publicKeyToken.Length == 0)
351 {
352 sb.Append("null");
353 }
354 else
355 {
356 AppendPublicKey(sb, publicKeyToken);
357 }
358 }
359 if ((flags & (int)AssemblyNameFlags.Retargetable) != 0)
360 {
361 sb.Append(", Retargetable=Yes");
362 }
363 if ((AssemblyContentType)((flags & 0xE00) >> 9) == AssemblyContentType.WindowsRuntime)
364 {
365 sb.Append(", ContentType=WindowsRuntime");
366 }
367
368 return sb.ToString();
369 }
370
371 internal static byte[] ComputePublicKeyToken(byte[] publicKey)
372 {
373 if (publicKey.Length == 0)
374 return publicKey;
375
376 byte[] hash;
377 using (SHA1 sha1 = SHA1.Create())
378 hash = sha1.ComputeHash(publicKey);
379
380 var token = new byte[8];
381 for (int i = 0; i < token.Length; i++)
382 token[i] = hash[hash.Length - 1 - i];
383
384 return token;
385 }
386
387 internal static string ComputePublicKeyToken(string publicKey)
388 {
389 var sb = new StringBuilder(16);
390 AppendPublicKey(sb, ComputePublicKeyToken(ParseKey(publicKey)));
391 return sb.ToString();
392 }
393
394 static void AppendPublicKey(StringBuilder sb, byte[] publicKey)
395 {
396 for (int i = 0; i < publicKey.Length; i++)
397 {
398 sb.Append("0123456789abcdef"[publicKey[i] >> 4]);
399 sb.Append("0123456789abcdef"[publicKey[i] & 0x0F]);
400 }
401 }
402
403 public override bool Equals(object obj)
404 {
405 return obj is AssemblyName other && other.FullName == FullName;
406 }
407
408 public override int GetHashCode()
409 {
410 return FullName.GetHashCode();
411 }
412
413 public object Clone()
414 {
415 var copy = (AssemblyName)MemberwiseClone();
416 copy.publicKey = Copy(publicKey);
417 copy.publicKeyToken = Copy(publicKeyToken);
418 return copy;
419 }
420
421 static byte[] Copy(byte[] b)
422 {
423 return b == null || b.Length == 0 ? b : (byte[])b.Clone();
424 }
425
426 public static bool ReferenceMatchesDefinition(AssemblyName reference, AssemblyName definition)
427 {
428 // HACK use the real AssemblyName to implement the (broken) ReferenceMatchesDefinition method
429 return System.Reflection.AssemblyName.ReferenceMatchesDefinition(new System.Reflection.AssemblyName(reference.FullName), new System.Reflection.AssemblyName(definition.FullName));
430 }
431
439 public static AssemblyName GetAssemblyName(string assemblyFile)
440 {
441 try
442 {
443 using var st = new FileStream(Path.GetFullPath(assemblyFile), FileMode.Open, FileAccess.Read, FileShare.Read);
444 using var pe = new System.Reflection.PortableExecutable.PEReader(st);
445 var mr = pe.GetMetadataReader();
446
447 if (mr.IsAssembly == false)
448 throw new BadImageFormatException("Module does not contain a manifest");
449
450 var nm = mr.GetAssemblyDefinition().Name;
451 if (nm.IsNil)
452 throw new BadImageFormatException("Module does not contain a manifest");
453
454 return new AssemblyName(mr.GetString(nm));
455 }
456 catch (IOException e)
457 {
458 throw new FileNotFoundException(e.Message, e);
459 }
460 catch (UnauthorizedAccessException e)
461 {
462 throw new FileNotFoundException(e.Message, e);
463 }
464 }
465
466 internal AssemblyNameFlags RawFlags
467 {
468 get => flags;
469 set => flags = value;
470 }
471
472 static bool ParseVersion(string str, bool mustBeComplete, out Version version)
473 {
474 version = null;
475
476 if (str == null)
477 return true;
478
479 var parts = str.Split('.');
480 if (parts.Length < 2 || parts.Length > 4)
481 {
482 // if the version consists of a single integer, it is invalid, but not invalid enough to fail the parse of the whole assembly name
483 return parts.Length == 1 && ushort.TryParse(parts[0], NumberStyles.Integer, null, out _);
484 }
485 if (parts[0] == "" || parts[1] == "")
486 {
487 // this is a strange scenario, the version is invalid, but not invalid enough to fail the parse of the whole assembly name
488 return true;
489 }
490
491 ushort build = 65535, revision = 65535;
492 if (ushort.TryParse(parts[0], NumberStyles.Integer, null, out ushort major) &&
493 ushort.TryParse(parts[1], NumberStyles.Integer, null, out ushort minor) &&
494 (parts.Length <= 2 || parts[2] == "" || ushort.TryParse(parts[2], NumberStyles.Integer, null, out build)) &&
495 (parts.Length <= 3 || parts[3] == "" || (parts[2] != "" && ushort.TryParse(parts[3], NumberStyles.Integer, null, out revision))))
496 {
497 if (mustBeComplete && (parts.Length < 4 || parts[2] == "" || parts[3] == ""))
498 version = null;
499 else if (major == 65535 || minor == 65535)
500 version = null;
501 else
502 version = new Version(major, minor, build, revision);
503
504 return true;
505 }
506
507 version = null;
508 return false;
509 }
510
511 }
512
513}
IKVM.Reflection.AssemblyName AssemblyName
global::java.lang.invoke.LambdaForm.Name Name