@joebigelow / wix / commits / b3deb46d

add firewall extension decompiler

chris_bednarski committed Aug 26, 2023 at 08:31 UTC b3deb46def7d303a79f4a5eba6a921e45d5f16ef
5 files changed +164 -81
src/ext/Firewall/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs
+58 -1
@@ -2,9 +2,11 @@
2
3 namespace WixToolsetTest.Firewall
4 {
5 + using System.IO;
6 using System.Linq;
6 - using WixInternal.TestSupport;
7 + using System.Xml.Linq;
8 using WixInternal.Core.TestPackage;
9 + using WixInternal.TestSupport;
10 using WixToolset.Firewall;
11 using Xunit;
12
@@ -50,6 +52,55 @@ namespace WixToolsetTest.Firewall
52 }, results);
53 }
54
55 + [Fact]
56 + public void CanRoundtripFirewallExceptions()
57 + {
58 + var folder = TestData.Get(@"TestData", "UsingFirewall");
59 + var build = new Builder(folder, typeof(FirewallExtensionFactory), new[] { folder });
60 + var output = Path.Combine(folder, "FirewallExceptionDecompile.xml");
61 +
62 + build.BuildAndDecompileAndBuild(Build, Decompile, output);
63 +
64 + var doc = XDocument.Load(output);
65 + var actual = doc.Descendants()
66 + .Where(e => e.Name.Namespace == "http://wixtoolset.org/schemas/v4/wxs/firewall")
67 + .Select(fe => new { Name = fe.Name.LocalName, Attributes = fe.Attributes().Select(a => $"{a.Name.LocalName}={a.Value}").ToArray() })
68 + .ToArray();
69 +
70 + WixAssert.CompareLineByLine(new[]
71 + {
72 + "FirewallException",
73 + "FirewallException",
74 + }, actual.Select(a => a.Name).ToArray());
75 +
76 + WixAssert.CompareLineByLine(new[]
77 + {
78 + "Id=ExampleFirewall",
79 + "Name=ExampleApp",
80 + "Scope=any",
81 + "Port=42",
82 + "Protocol=tcp",
83 + "Program=[#filNdJBJmq3UCUIwmXS8x21aAsvqzk]",
84 + "Profile=all",
85 + "Description=An app-based firewall exception",
86 + "Outbound=no",
87 + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall",
88 + }, actual[0].Attributes);
89 +
90 + WixAssert.CompareLineByLine(new[]
91 + {
92 + "Id=fex70IVsYNnbwiHQrEepmdTPKH8XYs",
93 + "Name=ExamplePort",
94 + "Scope=localSubnet",
95 + "Port=42",
96 + "Protocol=tcp",
97 + "Profile=all",
98 + "Description=A port-based firewall exception",
99 + "Outbound=yes",
100 + "xmlns=http://wixtoolset.org/schemas/v4/wxs/firewall",
101 + }, actual[1].Attributes);
102 + }
103 +
104 private static void Build(string[] args)
105 {
106 var result = WixRunner.Execute(args);
@@ -65,5 +116,11 @@ namespace WixToolsetTest.Firewall
116 var result = WixRunner.Execute(newArgs.ToArray());
117 result.AssertSuccess();
118 }
119 +
120 + private static void Decompile(string[] args)
121 + {
122 + var result = WixRunner.Execute(args);
123 + result.AssertSuccess();
124 + }
125 }
126 }
src/ext/Firewall/wixext/FirewallCompiler.cs
+1 -1
@@ -15,7 +15,7 @@ namespace WixToolset.Firewall
15 /// </summary>
16 public sealed class FirewallCompiler : BaseCompilerExtension
17 {
18 - public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/firewall";
18 + public override XNamespace Namespace => FirewallConstants.Namespace;
19
20 /// <summary>
21 /// Processes an element for the Compiler.
src/ext/Firewall/wixext/FirewallConstants.cs
+5 -3
@@ -2,12 +2,14 @@
2
3 namespace WixToolset.Firewall
4 {
5 - using System;
6 - using System.Collections.Generic;
7 - using System.Text;
5 + using System.Xml.Linq;
6
7 static class FirewallConstants
8 {
9 + internal static readonly XNamespace Namespace = "http://wixtoolset.org/schemas/v4/wxs/firewall";
10 + internal static readonly XName FirewallExceptionName = Namespace + "FirewallException";
11 + internal static readonly XName RemoteAddressName = Namespace + "RemoteAddress";
12 +
13 // from icftypes.h
14 public const int NET_FW_RULE_DIR_IN = 1;
15 public const int NET_FW_RULE_DIR_OUT = 2;
src/ext/Firewall/wixext/FirewallDecompiler.cs
+98 -75
@@ -2,54 +2,53 @@
2
3 namespace WixToolset.Firewall
4 {
5 -#if TODO_CONSIDER_DECOMPILER
5 using System;
7 - using System.Collections;
8 - using System.Diagnostics;
9 - using System.Globalization;
6 + using System.Collections.Generic;
7 + using System.Xml.Linq;
8 using WixToolset.Data;
9 + using WixToolset.Data.WindowsInstaller;
10 using WixToolset.Extensibility;
12 - using Firewall = WixToolset.Extensions.Serialize.Firewall;
13 - using Wix = WixToolset.Data.Serialize;
11
12 /// <summary>
13 /// The decompiler for the WiX Toolset Firewall Extension.
14 /// </summary>
18 - public sealed class FirewallDecompiler : DecompilerExtension
15 + public sealed class FirewallDecompiler : BaseWindowsInstallerDecompilerExtension
16 {
20 - /// <summary>
21 - /// Creates a decompiler for Firewall Extension.
22 - /// </summary>
23 - public FirewallDecompiler()
24 - {
25 - this.TableDefinitions = FirewallExtensionData.GetExtensionTableDefinitions();
26 - }
17 + public override IReadOnlyCollection<TableDefinition> TableDefinitions => FirewallTableDefinitions.All;
18
19 /// <summary>
29 - /// Get the extensions library to be removed.
20 + /// Called at the beginning of the decompilation of a database.
21 /// </summary>
31 - /// <param name="tableDefinitions">Table definitions for library.</param>
32 - /// <returns>Library to remove from decompiled output.</returns>
33 - public override Library GetLibraryToRemove(TableDefinitionCollection tableDefinitions)
22 + /// <param name="tables">The collection of all tables.</param>
23 + public override void PreDecompileTables(TableIndexedCollection tables)
24 {
35 - return FirewallExtensionData.GetExtensionLibrary(tableDefinitions);
25 }
26
27 /// <summary>
28 /// Decompiles an extension table.
29 /// </summary>
30 /// <param name="table">The table to decompile.</param>
42 - public override void DecompileTable(Table table)
31 + public override bool TryDecompileTable(Table table)
32 {
33 switch (table.Name)
34 {
46 - case "WixFirewallException":
35 + case "Wix4FirewallException":
36 this.DecompileWixFirewallExceptionTable(table);
37 break;
38 default:
50 - base.DecompileTable(table);
51 - break;
39 + return false;
40 }
41 +
42 + return true;
43 + }
44 +
45 + /// <summary>
46 + /// Finalize decompilation.
47 + /// </summary>
48 + /// <param name="tables">The collection of all tables.</param>
49 + public override void PostDecompileTables(TableIndexedCollection tables)
50 + {
51 + this.FinalizeFirewallExceptionTable(tables);
52 }
53
54 /// <summary>
@@ -60,38 +59,42 @@ namespace WixToolset.Firewall
59 {
60 foreach (Row row in table.Rows)
61 {
63 - Firewall.FirewallException fire = new Firewall.FirewallException();
64 - fire.Id = (string)row[0];
65 - fire.Name = (string)row[1];
62 + var firewallException = new XElement(FirewallConstants.FirewallExceptionName,
63 + new XAttribute("Id", row.FieldAsString(0)),
64 + new XAttribute("Name", row.FieldAsString(1))
65 + );
66
67 - string[] addresses = ((string)row[2]).Split(',');
68 - if (1 == addresses.Length)
67 + if (!row.IsColumnEmpty(2))
68 {
70 - // special-case the Scope attribute values
71 - if ("*" == addresses[0])
69 + string[] addresses = ((string)row[2]).Split(',');
70 + if (addresses.Length == 1)
71 {
73 - fire.Scope = Firewall.FirewallException.ScopeType.any;
74 - }
75 - else if ("LocalSubnet" == addresses[0])
76 - {
77 - fire.Scope = Firewall.FirewallException.ScopeType.localSubnet;
72 + // special-case the Scope attribute values
73 + if (addresses[0] == "*")
74 + {
75 + firewallException.Add(new XAttribute("Scope", "any"));
76 + }
77 + else if (addresses[0] == "LocalSubnet")
78 + {
79 + firewallException.Add(new XAttribute("Scope", "localSubnet"));
80 + }
81 + else
82 + {
83 + FirewallDecompiler.AddRemoteAddress(firewallException, addresses[0]);
84 + }
85 }
86 else
87 {
81 - FirewallDecompiler.AddRemoteAddress(fire, addresses[0]);
82 - }
83 - }
84 - else
85 - {
86 - foreach (string address in addresses)
87 - {
88 - FirewallDecompiler.AddRemoteAddress(fire, address);
88 + foreach (string address in addresses)
89 + {
90 + FirewallDecompiler.AddRemoteAddress(firewallException, address);
91 + }
92 }
93 }
94
95 if (!row.IsColumnEmpty(3))
96 {
94 - fire.Port = (string)row[3];
97 + firewallException.Add(new XAttribute("Port", row.FieldAsString(3)));
98 }
99
100 if (!row.IsColumnEmpty(4))
@@ -99,26 +102,23 @@ namespace WixToolset.Firewall
102 switch (Convert.ToInt32(row[4]))
103 {
104 case FirewallConstants.NET_FW_IP_PROTOCOL_TCP:
102 - fire.Protocol = Firewall.FirewallException.ProtocolType.tcp;
105 + firewallException.Add(new XAttribute("Protocol", "tcp"));
106 break;
107 case FirewallConstants.NET_FW_IP_PROTOCOL_UDP:
105 - fire.Protocol = Firewall.FirewallException.ProtocolType.udp;
108 + firewallException.Add(new XAttribute("Protocol", "udp"));
109 break;
110 }
111 }
112
113 if (!row.IsColumnEmpty(5))
114 {
112 - fire.Program = (string)row[5];
115 + firewallException.Add(new XAttribute("Program", row.FieldAsString(5)));
116 }
117
118 if (!row.IsColumnEmpty(6))
119 {
117 - int attr = Convert.ToInt32(row[6]);
118 - if (0x1 == (attr & 0x1)) // feaIgnoreFailures
119 - {
120 - fire.IgnoreFailure = Firewall.YesNoType.yes;
121 - }
120 + var attr = Convert.ToInt32(row[6]);
121 + AttributeIfNotNull("IgnoreFailure", (attr & 0x1) == 0x1);
122 }
123
124 if (!row.IsColumnEmpty(7))
@@ -126,24 +126,23 @@ namespace WixToolset.Firewall
126 switch (Convert.ToInt32(row[7]))
127 {
128 case FirewallConstants.NET_FW_PROFILE2_DOMAIN:
129 - fire.Profile = Firewall.FirewallException.ProfileType.domain;
129 + firewallException.Add(new XAttribute("Profile", "domain"));
130 break;
131 case FirewallConstants.NET_FW_PROFILE2_PRIVATE:
132 - fire.Profile = Firewall.FirewallException.ProfileType.@private;
132 + firewallException.Add(new XAttribute("Profile", "private"));
133 break;
134 case FirewallConstants.NET_FW_PROFILE2_PUBLIC:
135 - fire.Profile = Firewall.FirewallException.ProfileType.@public;
135 + firewallException.Add(new XAttribute("Profile", "public"));
136 break;
137 case FirewallConstants.NET_FW_PROFILE2_ALL:
138 - fire.Profile = Firewall.FirewallException.ProfileType.all;
138 + firewallException.Add(new XAttribute("Profile", "all"));
139 break;
140 }
141 }
142
143 - // Description column is new in v3.6
144 - if (9 < row.Fields.Length && !row.IsColumnEmpty(9))
143 + if (!row.IsColumnEmpty(9))
144 {
146 - fire.Description = (string)row[9];
145 + firewallException.Add(new XAttribute("Description", row.FieldAsString(9)));
146 }
147
148 if (!row.IsColumnEmpty(10))
@@ -151,32 +150,56 @@ namespace WixToolset.Firewall
150 switch (Convert.ToInt32(row[10]))
151 {
152 case FirewallConstants.NET_FW_RULE_DIR_IN:
154 - fire.Direction = Firewall.FirewallException.DirectionType.@in;
153 +
154 + firewallException.Add(AttributeIfNotNull("Outbound", false));
155 break;
156 case FirewallConstants.NET_FW_RULE_DIR_OUT:
157 - fire.Direction = Firewall.FirewallException.DirectionType.@out;
157 + firewallException.Add(AttributeIfNotNull("Outbound", true));
158 break;
159 }
160 }
161
162 - Wix.Component component = (Wix.Component)this.Core.GetIndexedElement("Component", (string)row[8]);
163 - if (null != component)
164 - {
165 - component.AddChild(fire);
166 - }
167 - else
168 - {
169 - this.Core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", (string)row[6], "Component"));
170 - }
162 + this.DecompilerHelper.IndexElement(row, firewallException);
163 }
164 }
165
174 - private static void AddRemoteAddress(Firewall.FirewallException fire, string address)
166 + private static void AddRemoteAddress(XElement firewallException, string address)
167 + {
168 + var remoteAddress = new XElement(FirewallConstants.RemoteAddressName,
169 + new XAttribute("Value", address)
170 + );
171 +
172 + firewallException.AddAfterSelf(remoteAddress);
173 + }
174 +
175 + private static XAttribute AttributeIfNotNull(string name, bool value)
176 + {
177 + return new XAttribute(name, value ? "yes" : "no");
178 + }
179 +
180 + /// <summary>
181 + /// Finalize the FirewallException table.
182 + /// </summary>
183 + /// <param name="tables">Collection of all tables.</param>
184 + private void FinalizeFirewallExceptionTable(TableIndexedCollection tables)
185 {
176 - Firewall.RemoteAddress remote = new Firewall.RemoteAddress();
177 - remote.Content = address;
178 - fire.AddChild(remote);
186 + if (tables.TryGetTable("Wix4FirewallException", out var firewallExceptionTable))
187 + {
188 + foreach (var row in firewallExceptionTable.Rows)
189 + {
190 + var xmlConfig = this.DecompilerHelper.GetIndexedElement(row);
191 +
192 + var componentId = row.FieldAsString(8);
193 + if (this.DecompilerHelper.TryGetIndexedElement("Component", componentId, out var component))
194 + {
195 + component.Add(xmlConfig);
196 + }
197 + else
198 + {
199 + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, firewallExceptionTable.Name, row.GetPrimaryKey(), "Component_", componentId, "Component"));
200 + }
201 + }
202 + }
203 }
204 }
181 -#endif
205 }
src/ext/Firewall/wixext/FirewallExtensionFactory.cs
+2 -1
@@ -1,4 +1,4 @@
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.
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.Firewall
4 {
@@ -13,6 +13,7 @@ namespace WixToolset.Firewall
13 typeof(FirewallCompiler),
14 typeof(FirewallExtensionData),
15 typeof(FirewallWindowsInstallerBackendBinderExtension),
16 + typeof(FirewallDecompiler),
17 };
18 }
19 }