aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2018-12-26 12:40:58 -0800
committerRob Mensching <rob@robmensching.com>2018-12-26 14:03:10 -0800
commit232176210981a8e21793f6096dd651eba29caf8e (patch)
tree5880b1b9c968f494fbab3f78c07cba95ed309c40 /src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs
parent758bcf4bb6d61dd9d6471d9f9629d4f7b18d43cb (diff)
downloadwix-232176210981a8e21793f6096dd651eba29caf8e.tar.gz
wix-232176210981a8e21793f6096dd651eba29caf8e.tar.bz2
wix-232176210981a8e21793f6096dd651eba29caf8e.zip
Populate MsiAssemblyName table
Fixes wixtoolset/issues#5865
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs192
1 files changed, 192 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs
new file mode 100644
index 00000000..4815cb35
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs
@@ -0,0 +1,192 @@
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
3namespace 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
15 internal static class AssemblyNameReader
16 {
17 public static AssemblyName ReadAssembly(SourceLineNumber sourceLineNumbers, string assemblyPath, string fileVersion)
18 {
19 try
20 {
21 using (var stream = File.OpenRead(assemblyPath))
22 using (var peReader = new PEReader(stream))
23 {
24 var reader = peReader.GetMetadataReader();
25 var headers = peReader.PEHeaders;
26
27 var assembly = reader.GetAssemblyDefinition();
28 var attributes = assembly.GetCustomAttributes();
29
30 var name = ReadString(reader, assembly.Name);
31 var culture = ReadString(reader, assembly.Culture);
32 var architecture = headers.PEHeader.Magic == PEMagic.PE32Plus ? "x64" : (headers.CorHeader.Flags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit ? "x86" : null;
33 var version = assembly.Version.ToString();
34 var publicKeyToken = ReadPublicKeyToken(reader, assembly.PublicKey);
35
36 // There is a bug in v1 fusion that requires the assembly's "version" attribute
37 // to be equal to or longer than the "fileVersion" in length when its present;
38 // the workaround is to prepend zeroes to the last version number in the assembly
39 // version.
40 var targetNetfx1 = (headers.CorHeader.MajorRuntimeVersion == 2) && (headers.CorHeader.MinorRuntimeVersion == 0);
41 if (targetNetfx1 && !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 ReadString(MetadataReader reader, StringHandle handle)
148 {
149 return handle.IsNil ? null : reader.GetString(handle);
150 }
151
152 private static string ReadPublicKeyToken(MetadataReader reader, BlobHandle handle)
153 {
154 if (handle.IsNil)
155 {
156 return null;
157 }
158
159 var bytes = reader.GetBlobBytes(handle);
160 if (bytes.Length == 0)
161 {
162 return null;
163 }
164
165 var result = new StringBuilder();
166
167 // If we have the full public key, calculate the public key token from the
168 // last 8 bytes (in reverse order) of the public key's SHA1 hash.
169 if (bytes.Length > 8)
170 {
171 using (var sha1 = SHA1.Create())
172 {
173 var hash = sha1.ComputeHash(bytes);
174
175 for (var i = 1; i <= 8; ++i)
176 {
177 result.Append(hash[hash.Length - i].ToString("X2"));
178 }
179 }
180 }
181 else
182 {
183 foreach (var b in bytes)
184 {
185 result.Append(b.ToString("X2"));
186 }
187 }
188
189 return result.ToString();
190 }
191 }
192}