aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSean Hall <r.sean.hall@gmail.com>2022-03-15 18:09:12 -0500
committerSean Hall <r.sean.hall@gmail.com>2022-03-16 20:14:37 -0500
commitdb22d99a4d603caab18fd42cb40881431c353912 (patch)
tree93085f239b881010760392d62a695f7717cebb1a /src
parent8c9ca787bee29f969cd7ca9aeaa46626d557d196 (diff)
downloadwix-db22d99a4d603caab18fd42cb40881431c353912.tar.gz
wix-db22d99a4d603caab18fd42cb40881431c353912.tar.bz2
wix-db22d99a4d603caab18fd42cb40881431c353912.zip
Enhance bundle backend validation.
Diffstat (limited to 'src')
-rw-r--r--src/api/wix/WixToolset.Data/ErrorMessages.cs6
-rw-r--r--src/api/wix/WixToolset.Data/WarningMessages.cs6
-rw-r--r--src/api/wix/WixToolset.Extensibility/Data/BundleConditionPhase.cs35
-rw-r--r--src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs11
-rw-r--r--src/api/wix/WixToolset.Extensibility/Services/IBundleValidator.cs56
-rw-r--r--src/api/wix/WixToolset.Extensibility/Services/IBurnBackendHelper.cs2
-rw-r--r--src/api/wix/WixToolset.Extensibility/Services/IParseHelper.cs13
-rw-r--r--src/burn/test/BurnUnitTest/VariableTest.cpp1
-rw-r--r--src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs22
-rw-r--r--src/ext/Bal/test/WixToolsetTest.Bal/TestData/Overridable/WrongCaseBundle.wxl4
-rw-r--r--src/ext/Bal/test/WixToolsetTest.Bal/TestData/Overridable/WrongCaseBundle.wxs2
-rw-r--r--src/ext/Bal/wixext/BalBurnBackendExtension.cs23
-rw-r--r--src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs15
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/PerformBundleBackendValidationCommand.cs156
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs4
-rw-r--r--src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs121
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs97
-rw-r--r--src/wix/WixToolset.Core/Common.cs21
-rw-r--r--src/wix/WixToolset.Core/CompilerCore.cs173
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs5
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/BundleValidator.cs326
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs35
-rw-r--r--src/wix/WixToolset.Core/WixToolsetServiceProvider.cs1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs43
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithInvalid/BundleWithInvalidLocValues.wxl8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithInvalid/BundleWithInvalidLocValues.wxs18
26 files changed, 926 insertions, 278 deletions
diff --git a/src/api/wix/WixToolset.Data/ErrorMessages.cs b/src/api/wix/WixToolset.Data/ErrorMessages.cs
index 80738f5e..a833452d 100644
--- a/src/api/wix/WixToolset.Data/ErrorMessages.cs
+++ b/src/api/wix/WixToolset.Data/ErrorMessages.cs
@@ -1184,6 +1184,11 @@ namespace WixToolset.Data
1184 return Message(null, Ids.InvalidCommandLineFileName, "Invalid file name specified on the command line: '{0}'. Error message: '{1}'", fileName, error); 1184 return Message(null, Ids.InvalidCommandLineFileName, "Invalid file name specified on the command line: '{0}'. Error message: '{1}'", fileName, error);
1185 } 1185 }
1186 1186
1187 public static Message InvalidBundleCondition(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string condition)
1188 {
1189 return Message(sourceLineNumbers, Ids.InvalidBundleCondition, "The {0}/@{1} attribute's value '{2}' is not a valid bundle condition.", elementName, attributeName, condition);
1190 }
1191
1187 public static Message InvalidDateTimeFormat(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string value) 1192 public static Message InvalidDateTimeFormat(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string value)
1188 { 1193 {
1189 return Message(sourceLineNumbers, Ids.InvalidDateTimeFormat, "The {0}/@{1} attribute's value '{2}' is not a valid date/time value. A date/time value should follow the format YYYY-MM-DDTHH:mm:ss.", elementName, attributeName, value); 1194 return Message(sourceLineNumbers, Ids.InvalidDateTimeFormat, "The {0}/@{1} attribute's value '{2}' is not a valid date/time value. A date/time value should follow the format YYYY-MM-DDTHH:mm:ss.", elementName, attributeName, value);
@@ -2686,6 +2691,7 @@ namespace WixToolset.Data
2686 MultiplePackagePayloads3 = 406, 2691 MultiplePackagePayloads3 = 406,
2687 MissingPackagePayload = 407, 2692 MissingPackagePayload = 407,
2688 ExpectedAttributeWithoutOtherAttributes = 408, 2693 ExpectedAttributeWithoutOtherAttributes = 408,
2694 InvalidBundleCondition = 409,
2689 } 2695 }
2690 } 2696 }
2691} 2697}
diff --git a/src/api/wix/WixToolset.Data/WarningMessages.cs b/src/api/wix/WixToolset.Data/WarningMessages.cs
index b749bcf4..f555fd93 100644
--- a/src/api/wix/WixToolset.Data/WarningMessages.cs
+++ b/src/api/wix/WixToolset.Data/WarningMessages.cs
@@ -593,6 +593,11 @@ namespace WixToolset.Data
593 return Message(null, Ids.UnableToResetAcls, "Unable to reset acls on destination files. Exception detail: {0}", error); 593 return Message(null, Ids.UnableToResetAcls, "Unable to reset acls on destination files. Exception detail: {0}", error);
594 } 594 }
595 595
596 public static Message UnavailableBundleConditionVariable(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string variable, string illegalValueList)
597 {
598 return Message(sourceLineNumbers, Ids.UnavailableBundleConditionVariable, "{0}/@{1} contains the built-in Variable '{2}', which is not available when it is evaluated. (Unavailable Variables are: {3}.). Rewrite the condition to avoid Variables that are never valid during its evaluation.", elementName, attributeName, variable, illegalValueList);
599 }
600
596 public static Message UnclearShortcut(SourceLineNumber sourceLineNumbers, string shortcutId, string fileId, string componentId) 601 public static Message UnclearShortcut(SourceLineNumber sourceLineNumbers, string shortcutId, string fileId, string componentId)
597 { 602 {
598 return Message(sourceLineNumbers, Ids.UnclearShortcut, "Because it is an advertised shortcut, the target of shortcut '{0}' will be the keypath of component '{2}' rather than parent file '{1}'. To eliminate this warning, you can (1) make the Shortcut element a child of the File element that is the keypath of component '{2}', (2) make file '{1}' the keypath of component '{2}', or (3) remove the @Advertise attribute so the shortcut is a non-advertised shortcut.", shortcutId, fileId, componentId); 603 return Message(sourceLineNumbers, Ids.UnclearShortcut, "Because it is an advertised shortcut, the target of shortcut '{0}' will be the keypath of component '{2}' rather than parent file '{1}'. To eliminate this warning, you can (1) make the Shortcut element a child of the File element that is the keypath of component '{2}', (2) make file '{1}' the keypath of component '{2}', or (3) remove the @Advertise attribute so the shortcut is a non-advertised shortcut.", shortcutId, fileId, componentId);
@@ -804,6 +809,7 @@ namespace WixToolset.Data
804 DetectConditionRecommended = 1153, 809 DetectConditionRecommended = 1153,
805 CollidingModularizationTypes = 1156, 810 CollidingModularizationTypes = 1156,
806 InvalidEnvironmentVariable = 1157, 811 InvalidEnvironmentVariable = 1157,
812 UnavailableBundleConditionVariable = 1159,
807 } 813 }
808 } 814 }
809} 815}
diff --git a/src/api/wix/WixToolset.Extensibility/Data/BundleConditionPhase.cs b/src/api/wix/WixToolset.Extensibility/Data/BundleConditionPhase.cs
new file mode 100644
index 00000000..6d876bcc
--- /dev/null
+++ b/src/api/wix/WixToolset.Extensibility/Data/BundleConditionPhase.cs
@@ -0,0 +1,35 @@
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.Extensibility.Data
4{
5 /// <summary>
6 /// The Burn execution phase during which a Condition will be evaluated.
7 /// </summary>
8 public enum BundleConditionPhase
9 {
10 /// <summary>
11 /// Condition is evaluated by the engine before loading the BootstrapperApplication (Bundle/@Condition).
12 /// </summary>
13 Startup,
14
15 /// <summary>
16 /// Condition is evaluated during Detect (ExePackage/@DetectCondition).
17 /// </summary>
18 Detect,
19
20 /// <summary>
21 /// Condition is evaluated during Plan (ExePackage/@InstallCondition).
22 /// </summary>
23 Plan,
24
25 /// <summary>
26 /// Condition is evaluated during Apply (MsiProperty/@Condition).
27 /// </summary>
28 Execute,
29
30 /// <summary>
31 /// Condition is evaluated after Apply.
32 /// </summary>
33 Shutdown,
34 }
35}
diff --git a/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs b/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs
index 29b8f8e6..23ad44f5 100644
--- a/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs
+++ b/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs
@@ -74,17 +74,6 @@ namespace WixToolset.Extensibility.Services
74 string GenerateIdentifier(string prefix, params string[] args); 74 string GenerateIdentifier(string prefix, params string[] args);
75 75
76 /// <summary> 76 /// <summary>
77 /// Validates path is relative and canonicalizes it.
78 /// For example, "a\..\c\.\d.exe" => "c\d.exe".
79 /// </summary>
80 /// <param name="sourceLineNumbers"></param>
81 /// <param name="elementName"></param>
82 /// <param name="attributeName"></param>
83 /// <param name="relativePath"></param>
84 /// <returns>The original value if not relative, otherwise the canonicalized relative path.</returns>
85 string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath);
86
87 /// <summary>
88 /// Gets a valid code page from the given web name or integer value. 77 /// Gets a valid code page from the given web name or integer value.
89 /// </summary> 78 /// </summary>
90 /// <param name="value">A code page web name or integer value as a string.</param> 79 /// <param name="value">A code page web name or integer value as a string.</param>
diff --git a/src/api/wix/WixToolset.Extensibility/Services/IBundleValidator.cs b/src/api/wix/WixToolset.Extensibility/Services/IBundleValidator.cs
new file mode 100644
index 00000000..fc88a443
--- /dev/null
+++ b/src/api/wix/WixToolset.Extensibility/Services/IBundleValidator.cs
@@ -0,0 +1,56 @@
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.Extensibility.Services
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility.Data;
7
8 /// <summary>
9 /// Interface provided to help with bundle validation.
10 /// </summary>
11 public interface IBundleValidator
12 {
13
14 /// <summary>
15 /// Validates path is relative and canonicalizes it.
16 /// For example, "a\..\c\.\d.exe" => "c\d.exe".
17 /// </summary>
18 /// <param name="sourceLineNumbers"></param>
19 /// <param name="elementName"></param>
20 /// <param name="attributeName"></param>
21 /// <param name="relativePath"></param>
22 /// <returns>The original value if not relative, otherwise the canonicalized relative path.</returns>
23 string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath);
24
25 /// <summary>
26 /// Validates an MsiProperty name value and displays an error for an illegal value.
27 /// </summary>
28 /// <param name="sourceLineNumbers"></param>
29 /// <param name="elementName"></param>
30 /// <param name="attributeName"></param>
31 /// <param name="propertyName"></param>
32 /// <returns>Whether the name is valid.</returns>
33 bool ValidateBundleMsiPropertyName(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string propertyName);
34
35 /// <summary>
36 /// Validates a Bundle variable name and displays an error for an illegal value.
37 /// </summary>
38 /// <param name="sourceLineNumbers"></param>
39 /// <param name="elementName"></param>
40 /// <param name="attributeName"></param>
41 /// <param name="variableName"></param>
42 /// <returns>Whether the name is valid.</returns>
43 bool ValidateBundleVariableName(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string variableName);
44
45 /// <summary>
46 /// Validates a bundle condition and displays an error for an illegal value.
47 /// </summary>
48 /// <param name="sourceLineNumbers"></param>
49 /// <param name="elementName"></param>
50 /// <param name="attributeName"></param>
51 /// <param name="condition"></param>
52 /// <param name="phase"></param>
53 /// <returns>Whether the condition is valid.</returns>
54 bool ValidateBundleCondition(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string condition, BundleConditionPhase phase);
55 }
56}
diff --git a/src/api/wix/WixToolset.Extensibility/Services/IBurnBackendHelper.cs b/src/api/wix/WixToolset.Extensibility/Services/IBurnBackendHelper.cs
index ef5fcc65..1b6a2828 100644
--- a/src/api/wix/WixToolset.Extensibility/Services/IBurnBackendHelper.cs
+++ b/src/api/wix/WixToolset.Extensibility/Services/IBurnBackendHelper.cs
@@ -7,7 +7,7 @@ namespace WixToolset.Extensibility.Services
7 /// <summary> 7 /// <summary>
8 /// Interface provided to help Burn backend extensions. 8 /// Interface provided to help Burn backend extensions.
9 /// </summary> 9 /// </summary>
10 public interface IBurnBackendHelper : IBackendHelper 10 public interface IBurnBackendHelper : IBackendHelper, IBundleValidator
11 { 11 {
12 /// <summary> 12 /// <summary>
13 /// Adds the given XML to the BootstrapperApplicationData manifest. 13 /// Adds the given XML to the BootstrapperApplicationData manifest.
diff --git a/src/api/wix/WixToolset.Extensibility/Services/IParseHelper.cs b/src/api/wix/WixToolset.Extensibility/Services/IParseHelper.cs
index fbe5aae4..de2c6f9f 100644
--- a/src/api/wix/WixToolset.Extensibility/Services/IParseHelper.cs
+++ b/src/api/wix/WixToolset.Extensibility/Services/IParseHelper.cs
@@ -13,7 +13,7 @@ namespace WixToolset.Extensibility.Services
13 /// <summary> 13 /// <summary>
14 /// Interface provided to help compiler extensions parse. 14 /// Interface provided to help compiler extensions parse.
15 /// </summary> 15 /// </summary>
16 public interface IParseHelper 16 public interface IParseHelper : IBundleValidator
17 { 17 {
18 /// <summary> 18 /// <summary>
19 /// Creates a version 3 name-based UUID. 19 /// Creates a version 3 name-based UUID.
@@ -323,17 +323,6 @@ namespace WixToolset.Extensibility.Services
323 YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute); 323 YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute);
324 324
325 /// <summary> 325 /// <summary>
326 /// Validates path is relative and canonicalizes it.
327 /// For example, "a\..\c\.\d.exe" => "c\d.exe".
328 /// </summary>
329 /// <param name="sourceLineNumbers"></param>
330 /// <param name="elementName"></param>
331 /// <param name="attributeName"></param>
332 /// <param name="relativePath"></param>
333 /// <returns>The original value if not relative, otherwise the canonicalized relative path.</returns>
334 string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath);
335
336 /// <summary>
337 /// Gets a source line number for an element. 326 /// Gets a source line number for an element.
338 /// </summary> 327 /// </summary>
339 /// <param name="element">Element to get source line number.</param> 328 /// <param name="element">Element to get source line number.</param>
diff --git a/src/burn/test/BurnUnitTest/VariableTest.cpp b/src/burn/test/BurnUnitTest/VariableTest.cpp
index 53105e69..d0361c0e 100644
--- a/src/burn/test/BurnUnitTest/VariableTest.cpp
+++ b/src/burn/test/BurnUnitTest/VariableTest.cpp
@@ -381,6 +381,7 @@ namespace Bootstrapper
381 Assert::True(EvaluateConditionHelper(&variables, L"(PROP3 = \"NOT\" OR PROP1 = \"VAL1\") AND PROP2 = \"VAL2\"")); 381 Assert::True(EvaluateConditionHelper(&variables, L"(PROP3 = \"NOT\" OR PROP1 = \"VAL1\") AND PROP2 = \"VAL2\""));
382 Assert::True(EvaluateConditionHelper(&variables, L"PROP3 = \"NOT\" OR (PROP1 = \"VAL1\" AND PROP2 = \"VAL2\")")); 382 Assert::True(EvaluateConditionHelper(&variables, L"PROP3 = \"NOT\" OR (PROP1 = \"VAL1\" AND PROP2 = \"VAL2\")"));
383 383
384 Assert::True(EvaluateFailureConditionHelper(&variables, L""));
384 Assert::True(EvaluateFailureConditionHelper(&variables, L"=")); 385 Assert::True(EvaluateFailureConditionHelper(&variables, L"="));
385 Assert::True(EvaluateFailureConditionHelper(&variables, L"(PROP1")); 386 Assert::True(EvaluateFailureConditionHelper(&variables, L"(PROP1"));
386 Assert::True(EvaluateFailureConditionHelper(&variables, L"(PROP1 = \"")); 387 Assert::True(EvaluateFailureConditionHelper(&variables, L"(PROP1 = \""));
diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs b/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs
index ef4ee49a..9aea8c1d 100644
--- a/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs
+++ b/src/ext/Bal/test/WixToolsetTest.Bal/BalExtensionFixture.cs
@@ -2,6 +2,7 @@
2 2
3namespace WixToolsetTest.Bal 3namespace WixToolsetTest.Bal
4{ 4{
5 using System;
5 using System.IO; 6 using System.IO;
6 using System.Linq; 7 using System.Linq;
7 using System.Xml; 8 using System.Xml;
@@ -138,20 +139,33 @@ namespace WixToolsetTest.Bal
138 { 139 {
139 var baseFolder = fs.GetFolder(); 140 var baseFolder = fs.GetFolder();
140 var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); 141 var bundleFile = Path.Combine(baseFolder, "bin", "test.exe");
141 var bundleSourceFolder = TestData.Get(@"TestData\Overridable"); 142 var bundleSourceFolder = TestData.Get(@"TestData");
142 var intermediateFolder = Path.Combine(baseFolder, "obj"); 143 var intermediateFolder = Path.Combine(baseFolder, "obj");
143 var baFolderPath = Path.Combine(baseFolder, "ba"); 144 var baFolderPath = Path.Combine(baseFolder, "ba");
144 var extractFolderPath = Path.Combine(baseFolder, "extract"); 145 var extractFolderPath = Path.Combine(baseFolder, "extract");
145 146
146 var compileResult = WixRunner.Execute(new[] 147 var result = WixRunner.Execute(new[]
147 { 148 {
148 "build", 149 "build",
149 Path.Combine(bundleSourceFolder, "WrongCaseBundle.wxs"), 150 Path.Combine(bundleSourceFolder, "Overridable", "WrongCaseBundle.wxs"),
151 "-loc", Path.Combine(bundleSourceFolder, "Overridable", "WrongCaseBundle.wxl"),
152 "-bindpath", Path.Combine(bundleSourceFolder, "WixStdBa", "Data"),
150 "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"), 153 "-ext", TestData.Get(@"WixToolset.Bal.wixext.dll"),
151 "-intermediateFolder", intermediateFolder, 154 "-intermediateFolder", intermediateFolder,
152 "-o", bundleFile, 155 "-o", bundleFile,
153 }); 156 });
154 Assert.Equal((int)BalErrors.Ids.NonUpperCaseOverridableVariable, compileResult.ExitCode); 157
158 Assert.InRange(result.ExitCode, 2, Int32.MaxValue);
159
160 var messages = result.Messages.Select(m => m.ToString()).ToList();
161 messages.Sort();
162
163 WixAssert.CompareLineByLine(new[]
164 {
165 "bal:Condition/@Condition contains the built-in Variable 'WixBundleAction', which is not available when it is evaluated. (Unavailable Variables are: 'WixBundleAction'.). Rewrite the condition to avoid Variables that are never valid during its evaluation.",
166 "Overridable variable 'Test1' must be 'TEST1' with Bundle/@CommandLineVariables value 'upperCase'.",
167 "The *Package/@bal:DisplayInternalUICondition attribute's value '=' is not a valid bundle condition.",
168 }, messages.ToArray());
155 } 169 }
156 } 170 }
157 } 171 }
diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/Overridable/WrongCaseBundle.wxl b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/Overridable/WrongCaseBundle.wxl
new file mode 100644
index 00000000..223a7874
--- /dev/null
+++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/Overridable/WrongCaseBundle.wxl
@@ -0,0 +1,4 @@
1<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
2 <String Id="NonsenseDetectCondition">WixBundleAction = 4</String>
3 <String Id="NonsensePlanCondition">=</String>
4</WixLocalization>
diff --git a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/Overridable/WrongCaseBundle.wxs b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/Overridable/WrongCaseBundle.wxs
index 91380c69..547af644 100644
--- a/src/ext/Bal/test/WixToolsetTest.Bal/TestData/Overridable/WrongCaseBundle.wxs
+++ b/src/ext/Bal/test/WixToolsetTest.Bal/TestData/Overridable/WrongCaseBundle.wxs
@@ -8,6 +8,8 @@
8 <Variable Name="Test1" bal:Overridable="yes" /> 8 <Variable Name="Test1" bal:Overridable="yes" />
9 <Chain> 9 <Chain>
10 <ExePackage Permanent="yes" DetectCondition="none" SourceFile="runtimes\win-x86\native\wixnative.exe" /> 10 <ExePackage Permanent="yes" DetectCondition="none" SourceFile="runtimes\win-x86\native\wixnative.exe" />
11 <MsiPackage SourceFile="test.msi" bal:DisplayInternalUICondition="!(loc.NonsensePlanCondition)" />
11 </Chain> 12 </Chain>
13 <bal:Condition Condition="!(loc.NonsenseDetectCondition)" Message="Unsupported" />
12 </Bundle> 14 </Bundle>
13</Wix> 15</Wix>
diff --git a/src/ext/Bal/wixext/BalBurnBackendExtension.cs b/src/ext/Bal/wixext/BalBurnBackendExtension.cs
index 854b8b35..3b19ae78 100644
--- a/src/ext/Bal/wixext/BalBurnBackendExtension.cs
+++ b/src/ext/Bal/wixext/BalBurnBackendExtension.cs
@@ -10,6 +10,7 @@ namespace WixToolset.Bal
10 using WixToolset.Data.Burn; 10 using WixToolset.Data.Burn;
11 using WixToolset.Data.Symbols; 11 using WixToolset.Data.Symbols;
12 using WixToolset.Extensibility; 12 using WixToolset.Extensibility;
13 using WixToolset.Extensibility.Data;
13 14
14 public class BalBurnBackendExtension : BaseBurnBackendBinderExtension 15 public class BalBurnBackendExtension : BaseBurnBackendBinderExtension
15 { 16 {
@@ -31,6 +32,8 @@ namespace WixToolset.Bal
31 { 32 {
32 base.SymbolsFinalized(section); 33 base.SymbolsFinalized(section);
33 34
35 this.VerifyBalConditions(section);
36 this.VerifyBalPackageInfos(section);
34 this.VerifyOverridableVariables(section); 37 this.VerifyOverridableVariables(section);
35 38
36 var baSymbol = section.Symbols.OfType<WixBootstrapperApplicationDllSymbol>().SingleOrDefault(); 39 var baSymbol = section.Symbols.OfType<WixBootstrapperApplicationDllSymbol>().SingleOrDefault();
@@ -100,7 +103,7 @@ namespace WixToolset.Bal
100 { 103 {
101 foreach (var payloadPropertiesSymbol in payloadPropertiesSymbols) 104 foreach (var payloadPropertiesSymbol in payloadPropertiesSymbols)
102 { 105 {
103 if (string.Equals(payloadPropertiesSymbol.Name, "bafunctions.dll", StringComparison.OrdinalIgnoreCase) && 106 if (String.Equals(payloadPropertiesSymbol.Name, "bafunctions.dll", StringComparison.OrdinalIgnoreCase) &&
104 BurnConstants.BurnUXContainerName == payloadPropertiesSymbol.ContainerRef) 107 BurnConstants.BurnUXContainerName == payloadPropertiesSymbol.ContainerRef)
105 { 108 {
106 this.Messaging.Write(BalWarnings.UnmarkedBAFunctionsDLL(payloadPropertiesSymbol.SourceLineNumbers)); 109 this.Messaging.Write(BalWarnings.UnmarkedBAFunctionsDLL(payloadPropertiesSymbol.SourceLineNumbers));
@@ -120,6 +123,24 @@ namespace WixToolset.Bal
120 } 123 }
121 } 124 }
122 125
126 private void VerifyBalConditions(IntermediateSection section)
127 {
128 var balConditionSymbols = section.Symbols.OfType<WixBalConditionSymbol>().ToList();
129 foreach (var balConditionSymbol in balConditionSymbols)
130 {
131 this.BackendHelper.ValidateBundleCondition(balConditionSymbol.SourceLineNumbers, "bal:Condition", "Condition", balConditionSymbol.Condition, BundleConditionPhase.Detect);
132 }
133 }
134
135 private void VerifyBalPackageInfos(IntermediateSection section)
136 {
137 var balPackageInfoSymbols = section.Symbols.OfType<WixBalPackageInfoSymbol>().ToList();
138 foreach (var balPackageInfoSymbol in balPackageInfoSymbols)
139 {
140 this.BackendHelper.ValidateBundleCondition(balPackageInfoSymbol.SourceLineNumbers, "*Package", "bal:DisplayInternalUICondition", balPackageInfoSymbol.DisplayInternalUICondition, BundleConditionPhase.Plan);
141 }
142 }
143
123 private void VerifyOverridableVariables(IntermediateSection section) 144 private void VerifyOverridableVariables(IntermediateSection section)
124 { 145 {
125 var bundleSymbol = section.Symbols.OfType<WixBundleSymbol>().Single(); 146 var bundleSymbol = section.Symbols.OfType<WixBundleSymbol>().Single();
diff --git a/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
index 16e63492..a73992da 100644
--- a/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
+++ b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
@@ -150,7 +150,7 @@ namespace WixToolset.Core.Burn
150 // Process the explicitly authored payloads. 150 // Process the explicitly authored payloads.
151 ISet<string> processedPayloads; 151 ISet<string> processedPayloads;
152 { 152 {
153 var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory); 153 var command = new ProcessPayloadsCommand(this.InternalBurnBackendHelper, this.PayloadHarvester, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory);
154 command.Execute(); 154 command.Execute();
155 155
156 fileTransfers.AddRange(command.FileTransfers); 156 fileTransfers.AddRange(command.FileTransfers);
@@ -228,7 +228,7 @@ namespace WixToolset.Core.Burn
228 { 228 {
229 var toProcess = payloadSymbols.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList(); 229 var toProcess = payloadSymbols.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList();
230 230
231 var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory); 231 var command = new ProcessPayloadsCommand(this.InternalBurnBackendHelper, this.PayloadHarvester, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory);
232 command.Execute(); 232 command.Execute();
233 233
234 fileTransfers.AddRange(command.FileTransfers); 234 fileTransfers.AddRange(command.FileTransfers);
@@ -381,6 +381,17 @@ namespace WixToolset.Core.Burn
381 return; 381 return;
382 } 382 }
383 383
384 // Now that extensions can't change anything else, verify everything is still valid.
385 {
386 var command = new PerformBundleBackendValidationCommand(this.Messaging, this.InternalBurnBackendHelper, section, facades);
387 command.Execute();
388 }
389
390 if (this.Messaging.EncounteredError)
391 {
392 return;
393 }
394
384 // Generate data for all manifests. 395 // Generate data for all manifests.
385 { 396 {
386 var command = new GenerateManifestDataFromIRCommand(this.Messaging, section, this.BackendExtensions, this.InternalBurnBackendHelper, extensionSearchSymbolsById); 397 var command = new GenerateManifestDataFromIRCommand(this.Messaging, section, this.BackendExtensions, this.InternalBurnBackendHelper, extensionSearchSymbolsById);
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/PerformBundleBackendValidationCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/PerformBundleBackendValidationCommand.cs
new file mode 100644
index 00000000..ee18ff2c
--- /dev/null
+++ b/src/wix/WixToolset.Core.Burn/Bundles/PerformBundleBackendValidationCommand.cs
@@ -0,0 +1,156 @@
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.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9 using WixToolset.Extensibility.Data;
10 using WixToolset.Extensibility.Services;
11
12 internal class PerformBundleBackendValidationCommand
13 {
14 public PerformBundleBackendValidationCommand(IMessaging messaging, IBurnBackendHelper burnBackendHelper, IntermediateSection section, IDictionary<string, PackageFacade> packageFacadesById)
15 {
16 this.Messaging = messaging;
17 this.BackendHelper = burnBackendHelper;
18 this.Section = section;
19 this.PackageFacadesById = packageFacadesById;
20 }
21
22 public IMessaging Messaging { get; }
23
24 public IBurnBackendHelper BackendHelper { get; }
25
26 public IntermediateSection Section { get; }
27
28 public IDictionary<string, PackageFacade> PackageFacadesById { get; }
29
30 public void Execute()
31 {
32 foreach (var symbol in this.Section.Symbols)
33 {
34 if (symbol is WixBundleSymbol wixBundleSymbol)
35 {
36 this.ValidateBundle(wixBundleSymbol);
37 }
38 else if (symbol is WixBundleMsiPropertySymbol wixBundleMsiPropertySymbol)
39 {
40 this.ValidateMsiProperty(wixBundleMsiPropertySymbol);
41 }
42 else if (symbol is WixBundleVariableSymbol wixBundleVariableSymbol)
43 {
44 this.ValidateVariable(wixBundleVariableSymbol);
45 }
46 else if (symbol is WixBundlePackageCommandLineSymbol wixBundlePackageCommandLineSymbol)
47 {
48 this.ValidatePackageCommandLine(wixBundlePackageCommandLineSymbol);
49 }
50 else if (symbol is WixSearchSymbol wixSearchSymbol)
51 {
52 this.ValidateSearch(wixSearchSymbol);
53 }
54 }
55
56 foreach (var packageFacade in this.PackageFacadesById.Values)
57 {
58 if (packageFacade.SpecificPackageSymbol is WixBundleExePackageSymbol wixBundleExePackageSymbol)
59 {
60 this.ValidateExePackage(wixBundleExePackageSymbol, packageFacade.PackageSymbol);
61 }
62 else if (packageFacade.SpecificPackageSymbol is WixBundleMsiPackageSymbol wixBundleMsiPackageSymbol)
63 {
64 this.ValidateMsiPackage(wixBundleMsiPackageSymbol, packageFacade.PackageSymbol);
65 }
66 else if (packageFacade.SpecificPackageSymbol is WixBundleMspPackageSymbol wixBundleMspPackageSymbol)
67 {
68 this.ValidateMspPackage(wixBundleMspPackageSymbol, packageFacade.PackageSymbol);
69 }
70 else if (packageFacade.SpecificPackageSymbol is WixBundleMsuPackageSymbol wixBundleMsuPackageSymbol)
71 {
72 this.ValidateMsuPackage(wixBundleMsuPackageSymbol, packageFacade.PackageSymbol);
73 }
74 }
75 }
76
77 private void ValidateBundle(WixBundleSymbol symbol)
78 {
79 if (symbol.Condition != null)
80 {
81 this.BackendHelper.ValidateBundleCondition(symbol.SourceLineNumbers, "Bundle", "Condition", symbol.Condition, BundleConditionPhase.Startup);
82 }
83 }
84
85 private void ValidateChainPackage(WixBundlePackageSymbol symbol, string elementName)
86 {
87 if (!String.IsNullOrEmpty(symbol.InstallCondition))
88 {
89 this.BackendHelper.ValidateBundleCondition(symbol.SourceLineNumbers, elementName, "InstallCondition", symbol.InstallCondition, BundleConditionPhase.Plan);
90 }
91 }
92
93 private void ValidateExePackage(WixBundleExePackageSymbol symbol, WixBundlePackageSymbol packageSymbol)
94 {
95 this.ValidateChainPackage(packageSymbol, "ExePackage");
96
97 if (!packageSymbol.Permanent)
98 {
99 this.BackendHelper.ValidateBundleCondition(symbol.SourceLineNumbers, "ExePackage", "DetectCondition", symbol.DetectCondition, BundleConditionPhase.Detect);
100 }
101 }
102
103 private void ValidateMsiPackage(WixBundleMsiPackageSymbol symbol, WixBundlePackageSymbol packageSymbol)
104 {
105 this.ValidateChainPackage(packageSymbol, "MsiPackage");
106 }
107
108 private void ValidateMsiProperty(WixBundleMsiPropertySymbol symbol)
109 {
110 this.BackendHelper.ValidateBundleMsiPropertyName(symbol.SourceLineNumbers, "MsiProperty", "Name", symbol.Name);
111
112 if (symbol.Condition != null)
113 {
114 this.BackendHelper.ValidateBundleCondition(symbol.SourceLineNumbers, "MsiProperty", "Condition", symbol.Condition, BundleConditionPhase.Execute);
115 }
116 }
117
118 private void ValidateMspPackage(WixBundleMspPackageSymbol symbol, WixBundlePackageSymbol packageSymbol)
119 {
120 this.ValidateChainPackage(packageSymbol, "MspPackage");
121 }
122
123 private void ValidateMsuPackage(WixBundleMsuPackageSymbol symbol, WixBundlePackageSymbol packageSymbol)
124 {
125 this.ValidateChainPackage(packageSymbol, "MsuPackage");
126
127 if (!packageSymbol.Permanent)
128 {
129 this.BackendHelper.ValidateBundleCondition(symbol.SourceLineNumbers, "MsuPackage", "DetectCondition", symbol.DetectCondition, BundleConditionPhase.Detect);
130 }
131 }
132
133 private void ValidatePackageCommandLine(WixBundlePackageCommandLineSymbol symbol)
134 {
135 if (symbol.Condition != null)
136 {
137 this.BackendHelper.ValidateBundleCondition(symbol.SourceLineNumbers, "CommandLine", "Condition", symbol.Condition, BundleConditionPhase.Execute);
138 }
139 }
140
141 private void ValidateSearch(WixSearchSymbol symbol)
142 {
143 this.BackendHelper.ValidateBundleVariableName(symbol.SourceLineNumbers, "*Search", "Variable", symbol.Variable);
144
145 if (symbol.Condition != null)
146 {
147 this.BackendHelper.ValidateBundleCondition(symbol.SourceLineNumbers, "*Search", "Condition", symbol.Condition, BundleConditionPhase.Detect);
148 }
149 }
150
151 private void ValidateVariable(WixBundleVariableSymbol symbol)
152 {
153 this.BackendHelper.ValidateBundleVariableName(symbol.SourceLineNumbers, "Variable", "Name", symbol.Id.Id);
154 }
155 }
156}
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs
index 3bd1f938..c3285884 100644
--- a/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs
+++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs
@@ -15,7 +15,7 @@ namespace WixToolset.Core.Burn.Bundles
15 15
16 internal class ProcessPayloadsCommand 16 internal class ProcessPayloadsCommand
17 { 17 {
18 public ProcessPayloadsCommand(IBackendHelper backendHelper, IPayloadHarvester payloadHarvester, IEnumerable<WixBundlePayloadSymbol> payloads, PackagingType defaultPackaging, string layoutDirectory) 18 public ProcessPayloadsCommand(IBurnBackendHelper backendHelper, IPayloadHarvester payloadHarvester, IEnumerable<WixBundlePayloadSymbol> payloads, PackagingType defaultPackaging, string layoutDirectory)
19 { 19 {
20 this.BackendHelper = backendHelper; 20 this.BackendHelper = backendHelper;
21 this.PayloadHarvester = payloadHarvester; 21 this.PayloadHarvester = payloadHarvester;
@@ -28,7 +28,7 @@ namespace WixToolset.Core.Burn.Bundles
28 28
29 public IEnumerable<ITrackedFile> TrackedFiles { get; private set; } 29 public IEnumerable<ITrackedFile> TrackedFiles { get; private set; }
30 30
31 private IBackendHelper BackendHelper { get; } 31 private IBurnBackendHelper BackendHelper { get; }
32 32
33 private IPayloadHarvester PayloadHarvester { get; } 33 private IPayloadHarvester PayloadHarvester { get; }
34 34
diff --git a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs
index e4d2b0c9..d77606fb 100644
--- a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs
+++ b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs
@@ -20,6 +20,7 @@ namespace WixToolset.Core.Burn.ExtensibilityServices
20 public static readonly XmlWriterSettings WriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment }; 20 public static readonly XmlWriterSettings WriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment };
21 21
22 private readonly IBackendHelper backendHelper; 22 private readonly IBackendHelper backendHelper;
23 private readonly IBundleValidator bundleValidator;
23 24
24 private ManifestData BootstrapperApplicationManifestData { get; } = new ManifestData(); 25 private ManifestData BootstrapperApplicationManifestData { get; } = new ManifestData();
25 26
@@ -28,49 +29,105 @@ namespace WixToolset.Core.Burn.ExtensibilityServices
28 public BurnBackendHelper(IServiceProvider serviceProvider) 29 public BurnBackendHelper(IServiceProvider serviceProvider)
29 { 30 {
30 this.backendHelper = serviceProvider.GetService<IBackendHelper>(); 31 this.backendHelper = serviceProvider.GetService<IBackendHelper>();
32 this.bundleValidator = serviceProvider.GetService<IBundleValidator>();
31 } 33 }
32 34
33 #region IBackendHelper interfaces 35 #region IBackendHelper interfaces
34 36
35 public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); 37 public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly)
36 38 {
37 public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); 39 return this.backendHelper.CreateFileFacade(file, assembly);
40 }
38 41
39 public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); 42 public IFileFacade CreateFileFacade(FileRow fileRow)
43 {
44 return this.backendHelper.CreateFileFacade(fileRow);
45 }
40 46
41 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); 47 public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol)
48 {
49 return this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol);
50 }
42 51
43 public string CreateGuid() => this.backendHelper.CreateGuid(); 52 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null)
53 {
54 return this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers);
55 }
44 56
45 public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); 57 public string CreateGuid()
58 {
59 return this.backendHelper.CreateGuid();
60 }
46 61
47 public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); 62 public string CreateGuid(Guid namespaceGuid, string value)
63 {
64 return this.backendHelper.CreateGuid(namespaceGuid, value);
65 }
48 66
49 public IReadOnlyList<ITrackedFile> ExtractEmbeddedFiles(IEnumerable<IExpectedExtractFile> embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); 67 public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name)
68 {
69 return this.backendHelper.CreateResolvedDirectory(directoryParent, name);
70 }
50 71
51 public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); 72 public IReadOnlyList<ITrackedFile> ExtractEmbeddedFiles(IEnumerable<IExpectedExtractFile> embeddedFiles)
73 {
74 return this.backendHelper.ExtractEmbeddedFiles(embeddedFiles);
75 }
52 76
53 public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); 77 public string GenerateIdentifier(string prefix, params string[] args)
78 {
79 return this.backendHelper.GenerateIdentifier(prefix, args);
80 }
54 81
55 public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); 82 public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null)
83 {
84 return this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers);
85 }
56 86
57 public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); 87 public string GetMsiFileName(string value, bool source, bool longName)
88 {
89 return this.backendHelper.GetMsiFileName(value, source, longName);
90 }
58 91
59 public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); 92 public bool IsValidBinderVariable(string variable)
93 {
94 return this.backendHelper.IsValidBinderVariable(variable);
95 }
60 96
61 public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); 97 public bool IsValidFourPartVersion(string version)
98 {
99 return this.backendHelper.IsValidFourPartVersion(version);
100 }
62 101
63 public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); 102 public bool IsValidIdentifier(string id)
103 {
104 return this.backendHelper.IsValidIdentifier(id);
105 }
64 106
65 public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); 107 public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative)
108 {
109 return this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative);
110 }
66 111
67 public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); 112 public bool IsValidShortFilename(string filename, bool allowWildcards)
113 {
114 return this.backendHelper.IsValidShortFilename(filename, allowWildcards);
115 }
68 116
69 public void ResolveDelayedFields(IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); 117 public void ResolveDelayedFields(IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache)
118 {
119 this.backendHelper.ResolveDelayedFields(delayedFields, variableCache);
120 }
70 121
71 public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); 122 public string[] SplitMsiFileName(string value)
123 {
124 return this.backendHelper.SplitMsiFileName(value);
125 }
72 126
73 public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); 127 public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null)
128 {
129 return this.backendHelper.TrackFile(path, type, sourceLineNumbers);
130 }
74 131
75 #endregion 132 #endregion
76 133
@@ -100,6 +157,28 @@ namespace WixToolset.Core.Burn.ExtensibilityServices
100 157
101 #endregion 158 #endregion
102 159
160 #region IBundleValidator
161 public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath)
162 {
163 return this.bundleValidator.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath);
164 }
165
166 public bool ValidateBundleMsiPropertyName(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string propertyName)
167 {
168 return this.bundleValidator.ValidateBundleMsiPropertyName(sourceLineNumbers, elementName, attributeName, propertyName);
169 }
170
171 public bool ValidateBundleVariableName(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string variableName)
172 {
173 return this.bundleValidator.ValidateBundleVariableName(sourceLineNumbers, elementName, attributeName, variableName);
174 }
175
176 public bool ValidateBundleCondition(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string condition, BundleConditionPhase phase)
177 {
178 return this.bundleValidator.ValidateBundleCondition(sourceLineNumbers, elementName, attributeName, condition, phase);
179 }
180 #endregion
181
103 #region IInternalBurnBackendHelper interfaces 182 #region IInternalBurnBackendHelper interfaces
104 183
105 public void WriteBootstrapperApplicationData(XmlWriter writer) 184 public void WriteBootstrapperApplicationData(XmlWriter writer)
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs
index 8305b5e6..b3b4421a 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs
@@ -23,45 +23,100 @@ namespace WixToolset.Core.WindowsInstaller.ExtensibilityServices
23 23
24 #region IBackendHelper interfaces 24 #region IBackendHelper interfaces
25 25
26 public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); 26 public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly)
27 27 {
28 public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); 28 return this.backendHelper.CreateFileFacade(file, assembly);
29 }
29 30
30 public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); 31 public IFileFacade CreateFileFacade(FileRow fileRow)
32 {
33 return this.backendHelper.CreateFileFacade(fileRow);
34 }
31 35
32 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); 36 public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol)
37 {
38 return this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol);
39 }
33 40
34 public string CreateGuid() => this.backendHelper.CreateGuid(); 41 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null)
42 {
43 return this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers);
44 }
35 45
36 public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); 46 public string CreateGuid()
47 {
48 return this.backendHelper.CreateGuid();
49 }
37 50
38 public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); 51 public string CreateGuid(Guid namespaceGuid, string value)
52 {
53 return this.backendHelper.CreateGuid(namespaceGuid, value);
54 }
39 55
40 public IReadOnlyList<ITrackedFile> ExtractEmbeddedFiles(IEnumerable<IExpectedExtractFile> embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); 56 public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name)
57 {
58 return this.backendHelper.CreateResolvedDirectory(directoryParent, name);
59 }
41 60
42 public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); 61 public IReadOnlyList<ITrackedFile> ExtractEmbeddedFiles(IEnumerable<IExpectedExtractFile> embeddedFiles)
62 {
63 return this.backendHelper.ExtractEmbeddedFiles(embeddedFiles);
64 }
43 65
44 public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); 66 public string GenerateIdentifier(string prefix, params string[] args)
67 {
68 return this.backendHelper.GenerateIdentifier(prefix, args);
69 }
45 70
46 public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); 71 public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null)
72 {
73 return this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers);
74 }
47 75
48 public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); 76 public string GetMsiFileName(string value, bool source, bool longName)
77 {
78 return this.backendHelper.GetMsiFileName(value, source, longName);
79 }
49 80
50 public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); 81 public bool IsValidBinderVariable(string variable)
82 {
83 return this.backendHelper.IsValidBinderVariable(variable);
84 }
51 85
52 public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); 86 public bool IsValidFourPartVersion(string version)
87 {
88 return this.backendHelper.IsValidFourPartVersion(version);
89 }
53 90
54 public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); 91 public bool IsValidIdentifier(string id)
92 {
93 return this.backendHelper.IsValidIdentifier(id);
94 }
55 95
56 public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); 96 public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative)
97 {
98 return this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative);
99 }
57 100
58 public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); 101 public bool IsValidShortFilename(string filename, bool allowWildcards)
102 {
103 return this.backendHelper.IsValidShortFilename(filename, allowWildcards);
104 }
59 105
60 public void ResolveDelayedFields(IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); 106 public void ResolveDelayedFields(IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache)
107 {
108 this.backendHelper.ResolveDelayedFields(delayedFields, variableCache);
109 }
61 110
62 public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); 111 public string[] SplitMsiFileName(string value)
112 {
113 return this.backendHelper.SplitMsiFileName(value);
114 }
63 115
64 public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); 116 public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null)
117 {
118 return this.backendHelper.TrackFile(path, type, sourceLineNumbers);
119 }
65 120
66 #endregion 121 #endregion
67 122
diff --git a/src/wix/WixToolset.Core/Common.cs b/src/wix/WixToolset.Core/Common.cs
index 848f009a..8e341c52 100644
--- a/src/wix/WixToolset.Core/Common.cs
+++ b/src/wix/WixToolset.Core/Common.cs
@@ -27,27 +27,6 @@ namespace WixToolset.Core
27 internal static readonly char[] IllegalRelativeLongFilenameCharacters = new[] { '?', '*', '|', '>', '<', ':', '\"' }; // like illegal, but we allow '\' and '/' 27 internal static readonly char[] IllegalRelativeLongFilenameCharacters = new[] { '?', '*', '|', '>', '<', ':', '\"' }; // like illegal, but we allow '\' and '/'
28 internal static readonly char[] IllegalWildcardLongFilenameCharacters = new[] { '\\', '/', '|', '>', '<', ':', '\"' }; // like illegal: but we allow '*' and '?' 28 internal static readonly char[] IllegalWildcardLongFilenameCharacters = new[] { '\\', '/', '|', '>', '<', ':', '\"' }; // like illegal: but we allow '*' and '?'
29 29
30 public static string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath, IMessaging messageHandler)
31 {
32 const string root = @"C:\";
33 if (!Path.IsPathRooted(relativePath))
34 {
35 var normalizedPath = Path.GetFullPath(root + relativePath);
36 if (normalizedPath.StartsWith(root))
37 {
38 var canonicalizedPath = normalizedPath.Substring(root.Length);
39 if (canonicalizedPath != relativePath)
40 {
41 messageHandler.Write(WarningMessages.PathCanonicalized(sourceLineNumbers, elementName, attributeName, relativePath, canonicalizedPath));
42 }
43 return canonicalizedPath;
44 }
45 }
46
47 messageHandler.Write(ErrorMessages.PayloadMustBeRelativeToCache(sourceLineNumbers, elementName, attributeName, relativePath));
48 return relativePath;
49 }
50
51 /// <summary> 30 /// <summary>
52 /// Gets a valid code page from the given web name or integer value. 31 /// Gets a valid code page from the given web name or integer value.
53 /// </summary> 32 /// </summary>
diff --git a/src/wix/WixToolset.Core/CompilerCore.cs b/src/wix/WixToolset.Core/CompilerCore.cs
index dc44f1b6..7effa0b9 100644
--- a/src/wix/WixToolset.Core/CompilerCore.cs
+++ b/src/wix/WixToolset.Core/CompilerCore.cs
@@ -17,24 +17,6 @@ namespace WixToolset.Core
17 using WixToolset.Extensibility.Data; 17 using WixToolset.Extensibility.Data;
18 using WixToolset.Extensibility.Services; 18 using WixToolset.Extensibility.Services;
19 19
20 internal enum ValueListKind
21 {
22 /// <summary>
23 /// A list of values with nothing before the final value.
24 /// </summary>
25 None,
26
27 /// <summary>
28 /// A list of values with 'and' before the final value.
29 /// </summary>
30 And,
31
32 /// <summary>
33 /// A list of values with 'or' before the final value.
34 /// </summary>
35 Or
36 }
37
38 /// <summary> 20 /// <summary>
39 /// Core class for the compiler. 21 /// Core class for the compiler.
40 /// </summary> 22 /// </summary>
@@ -43,80 +25,6 @@ namespace WixToolset.Core
43 internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; 25 internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/";
44 internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; 26 internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs";
45 27
46 // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113)
47 private static readonly List<string> BuiltinBundleVariables = new List<string>(
48 new string[] {
49 "AdminToolsFolder",
50 "AppDataFolder",
51 "CommonAppDataFolder",
52 "CommonFiles64Folder",
53 "CommonFilesFolder",
54 "CompatibilityMode",
55 "Date",
56 "DesktopFolder",
57 "FavoritesFolder",
58 "FontsFolder",
59 "InstallerName",
60 "InstallerVersion",
61 "LocalAppDataFolder",
62 "LogonUser",
63 "MyPicturesFolder",
64 "NativeMachine",
65 "NTProductType",
66 "NTSuiteBackOffice",
67 "NTSuiteDataCenter",
68 "NTSuiteEnterprise",
69 "NTSuitePersonal",
70 "NTSuiteSmallBusiness",
71 "NTSuiteSmallBusinessRestricted",
72 "NTSuiteWebServer",
73 "PersonalFolder",
74 "Privileged",
75 "ProgramFiles64Folder",
76 "ProgramFiles6432Folder",
77 "ProgramFilesFolder",
78 "ProgramMenuFolder",
79 "RebootPending",
80 "SendToFolder",
81 "ServicePackLevel",
82 "StartMenuFolder",
83 "StartupFolder",
84 "System64Folder",
85 "SystemFolder",
86 "TempFolder",
87 "TemplateFolder",
88 "TerminalServer",
89 "UserLanguageID",
90 "UserUILanguageID",
91 "VersionMsi",
92 "VersionNT",
93 "VersionNT64",
94 "WindowsFolder",
95 "WindowsVolume",
96 "WixBundleAction",
97 "WixBundleCommandLineAction",
98 "WixBundleForcedRestartPackage",
99 "WixBundleElevated",
100 "WixBundleInstalled",
101 "WixBundleProviderKey",
102 "WixBundleTag",
103 "WixBundleVersion",
104 });
105
106 private static readonly List<string> DisallowedMsiProperties = new List<string>(
107 new string[] {
108 "ACTION",
109 "ADDLOCAL",
110 "ADDSOURCE",
111 "ADDDEFAULT",
112 "ADVERTISE",
113 "ALLUSERS",
114 "REBOOT",
115 "REINSTALL",
116 "REINSTALLMODE",
117 "REMOVE"
118 });
119
120 private readonly Dictionary<XNamespace, ICompilerExtension> extensions; 28 private readonly Dictionary<XNamespace, ICompilerExtension> extensions;
121 private readonly IParseHelper parseHelper; 29 private readonly IParseHelper parseHelper;
122 private readonly Intermediate intermediate; 30 private readonly Intermediate intermediate;
@@ -857,11 +765,7 @@ namespace WixToolset.Core
857 765
858 if (!String.IsNullOrEmpty(value)) 766 if (!String.IsNullOrEmpty(value))
859 { 767 {
860 if (CompilerCore.BuiltinBundleVariables.Contains(value)) 768 this.parseHelper.ValidateBundleVariableName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value);
861 {
862 string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.BuiltinBundleVariables);
863 this.Write(ErrorMessages.IllegalAttributeValueWithIllegalList(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, illegalValues));
864 }
865 } 769 }
866 770
867 return value; 771 return value;
@@ -879,11 +783,7 @@ namespace WixToolset.Core
879 783
880 if (0 < value.Length) 784 if (0 < value.Length)
881 { 785 {
882 if (CompilerCore.DisallowedMsiProperties.Contains(value)) 786 this.parseHelper.ValidateBundleMsiPropertyName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value);
883 {
884 string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.DisallowedMsiProperties);
885 this.Write(ErrorMessages.DisallowedMsiProperty(sourceLineNumbers, value, illegalValues));
886 }
887 } 787 }
888 788
889 return value; 789 return value;
@@ -1095,74 +995,5 @@ namespace WixToolset.Core
1095 { 995 {
1096 return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); 996 return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable);
1097 } 997 }
1098
1099 private static string CreateValueList(ValueListKind kind, IEnumerable<string> values)
1100 {
1101 // Ideally, we could denote the list kind (and the list itself) directly in the
1102 // message XML, and detect and expand in the MessageHandler.GenerateMessageString()
1103 // method. Doing so would make vararg-style messages much easier, but impacts
1104 // every single message we format. For now, callers just have to know when a
1105 // message takes a list of values in a single string argument, the caller will
1106 // have to do the expansion themselves. (And, unfortunately, hard-code the knowledge
1107 // that the list is an 'and' or 'or' list.)
1108
1109 // For a localizable solution, we need to be able to get the list format string
1110 // from resources. We aren't currently localized right now, so the values are
1111 // just hard-coded.
1112 const string valueFormat = "'{0}'";
1113 const string valueSeparator = ", ";
1114 string terminalTerm = String.Empty;
1115
1116 switch (kind)
1117 {
1118 case ValueListKind.None:
1119 terminalTerm = "";
1120 break;
1121 case ValueListKind.And:
1122 terminalTerm = "and ";
1123 break;
1124 case ValueListKind.Or:
1125 terminalTerm = "or ";
1126 break;
1127 }
1128
1129 StringBuilder list = new StringBuilder();
1130
1131 // This weird construction helps us determine when we're adding the last value
1132 // to the list. Instead of adding them as we encounter them, we cache the current
1133 // value and append the *previous* one.
1134 string previousValue = null;
1135 bool haveValues = false;
1136 foreach (string value in values)
1137 {
1138 if (null != previousValue)
1139 {
1140 if (haveValues)
1141 {
1142 list.Append(valueSeparator);
1143 }
1144 list.AppendFormat(valueFormat, previousValue);
1145 haveValues = true;
1146 }
1147
1148 previousValue = value;
1149 }
1150
1151 // If we have no previous value, that means that the list contained no values, and
1152 // something has gone very wrong.
1153 Debug.Assert(null != previousValue);
1154 if (null != previousValue)
1155 {
1156 if (haveValues)
1157 {
1158 list.Append(valueSeparator);
1159 list.Append(terminalTerm);
1160 }
1161 list.AppendFormat(valueFormat, previousValue);
1162 haveValues = true;
1163 }
1164
1165 return list.ToString();
1166 }
1167 } 998 }
1168} 999}
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs
index 9d657e1a..3348ad0b 100644
--- a/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs
+++ b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs
@@ -64,11 +64,6 @@ namespace WixToolset.Core.ExtensibilityServices
64 return Common.GenerateIdentifier(prefix, args); 64 return Common.GenerateIdentifier(prefix, args);
65 } 65 }
66 66
67 public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath)
68 {
69 return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging);
70 }
71
72 public int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) 67 public int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null)
73 { 68 {
74 return Common.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); 69 return Common.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers);
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/BundleValidator.cs b/src/wix/WixToolset.Core/ExtensibilityServices/BundleValidator.cs
new file mode 100644
index 00000000..0149fe94
--- /dev/null
+++ b/src/wix/WixToolset.Core/ExtensibilityServices/BundleValidator.cs
@@ -0,0 +1,326 @@
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.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Text;
10 using WixToolset.Data;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 internal class BundleValidator : IBundleValidator
15 {
16 public BundleValidator(IServiceProvider serviceProvider)
17 {
18 this.Messaging = serviceProvider.GetService<IMessaging>();
19 }
20
21 protected IMessaging Messaging { get; }
22
23 private enum ValueListKind
24 {
25 /// <summary>
26 /// A list of values with nothing before the final value.
27 /// </summary>
28 None,
29
30 /// <summary>
31 /// A list of values with 'and' before the final value.
32 /// </summary>
33 And,
34
35 /// <summary>
36 /// A list of values with 'or' before the final value.
37 /// </summary>
38 Or,
39 }
40
41 // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 207)
42 private static readonly List<string> BuiltinBundleVariables = new List<string>(
43 new string[] {
44 "AdminToolsFolder",
45 "AppDataFolder",
46 "CommonAppDataFolder",
47 "CommonFiles64Folder",
48 "CommonFilesFolder",
49 "CompatibilityMode",
50 "Date",
51 "DesktopFolder",
52 "FavoritesFolder",
53 "FontsFolder",
54 "InstallerName",
55 "InstallerVersion",
56 "LocalAppDataFolder",
57 "LogonUser",
58 "MyPicturesFolder",
59 "NativeMachine",
60 "NTProductType",
61 "NTSuiteBackOffice",
62 "NTSuiteDataCenter",
63 "NTSuiteEnterprise",
64 "NTSuitePersonal",
65 "NTSuiteSmallBusiness",
66 "NTSuiteSmallBusinessRestricted",
67 "NTSuiteWebServer",
68 "PersonalFolder",
69 "Privileged",
70 "ProgramFiles64Folder",
71 "ProgramFiles6432Folder",
72 "ProgramFilesFolder",
73 "ProgramMenuFolder",
74 "RebootPending",
75 "SendToFolder",
76 "ServicePackLevel",
77 "StartMenuFolder",
78 "StartupFolder",
79 "System64Folder",
80 "SystemFolder",
81 "TempFolder",
82 "TemplateFolder",
83 "TerminalServer",
84 "UserLanguageID",
85 "UserUILanguageID",
86 "VersionMsi",
87 "VersionNT",
88 "VersionNT64",
89 "WindowsFolder",
90 "WindowsVolume",
91 "WixBundleAction",
92 "WixBundleCommandLineAction",
93 "WixBundleForcedRestartPackage",
94 "WixBundleElevated",
95 "WixBundleInstalled",
96 "WixBundleProviderKey",
97 "WixBundleTag",
98 "WixBundleVersion",
99 });
100
101 private static readonly List<string> DisallowedMsiProperties = new List<string>(
102 new string[] {
103 "ACTION",
104 "ADDLOCAL",
105 "ADDSOURCE",
106 "ADDDEFAULT",
107 "ADVERTISE",
108 "ALLUSERS",
109 "REBOOT",
110 "REINSTALL",
111 "REINSTALLMODE",
112 "REMOVE"
113 });
114
115 private static readonly List<string> UnavailableStartupVariables = new List<string>(
116 new string[] {
117 "RebootPending",
118 "WixBundleAction",
119 "WixBundleInstalled",
120 });
121
122 private static readonly List<string> UnavailableDetectVariables = new List<string>(
123 new string[] {
124 "WixBundleAction",
125 });
126
127 public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath)
128 {
129 const string root = @"C:\";
130 if (!Path.IsPathRooted(relativePath))
131 {
132 var normalizedPath = Path.GetFullPath(root + relativePath);
133 if (normalizedPath.StartsWith(root))
134 {
135 var canonicalizedPath = normalizedPath.Substring(root.Length);
136 if (canonicalizedPath != relativePath)
137 {
138 this.Messaging.Write(WarningMessages.PathCanonicalized(sourceLineNumbers, elementName, attributeName, relativePath, canonicalizedPath));
139 }
140 return canonicalizedPath;
141 }
142 }
143
144 this.Messaging.Write(ErrorMessages.PayloadMustBeRelativeToCache(sourceLineNumbers, elementName, attributeName, relativePath));
145 return relativePath;
146 }
147
148 public bool ValidateBundleVariableName(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string variableName)
149 {
150 if (String.IsNullOrEmpty(variableName))
151 {
152 this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, elementName, attributeName));
153
154 return false;
155 }
156 else if (BuiltinBundleVariables.Contains(variableName))
157 {
158 var illegalValues = CreateValueList(ValueListKind.Or, BuiltinBundleVariables);
159 this.Messaging.Write(ErrorMessages.IllegalAttributeValueWithIllegalList(sourceLineNumbers, elementName, attributeName, variableName, illegalValues));
160
161 return false;
162 }
163 else
164 {
165 return true;
166 }
167 }
168
169 public bool ValidateBundleMsiPropertyName(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string propertyName)
170 {
171 if (String.IsNullOrEmpty(propertyName))
172 {
173 this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, elementName, attributeName));
174
175 return false;
176 }
177 else if (DisallowedMsiProperties.Contains(propertyName))
178 {
179 var illegalValues = CreateValueList(ValueListKind.Or, DisallowedMsiProperties);
180 this.Messaging.Write(ErrorMessages.DisallowedMsiProperty(sourceLineNumbers, propertyName, illegalValues));
181
182 return false;
183 }
184 else
185 {
186 return true;
187 }
188 }
189
190 public bool ValidateBundleCondition(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string condition, BundleConditionPhase phase)
191 {
192 if (!this.TryParseCondition(sourceLineNumbers, elementName, attributeName, condition))
193 {
194 return false;
195 }
196
197
198 // TODO: These lists are incomplete.
199 List<string> unavailableVariables = null;
200 switch (phase)
201 {
202 case BundleConditionPhase.Startup:
203 unavailableVariables = UnavailableStartupVariables;
204 break;
205 case BundleConditionPhase.Detect:
206 unavailableVariables = UnavailableDetectVariables;
207 break;
208 }
209
210 if (unavailableVariables != null)
211 {
212 return this.ValidateBundleConditionUnavailableVariables(sourceLineNumbers, elementName, attributeName, condition, unavailableVariables);
213 }
214 else
215 {
216 return true;
217 }
218 }
219
220 private bool ValidateBundleConditionUnavailableVariables(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string condition, List<string> unavailableVariables)
221 {
222 foreach (var variableName in unavailableVariables)
223 {
224 //TODO: use the results of parsing to validate that the restricted variables are actually used as variables
225 if (condition.Contains(variableName))
226 {
227 var illegalValues = CreateValueList(ValueListKind.Or, unavailableVariables);
228 this.Messaging.Write(WarningMessages.UnavailableBundleConditionVariable(sourceLineNumbers, elementName, attributeName, variableName, illegalValues));
229
230 return false;
231 }
232 }
233
234 return true;
235 }
236
237 private bool TryParseCondition(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string condition)
238 {
239 if (String.IsNullOrEmpty(condition))
240 {
241 this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, elementName, attributeName));
242
243 return false;
244 }
245 //TODO: Actually parse the condition to definitively tell which Variables are referenced.
246 else if (condition.Trim() == "=")
247 {
248 this.Messaging.Write(ErrorMessages.InvalidBundleCondition(sourceLineNumbers, elementName, attributeName, condition));
249 return false;
250 }
251 else
252 {
253 return true;
254 }
255 }
256
257 private static string CreateValueList(ValueListKind kind, IEnumerable<string> values)
258 {
259 // Ideally, we could denote the list kind (and the list itself) directly in the
260 // message XML, and detect and expand in the MessageHandler.GenerateMessageString()
261 // method. Doing so would make vararg-style messages much easier, but impacts
262 // every single message we format. For now, callers just have to know when a
263 // message takes a list of values in a single string argument, the caller will
264 // have to do the expansion themselves. (And, unfortunately, hard-code the knowledge
265 // that the list is an 'and' or 'or' list.)
266
267 // For a localizable solution, we need to be able to get the list format string
268 // from resources. We aren't currently localized right now, so the values are
269 // just hard-coded.
270 const string valueFormat = "'{0}'";
271 const string valueSeparator = ", ";
272 var terminalTerm = String.Empty;
273
274 switch (kind)
275 {
276 case ValueListKind.None:
277 terminalTerm = "";
278 break;
279 case ValueListKind.And:
280 terminalTerm = "and ";
281 break;
282 case ValueListKind.Or:
283 terminalTerm = "or ";
284 break;
285 }
286
287 var list = new StringBuilder();
288
289 // This weird construction helps us determine when we're adding the last value
290 // to the list. Instead of adding them as we encounter them, we cache the current
291 // value and append the *previous* one.
292 string previousValue = null;
293 var haveValues = false;
294 foreach (var value in values)
295 {
296 if (null != previousValue)
297 {
298 if (haveValues)
299 {
300 list.Append(valueSeparator);
301 }
302 list.AppendFormat(valueFormat, previousValue);
303 haveValues = true;
304 }
305
306 previousValue = value;
307 }
308
309 // If we have no previous value, that means that the list contained no values, and
310 // something has gone very wrong.
311 Debug.Assert(null != previousValue);
312 if (null != previousValue)
313 {
314 if (haveValues)
315 {
316 list.Append(valueSeparator);
317 list.Append(terminalTerm);
318 }
319 list.AppendFormat(valueFormat, previousValue);
320 //haveValues = true;
321 }
322
323 return list.ToString();
324 }
325 }
326}
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
index 1a678b0d..fa4f50ba 100644
--- a/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
+++ b/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
@@ -22,11 +22,14 @@ namespace WixToolset.Core.ExtensibilityServices
22 { 22 {
23 this.ServiceProvider = serviceProvider; 23 this.ServiceProvider = serviceProvider;
24 24
25 this.BundleValidator = serviceProvider.GetService<IBundleValidator>();
25 this.Messaging = serviceProvider.GetService<IMessaging>(); 26 this.Messaging = serviceProvider.GetService<IMessaging>();
26 } 27 }
27 28
28 private IServiceProvider ServiceProvider { get; } 29 private IServiceProvider ServiceProvider { get; }
29 30
31 private IBundleValidator BundleValidator { get; }
32
30 private IMessaging Messaging { get; } 33 private IMessaging Messaging { get; }
31 34
32 private ISymbolDefinitionCreator Creator { get; set; } 35 private ISymbolDefinitionCreator Creator { get; set; }
@@ -239,11 +242,14 @@ namespace WixToolset.Core.ExtensibilityServices
239 242
240 public void CreateWixSearchSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId) 243 public void CreateWixSearchSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId)
241 { 244 {
242 // TODO: verify variable is not a standard bundle variable
243 if (variable == null) 245 if (variable == null)
244 { 246 {
245 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable")); 247 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable"));
246 } 248 }
249 else
250 {
251 this.BundleValidator.ValidateBundleVariableName(sourceLineNumbers, elementName, "Variable", variable);
252 }
247 253
248 section.AddSymbol(new WixSearchSymbol(sourceLineNumbers, id) 254 section.AddSymbol(new WixSearchSymbol(sourceLineNumbers, id)
249 { 255 {
@@ -623,11 +629,6 @@ namespace WixToolset.Core.ExtensibilityServices
623 } 629 }
624 } 630 }
625 631
626 public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath)
627 {
628 return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging);
629 }
630
631 public SourceLineNumber GetSourceLineNumbers(XElement element) 632 public SourceLineNumber GetSourceLineNumbers(XElement element)
632 { 633 {
633 return Preprocessor.GetSourceLineNumbers(element); 634 return Preprocessor.GetSourceLineNumbers(element);
@@ -858,5 +859,27 @@ namespace WixToolset.Core.ExtensibilityServices
858 859
859 return extension != null; 860 return extension != null;
860 } 861 }
862
863 #region IBundleValidator
864 public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath)
865 {
866 return this.BundleValidator.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath);
867 }
868
869 public bool ValidateBundleMsiPropertyName(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string propertyName)
870 {
871 return this.BundleValidator.ValidateBundleMsiPropertyName(sourceLineNumbers, elementName, attributeName, propertyName);
872 }
873
874 public bool ValidateBundleVariableName(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string variableName)
875 {
876 return this.BundleValidator.ValidateBundleVariableName(sourceLineNumbers, elementName, attributeName, variableName);
877 }
878
879 public bool ValidateBundleCondition(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string condition, BundleConditionPhase phase)
880 {
881 return this.BundleValidator.ValidateBundleCondition(sourceLineNumbers, elementName, attributeName, condition, phase);
882 }
883 #endregion
861 } 884 }
862} 885}
diff --git a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs
index a74ba6b3..9fbf6717 100644
--- a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs
+++ b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs
@@ -20,6 +20,7 @@ namespace WixToolset.Core
20 // Singletons. 20 // Singletons.
21 this.AddService((provider, singletons) => AddSingleton<IExtensionManager>(singletons, new ExtensionManager(provider))); 21 this.AddService((provider, singletons) => AddSingleton<IExtensionManager>(singletons, new ExtensionManager(provider)));
22 this.AddService((provider, singletons) => AddSingleton<IMessaging>(singletons, new Messaging())); 22 this.AddService((provider, singletons) => AddSingleton<IMessaging>(singletons, new Messaging()));
23 this.AddService((provider, singletons) => AddSingleton<IBundleValidator>(singletons, new BundleValidator(provider)));
23 this.AddService((provider, singletons) => AddSingleton<ISymbolDefinitionCreator>(singletons, new SymbolDefinitionCreator(provider))); 24 this.AddService((provider, singletons) => AddSingleton<ISymbolDefinitionCreator>(singletons, new SymbolDefinitionCreator(provider)));
24 this.AddService((provider, singletons) => AddSingleton<IParseHelper>(singletons, new ParseHelper(provider))); 25 this.AddService((provider, singletons) => AddSingleton<IParseHelper>(singletons, new ParseHelper(provider)));
25 this.AddService((provider, singletons) => AddSingleton<IPreprocessHelper>(singletons, new PreprocessHelper(provider))); 26 this.AddService((provider, singletons) => AddSingleton<IPreprocessHelper>(singletons, new PreprocessHelper(provider)));
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs
index f91c0ab0..e5d6ecf1 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs
@@ -4,6 +4,7 @@ namespace WixToolsetTest.CoreIntegration
4{ 4{
5 using System; 5 using System;
6 using System.IO; 6 using System.IO;
7 using System.Linq;
7 using WixBuildTools.TestSupport; 8 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage; 9 using WixToolset.Core.TestPackage;
9 using Xunit; 10 using Xunit;
@@ -129,5 +130,47 @@ namespace WixToolsetTest.CoreIntegration
129 Assert.Equal(193, result.ExitCode); 130 Assert.Equal(193, result.ExitCode);
130 } 131 }
131 } 132 }
133
134 [Fact]
135 public void GuardsAgainstVariousBundleValuesFromLoc()
136 {
137 var folder = TestData.Get(@"TestData");
138
139 using (var fs = new DisposableFileSystem())
140 {
141 var baseFolder = fs.GetFolder();
142 var intermediateFolder = Path.Combine(baseFolder, "obj");
143
144 var result = WixRunner.Execute(new[]
145 {
146 "build",
147 Path.Combine(folder, "BundleWithInvalid", "BundleWithInvalidLocValues.wxs"),
148 "-loc", Path.Combine(folder, "BundleWithInvalid", "BundleWithInvalidLocValues.wxl"),
149 "-bindpath", Path.Combine(folder, ".Data"),
150 "-bindpath", Path.Combine(folder, "DecompileSingleFileCompressed"),
151 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
152 "-intermediateFolder", intermediateFolder,
153 "-o", Path.Combine(baseFolder, @"bin\test.exe")
154 });
155
156 Assert.InRange(result.ExitCode, 2, Int32.MaxValue);
157
158 var messages = result.Messages.Select(m => m.ToString()).ToList();
159 messages.Sort();
160
161 WixAssert.CompareLineByLine(new[]
162 {
163 "*Search/@Condition contains the built-in Variable 'WixBundleAction', which is not available when it is evaluated. (Unavailable Variables are: 'WixBundleAction'.). Rewrite the condition to avoid Variables that are never valid during its evaluation.",
164 "Bundle/@Condition contains the built-in Variable 'WixBundleInstalled', which is not available when it is evaluated. (Unavailable Variables are: 'RebootPending', 'WixBundleAction', or 'WixBundleInstalled'.). Rewrite the condition to avoid Variables that are never valid during its evaluation.",
165 "ExePackage/@DetectCondition contains the built-in Variable 'WixBundleAction', which is not available when it is evaluated. (Unavailable Variables are: 'WixBundleAction'.). Rewrite the condition to avoid Variables that are never valid during its evaluation.",
166 "The *Search/@Variable attribute's value, 'WixBundleInstalled', is one of the illegal options: 'AdminToolsFolder', 'AppDataFolder', 'CommonAppDataFolder', 'CommonFiles64Folder', 'CommonFilesFolder', 'CompatibilityMode', 'Date', 'DesktopFolder', 'FavoritesFolder', 'FontsFolder', 'InstallerName', 'InstallerVersion', 'LocalAppDataFolder', 'LogonUser', 'MyPicturesFolder', 'NativeMachine', 'NTProductType', 'NTSuiteBackOffice', 'NTSuiteDataCenter', 'NTSuiteEnterprise', 'NTSuitePersonal', 'NTSuiteSmallBusiness', 'NTSuiteSmallBusinessRestricted', 'NTSuiteWebServer', 'PersonalFolder', 'Privileged', 'ProgramFiles64Folder', 'ProgramFiles6432Folder', 'ProgramFilesFolder', 'ProgramMenuFolder', 'RebootPending', 'SendToFolder', 'ServicePackLevel', 'StartMenuFolder', 'StartupFolder', 'System64Folder', 'SystemFolder', 'TempFolder', 'TemplateFolder', 'TerminalServer', 'UserLanguageID', 'UserUILanguageID', 'VersionMsi', 'VersionNT', 'VersionNT64', 'WindowsFolder', 'WindowsVolume', 'WixBundleAction', 'WixBundleCommandLineAction', 'WixBundleForcedRestartPackage', 'WixBundleElevated', 'WixBundleInstalled', 'WixBundleProviderKey', 'WixBundleTag', or 'WixBundleVersion'.",
167 "The CommandLine/@Condition attribute's value '=' is not a valid bundle condition.",
168 "The MsiPackage/@InstallCondition attribute's value '=' is not a valid bundle condition.",
169 "The MsiProperty/@Condition attribute's value '=' is not a valid bundle condition.",
170 //"The Variable/@Name attribute's value, 'WixBundleInstalled', is one of the illegal options: 'AdminToolsFolder', 'AppDataFolder', 'CommonAppDataFolder', 'CommonFiles64Folder', 'CommonFilesFolder', 'CompatibilityMode', 'Date', 'DesktopFolder', 'FavoritesFolder', 'FontsFolder', 'InstallerName', 'InstallerVersion', 'LocalAppDataFolder', 'LogonUser', 'MyPicturesFolder', 'NativeMachine', 'NTProductType', 'NTSuiteBackOffice', 'NTSuiteDataCenter', 'NTSuiteEnterprise', 'NTSuitePersonal', 'NTSuiteSmallBusiness', 'NTSuiteSmallBusinessRestricted', 'NTSuiteWebServer', 'PersonalFolder', 'Privileged', 'ProgramFiles64Folder', 'ProgramFiles6432Folder', 'ProgramFilesFolder', 'ProgramMenuFolder', 'RebootPending', 'SendToFolder', 'ServicePackLevel', 'StartMenuFolder', 'StartupFolder', 'System64Folder', 'SystemFolder', 'TempFolder', 'TemplateFolder', 'TerminalServer', 'UserLanguageID', 'UserUILanguageID', 'VersionMsi', 'VersionNT', 'VersionNT64', 'WindowsFolder', 'WindowsVolume', 'WixBundleAction', 'WixBundleCommandLineAction', 'WixBundleForcedRestartPackage', 'WixBundleElevated', 'WixBundleInstalled', 'WixBundleProviderKey', 'WixBundleTag', or 'WixBundleVersion'.",
171 "The 'REINSTALLMODE' MsiProperty is controlled by the bootstrapper and cannot be authored. (Illegal properties are: 'ACTION', 'ADDLOCAL', 'ADDSOURCE', 'ADDDEFAULT', 'ADVERTISE', 'ALLUSERS', 'REBOOT', 'REINSTALL', 'REINSTALLMODE', or 'REMOVE'.) Remove the MsiProperty element.",
172 }, messages.ToArray());
173 }
174 }
132 } 175 }
133} 176}
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithInvalid/BundleWithInvalidLocValues.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithInvalid/BundleWithInvalidLocValues.wxl
new file mode 100644
index 00000000..0b5fac56
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithInvalid/BundleWithInvalidLocValues.wxl
@@ -0,0 +1,8 @@
1<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
2 <String Id="BuiltinMsiPropertyName">REINSTALLMODE</String>
3 <String Id="BuiltinBurnVariableName">WixBundleInstalled</String>
4 <String Id="NonsenseDetectCondition">WixBundleAction = 4</String>
5 <String Id="NonsenseExecuteCondition">=</String>
6 <String Id="NonsenseGlobalCondition">WixBundleInstalled &lt;&gt; 1</String>
7 <String Id="NonsensePlanCondition">=</String>
8</WixLocalization>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithInvalid/BundleWithInvalidLocValues.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithInvalid/BundleWithInvalidLocValues.wxs
new file mode 100644
index 00000000..504f6e48
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithInvalid/BundleWithInvalidLocValues.wxs
@@ -0,0 +1,18 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Bundle Name="BundleWithInvalidUpgradeCode" Condition="!(loc.NonsenseGlobalCondition)"
3 Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="{F2A56B65-2105-44C8-A532-A93A8C169D07}">
4 <BootstrapperApplication>
5 <BootstrapperApplicationDll SourceFile="fakeba.dll" />
6 </BootstrapperApplication>
7 <Chain>
8 <MsiPackage SourceFile="example.msi" InstallCondition="!(loc.NonsensePlanCondition)">
9 <MsiProperty Condition="!(loc.NonsenseExecuteCondition)" Name="!(loc.BuiltinMsiPropertyName)" Value="1" />
10 </MsiPackage>
11 <ExePackage DetectCondition="!(loc.NonsenseDetectCondition)" UninstallArguments="-uninstall" SourceFile="burn.exe">
12 <CommandLine Condition="!(loc.NonsenseExecuteCondition)" />
13 </ExePackage>
14 </Chain>
15 <Variable Name="!(loc.BuiltinBurnVariableName)" Value="1" />
16 <SetVariable Id="Builtin" Condition="!(loc.NonsenseDetectCondition)" Variable="!(loc.BuiltinBurnVariableName)" Value="1" />
17 </Bundle>
18</Wix>