diff options
| author | Rob Mensching <rob@firegiant.com> | 2018-12-26 12:40:58 -0800 |
|---|---|---|
| committer | Rob Mensching <rob@robmensching.com> | 2018-12-26 14:03:10 -0800 |
| commit | 232176210981a8e21793f6096dd651eba29caf8e (patch) | |
| tree | 5880b1b9c968f494fbab3f78c07cba95ed309c40 /src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs | |
| parent | 758bcf4bb6d61dd9d6471d9f9629d4f7b18d43cb (diff) | |
| download | wix-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.cs | 192 |
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 | |||
| 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 | |||
| 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 | } | ||
