@joebigelow / wix / commits / d14c02d4

Fix when xmlns placed directly on PermissionEx

This is NOT recommended in v4 and its converter will move namespaces to the root element, but WiX v3 allowed it so we'll continued to do so in v4. Fixes 7010

Rob Mensching committed Nov 15, 2022 at 15:47 UTC d14c02d4ca6ee820b7111b789d9f904e0fd52804
4 files changed +237 -18
src/ext/Util/test/WixToolsetTest.Util/TestData/PermissionEx/PackageComponents.wxs
+7 -2
@@ -1,4 +1,4 @@
1 -<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
1 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
2 <Fragment>
3 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
4 <Component>
@@ -15,7 +15,12 @@
15 <util:PermissionEx User="Everyone" GenericAll="yes" />
16 </RegistryKey>
17 <RegistryValue Root="HKLM" Key="TestRegistryValueKey" Value="abc">
18 - <util:PermissionEx User="Everyone" GenericAll="yes" />
18 + <!--
19 + Example of placing the namespace on the extension element. This is NOT recommended
20 + in v4 and its converter will move namespaces to the root, but WiX v3 allowed it so
21 + we'll continued to do so for now.
22 + -->
23 + <PermissionEx User="Everyone" GenericAll="yes" xmlns="http://wixtoolset.org/schemas/v4/wxs/util" />
24 </RegistryValue>
25 </Component>
26 </ComponentGroup>
src/ext/Util/wixext/UtilCompiler.cs
+20 -16
@@ -1286,6 +1286,8 @@ namespace WixToolset.Util
1286 var bits = new BitArray(32);
1287 string user = null;
1288
1289 + var validBitNames = new HashSet<string>(UtilConstants.StandardPermissions.Concat(UtilConstants.GenericPermissions).Concat(UtilConstants.FolderPermissions));
1290 +
1291 foreach (var attrib in element.Attributes())
1292 {
1293 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
@@ -1297,18 +1299,18 @@ namespace WixToolset.Util
1299 this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, UtilSymbolDefinitions.User, user);
1300 break;
1301 default:
1300 - var attribValue = this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1301 - if (!this.TrySetBitFromName(UtilConstants.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16))
1302 + if (validBitNames.Contains(attrib.Name.LocalName))
1303 {
1303 - if (!this.TrySetBitFromName(UtilConstants.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28))
1304 + var attribValue = this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1305 + if (this.TrySetBitFromName(UtilConstants.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16) ||
1306 + this.TrySetBitFromName(UtilConstants.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28) ||
1307 + this.TrySetBitFromName(UtilConstants.FolderPermissions, attrib.Name.LocalName, attribValue, bits, 0))
1308 {
1305 - if (!this.TrySetBitFromName(UtilConstants.FolderPermissions, attrib.Name.LocalName, attribValue, bits, 0))
1306 - {
1307 - this.ParseHelper.UnexpectedAttribute(element, attrib);
1308 - break;
1309 - }
1309 + break;
1310 }
1311 }
1312 +
1313 + this.ParseHelper.UnexpectedAttribute(element, attrib);
1314 break;
1315 }
1316 }
@@ -2477,6 +2479,8 @@ namespace WixToolset.Util
2479 break;
2480 }
2481
2482 + var validBitNames = new HashSet<string>(UtilConstants.StandardPermissions.Concat(UtilConstants.GenericPermissions).Concat(specialPermissions));
2483 +
2484 foreach (var attrib in element.Attributes())
2485 {
2486 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
@@ -2500,18 +2504,18 @@ namespace WixToolset.Util
2504 user = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
2505 break;
2506 default:
2503 - var attribValue = this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2504 - if (!this.TrySetBitFromName(UtilConstants.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16))
2507 + if (validBitNames.Contains(attrib.Name.LocalName))
2508 {
2506 - if (!this.TrySetBitFromName(UtilConstants.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28))
2509 + var attribValue = this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2510 + if (this.TrySetBitFromName(UtilConstants.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16) ||
2511 + this.TrySetBitFromName(UtilConstants.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28) ||
2512 + this.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0))
2513 {
2508 - if (!this.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0))
2509 - {
2510 - this.ParseHelper.UnexpectedAttribute(element, attrib);
2511 - break;
2512 - }
2514 + break;
2515 }
2516 }
2517 +
2518 + this.ParseHelper.UnexpectedAttribute(element, attrib);
2519 break;
2520 }
2521 }
src/wix/WixToolset.Converters/WixConverter.cs
+128
@@ -402,6 +402,7 @@ namespace WixToolset.Converters
402 // Start converting the nodes at the top.
403 this.ConvertNodes(document.Nodes(), 0);
404 this.RemoveUnusedNamespaces(document.Root);
405 + this.MoveNamespacesToRoot(document.Root);
406 }
407
408 private void Format(XDocument document)
@@ -424,6 +425,7 @@ namespace WixToolset.Converters
425 // Start converting the nodes at the top.
426 this.ConvertNodes(document.Nodes(), 0);
427 this.RemoveUnusedNamespaces(document.Root);
428 + this.MoveNamespacesToRoot(document.Root);
429 }
430
431 private bool TryOpenSourceFile(string sourceFile, out XDocument document)
@@ -2253,6 +2255,50 @@ namespace WixToolset.Converters
2255 }
2256 }
2257
2258 + private void MoveNamespacesToRoot(XElement root)
2259 + {
2260 + var rootNamespaces = new HashSet<string>();
2261 + var rootNamespacePrefixes = new HashSet<string>();
2262 + var nonRootDeclarations = new List<XAttribute>();
2263 +
2264 + VisitElement(root, x =>
2265 + {
2266 + if (x is XAttribute a && a.IsNamespaceDeclaration)
2267 + {
2268 + if (x.Parent == root)
2269 + {
2270 + rootNamespaces.Add(a.Value);
2271 + rootNamespacePrefixes.Add(a.Name.LocalName);
2272 + }
2273 + else
2274 + {
2275 + nonRootDeclarations.Add(a);
2276 + }
2277 + }
2278 +
2279 + return true;
2280 + });
2281 +
2282 + foreach (var declaration in nonRootDeclarations)
2283 + {
2284 + if (this.OnInformation(ConverterTestType.MoveNamespacesToRoot, declaration, "Namespace should be defined on the root. The '{0}' namespace was move to the root element.", declaration.Value))
2285 + {
2286 + if (!rootNamespaces.Contains(declaration.Value))
2287 + {
2288 + var prefix = GetNamespacePrefix(declaration, rootNamespacePrefixes);
2289 +
2290 + var rootDeclaration = new XAttribute(XNamespace.Xmlns + prefix, declaration.Value);
2291 + root.Add(rootDeclaration);
2292 +
2293 + rootNamespaces.Add(rootDeclaration.Value);
2294 + rootNamespacePrefixes.Add(rootDeclaration.Name.LocalName);
2295 + }
2296 +
2297 + declaration.Remove();
2298 + }
2299 + }
2300 + }
2301 +
2302 private int ReportMessages(XDocument document, bool saved)
2303 {
2304 var conversionCount = this.ConversionMessages.Count;
@@ -2457,6 +2503,83 @@ namespace WixToolset.Converters
2503 return result;
2504 }
2505
2506 + private static string GetNamespacePrefix(XAttribute declaration, HashSet<string> usedPrefixes)
2507 + {
2508 + var baseNamespace = String.Empty;
2509 +
2510 + switch (declaration.Value)
2511 + {
2512 + case "http://wixtoolset.org/schemas/v4/wxs/bal":
2513 + baseNamespace = "bal";
2514 + break;
2515 +
2516 + case "http://wixtoolset.org/schemas/v4/wxs/complus":
2517 + baseNamespace = "complus";
2518 + break;
2519 +
2520 + case "http://wixtoolset.org/schemas/v4/wxs/dependency":
2521 + baseNamespace = "dependency";
2522 + break;
2523 +
2524 + case "http://wixtoolset.org/schemas/v4/wxs/difxapp":
2525 + baseNamespace = "difx";
2526 + break;
2527 +
2528 + case "http://wixtoolset.org/schemas/v4/wxs/directx":
2529 + baseNamespace = "directx";
2530 + break;
2531 +
2532 + case "http://wixtoolset.org/schemas/v4/wxs/firewall":
2533 + baseNamespace = "fw";
2534 + break;
2535 +
2536 + case "http://wixtoolset.org/schemas/v4/wxs/http":
2537 + baseNamespace = "http";
2538 + break;
2539 +
2540 + case "http://wixtoolset.org/schemas/v4/wxs/iis":
2541 + baseNamespace = "iis";
2542 + break;
2543 +
2544 + case "http://wixtoolset.org/schemas/v4/wxs/msmq":
2545 + baseNamespace = "msmq";
2546 + break;
2547 +
2548 + case "http://wixtoolset.org/schemas/v4/wxs/netfx":
2549 + baseNamespace = "netfx";
2550 + break;
2551 +
2552 + case "http://wixtoolset.org/schemas/v4/wxs/powershell":
2553 + baseNamespace = "ps";
2554 + break;
2555 +
2556 + case "http://wixtoolset.org/schemas/v4/wxs/sql":
2557 + baseNamespace = "sql";
2558 + break;
2559 +
2560 + case "http://wixtoolset.org/schemas/v4/wxs/ui":
2561 + baseNamespace = "ui";
2562 + break;
2563 +
2564 + case "http://wixtoolset.org/schemas/v4/wxs/util":
2565 + baseNamespace = "util";
2566 + break;
2567 +
2568 + case "http://wixtoolset.org/schemas/v4/wxs/vs":
2569 + baseNamespace = "vs";
2570 + break;
2571 + }
2572 +
2573 + var ns = baseNamespace;
2574 +
2575 + for (var i = 1; usedPrefixes.Contains(ns); ++i)
2576 + {
2577 + ns = baseNamespace + i;
2578 + }
2579 +
2580 + return ns;
2581 + }
2582 +
2583 private static string LowercaseFirstChar(string value)
2584 {
2585 if (!String.IsNullOrEmpty(value))
@@ -2928,6 +3051,11 @@ namespace WixToolset.Converters
3051 /// The MsuPackage element contains obsolete '{0}' attribute. MSU packages are now always permanent because Windows no longer supports silently removing MSUs. The attribute will be removed.
3052 /// </summary>
3053 MsuPackagePermanentObsolete,
3054 +
3055 + /// <summary>
3056 + /// Namespace should be defined on the root. The '{0}' namespace was move to the root element.
3057 + /// </summary>
3058 + MoveNamespacesToRoot,
3059 }
3060 }
3061 }
src/wix/test/WixToolsetTest.Converters/FirewallExtensionFixture.cs
+82
@@ -3,6 +3,7 @@
3 namespace WixToolsetTest.Converters
4 {
5 using System;
6 + using System.Linq;
7 using System.Xml.Linq;
8 using WixInternal.TestSupport;
9 using WixToolset.Converters;
@@ -43,5 +44,86 @@ namespace WixToolsetTest.Converters
44 var actualLines = UnformattedDocumentLines(document);
45 WixAssert.CompareLineByLine(expected, actualLines);
46 }
47 +
48 + [Fact]
49 + public void FixNamespacePlacement()
50 + {
51 + var parse = String.Join(Environment.NewLine,
52 + "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
53 + " <Fragment>",
54 + " <RemoteAddress xmlns='http://schemas.microsoft.com/wix/FirewallExtension'>",
55 + " 127.0.0.1",
56 + " </RemoteAddress>",
57 + " </Fragment>",
58 + "</Wix>");
59 +
60 + var expected = new[]
61 + {
62 + "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:fw=\"http://wixtoolset.org/schemas/v4/wxs/firewall\">",
63 + " <Fragment>",
64 + " <fw:RemoteAddress Value=\"127.0.0.1\" />",
65 + " </Fragment>",
66 + "</Wix>"
67 + };
68 +
69 + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
70 +
71 + var messaging = new MockMessaging();
72 + var converter = new WixConverter(messaging, 2, null, null);
73 +
74 + var errors = converter.ConvertDocument(document);
75 + WixAssert.CompareLineByLine(new[]
76 + {
77 + "[Converted] The namespace 'http://schemas.microsoft.com/wix/2006/wi' is out of date. It must be 'http://wixtoolset.org/schemas/v4/wxs'. (XmlnsValueWrong)",
78 + "[Converted] The namespace 'http://schemas.microsoft.com/wix/FirewallExtension' is out of date. It must be 'http://wixtoolset.org/schemas/v4/wxs/firewall'. (XmlnsValueWrong)",
79 + "[Converted] Using RemoteAddress element text is deprecated. Use the 'Value' attribute instead. (InnerTextDeprecated)",
80 + "[Converted] Namespace should be defined on the root. The 'http://wixtoolset.org/schemas/v4/wxs/firewall' namespace was move to the root element. (MoveNamespacesToRoot)"
81 + }, messaging.Messages.Select(m => m.ToString()).ToArray());
82 + Assert.Equal(4, errors);
83 +
84 + var actualLines = UnformattedDocumentLines(document);
85 + WixAssert.CompareLineByLine(expected, actualLines);
86 + }
87 +
88 + [Fact]
89 + public void FixNamespacePlacementWhenItExists()
90 + {
91 + //xmlns:abc='http://schemas.microsoft.com/wix/FirewallExtension'
92 + var parse = String.Join(Environment.NewLine,
93 + "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>",
94 + " <Fragment>",
95 + " <RemoteAddress xmlns='http://schemas.microsoft.com/wix/FirewallExtension'>",
96 + " 127.0.0.1",
97 + " </RemoteAddress>",
98 + " </Fragment>",
99 + "</Wix>");
100 +
101 + var expected = new[]
102 + {
103 + "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:fw=\"http://wixtoolset.org/schemas/v4/wxs/firewall\">",
104 + " <Fragment>",
105 + " <fw:RemoteAddress Value=\"127.0.0.1\" />",
106 + " </Fragment>",
107 + "</Wix>"
108 + };
109 +
110 + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
111 +
112 + var messaging = new MockMessaging();
113 + var converter = new WixConverter(messaging, 2, null, null);
114 +
115 + var errors = converter.ConvertDocument(document);
116 + WixAssert.CompareLineByLine(new[]
117 + {
118 + "[Converted] The namespace 'http://schemas.microsoft.com/wix/2006/wi' is out of date. It must be 'http://wixtoolset.org/schemas/v4/wxs'. (XmlnsValueWrong)",
119 + "[Converted] The namespace 'http://schemas.microsoft.com/wix/FirewallExtension' is out of date. It must be 'http://wixtoolset.org/schemas/v4/wxs/firewall'. (XmlnsValueWrong)",
120 + "[Converted] Using RemoteAddress element text is deprecated. Use the 'Value' attribute instead. (InnerTextDeprecated)",
121 + "[Converted] Namespace should be defined on the root. The 'http://wixtoolset.org/schemas/v4/wxs/firewall' namespace was move to the root element. (MoveNamespacesToRoot)"
122 + }, messaging.Messages.Select(m => m.ToString()).ToArray());
123 + Assert.Equal(4, errors);
124 +
125 + var actualLines = UnformattedDocumentLines(document);
126 + WixAssert.CompareLineByLine(expected, actualLines);
127 + }
128 }
129 }