main
cs 225 lines 9.06 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 namespace WixToolset.Core.WindowsInstaller.Bind
4 {
5 using System;
6 using System.IO;
7 using System.Reflection.Metadata;
8 using System.Reflection.PortableExecutable;
9 using System.Security.Cryptography;
10 using System.Text;
11 using System.Xml;
12 using System.Xml.XPath;
13 using WixToolset.Data;
14 using WixToolset.Extensibility.Services;
15
16 internal static class AssemblyNameReader
17 {
18 public static AssemblyName ReadAssembly(IFileSystem fileSystem, SourceLineNumber sourceLineNumbers, string assemblyPath, string fileVersion)
19 {
20 try
21 {
22 using (var stream = fileSystem.OpenFile(sourceLineNumbers, assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read))
23 using (var peReader = new PEReader(stream))
24 {
25 var reader = peReader.GetMetadataReader();
26 var headers = peReader.PEHeaders;
27
28 var assembly = reader.GetAssemblyDefinition();
29 var attributes = assembly.GetCustomAttributes();
30
31 var name = ReadString(reader, assembly.Name);
32 var culture = ReadString(reader, assembly.Culture);
33 var architecture = ArchitectureFromHeaders(headers);
34 var version = assembly.Version.ToString();
35 var publicKeyToken = ReadPublicKeyToken(reader, assembly.PublicKey);
36
37 // There is a bug in v1 fusion that requires the assembly's "version" attribute
38 // to be equal to or longer than the "fileVersion" in length when its present;
39 // the workaround is to prepend zeroes to the last version number in the assembly
40 // version.
41 if (IsNetFx1xAssembly(headers) && !String.IsNullOrEmpty(fileVersion) && fileVersion.Length > version.Length)
42 {
43 var versionParts = version.Split('.');
44
45 if (versionParts.Length > 0)
46 {
47 var padding = new string('0', fileVersion.Length - version.Length);
48
49 versionParts[versionParts.Length - 1] = String.Concat(padding, versionParts[versionParts.Length - 1]);
50 version = String.Join(".", versionParts);
51 }
52 }
53
54 return new AssemblyName(name, culture, version, fileVersion, architecture, publicKeyToken, null);
55 }
56 }
57 catch (Exception e) when (e is FileNotFoundException || e is BadImageFormatException || e is InvalidOperationException)
58 {
59 throw new WixException(ErrorMessages.InvalidAssemblyFile(sourceLineNumbers, assemblyPath, $"{e.GetType().Name}: {e.Message}"));
60 }
61 }
62
63 public static AssemblyName ReadAssemblyManifest(SourceLineNumber sourceLineNumbers, string manifestPath)
64 {
65 string win32Type = null;
66 string win32Name = null;
67 string win32Version = null;
68 string win32ProcessorArchitecture = null;
69 string win32PublicKeyToken = null;
70
71 // Loading the dom is expensive we want more performant APIs than the DOM
72 // Navigator is cheaper than dom. Perhaps there is a cheaper API still.
73 try
74 {
75 var doc = new XPathDocument(manifestPath);
76 var nav = doc.CreateNavigator();
77 nav.MoveToRoot();
78
79 // This assumes a particular schema for a win32 manifest and does not
80 // provide error checking if the file does not conform to schema.
81 // The fallback case here is that nothing is added to the MsiAssemblyName
82 // table for an out of tolerance Win32 manifest. Perhaps warnings needed.
83 if (nav.MoveToFirstChild())
84 {
85 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly")
86 {
87 nav.MoveToNext();
88 }
89
90 if (nav.MoveToFirstChild())
91 {
92 var hasNextSibling = true;
93 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling)
94 {
95 hasNextSibling = nav.MoveToNext();
96 }
97
98 if (!hasNextSibling)
99 {
100 throw new WixException(ErrorMessages.InvalidManifestContent(sourceLineNumbers, manifestPath));
101 }
102
103 if (nav.MoveToAttribute("type", String.Empty))
104 {
105 win32Type = nav.Value;
106 nav.MoveToParent();
107 }
108
109 if (nav.MoveToAttribute("name", String.Empty))
110 {
111 win32Name = nav.Value;
112 nav.MoveToParent();
113 }
114
115 if (nav.MoveToAttribute("version", String.Empty))
116 {
117 win32Version = nav.Value;
118 nav.MoveToParent();
119 }
120
121 if (nav.MoveToAttribute("processorArchitecture", String.Empty))
122 {
123 win32ProcessorArchitecture = nav.Value;
124 nav.MoveToParent();
125 }
126
127 if (nav.MoveToAttribute("publicKeyToken", String.Empty))
128 {
129 win32PublicKeyToken = nav.Value;
130 nav.MoveToParent();
131 }
132 }
133 }
134 }
135 catch (FileNotFoundException fe)
136 {
137 throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, fe.FileName, "AssemblyManifest"));
138 }
139 catch (XmlException xe)
140 {
141 throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "manifest", xe.Message));
142 }
143
144 return new AssemblyName(win32Name, null, win32Version, null, win32ProcessorArchitecture, win32PublicKeyToken, win32Type);
145 }
146
147 private static string ArchitectureFromHeaders(PEHeaders headers)
148 {
149 if (headers.PEHeader.Magic == PEMagic.PE32Plus)
150 {
151 return "AMD64";
152 }
153 else if ((headers.CorHeader.Flags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit)
154 {
155 return "x86";
156 }
157 else if (IsNetFx1xAssembly(headers))
158 {
159 // .NET Framework 1.x didn't support 64-bit, so if the assembly isn't explicitly 32-bit-required,
160 // the architecture wasn't specified, unlike .NET 2.0 and later, which identify it as MSIL.
161 return null;
162 }
163 else if ((headers.CorHeader.Flags & CorFlags.ILOnly) == CorFlags.ILOnly)
164 {
165 return "MSIL";
166 }
167 else
168 {
169 // We return "x86" here because that seems to best match the Fusion-based
170 // GetAssemblyIdentityFromFile() method of acquiring the assembly identity.
171 return "x86";
172 }
173 }
174
175 private static bool IsNetFx1xAssembly(PEHeaders headers)
176 {
177 return headers.CorHeader.MajorRuntimeVersion == 2 && headers.CorHeader.MinorRuntimeVersion == 0;
178 }
179
180 private static string ReadString(MetadataReader reader, StringHandle handle)
181 {
182 return handle.IsNil ? null : reader.GetString(handle);
183 }
184
185 private static string ReadPublicKeyToken(MetadataReader reader, BlobHandle handle)
186 {
187 if (handle.IsNil)
188 {
189 return null;
190 }
191
192 var bytes = reader.GetBlobBytes(handle);
193 if (bytes.Length == 0)
194 {
195 return null;
196 }
197
198 var result = new StringBuilder();
199
200 // If we have the full public key, calculate the public key token from the
201 // last 8 bytes (in reverse order) of the public key's SHA1 hash.
202 if (bytes.Length > 8)
203 {
204 using (var sha1 = SHA1.Create())
205 {
206 var hash = sha1.ComputeHash(bytes);
207
208 for (var i = 1; i <= 8; ++i)
209 {
210 result.Append(hash[hash.Length - i].ToString("X2"));
211 }
212 }
213 }
214 else
215 {
216 foreach (var b in bytes)
217 {
218 result.Append(b.ToString("X2"));
219 }
220 }
221
222 return result.ToString();
223 }
224 }
225 }