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
}